重构network库(mvp版本)搭建基础设施和核心接口

定义ITransport/ISerializer/INetworkMessage接口
NetworkIdentity组件
基础事件定义
This commit is contained in:
YHH
2025-08-13 13:07:40 +08:00
parent 25136349ff
commit 62f250b43c
97 changed files with 1877 additions and 16607 deletions

3
.gitmodules vendored
View File

@@ -28,9 +28,6 @@
[submodule "thirdparty/ecs-astar"]
path = thirdparty/ecs-astar
url = https://github.com/esengine/ecs-astar.git
[submodule "examples/electric-world"]
path = examples/electric-world
url = https://github.com/esengine/electric-world.git
[submodule "examples/lawn-mower-demo"]
path = examples/lawn-mower-demo
url = https://github.com/esengine/lawn-mower-demo.git

6
package-lock.json generated
View File

@@ -11603,7 +11603,7 @@
},
"packages/network-client": {
"name": "@esengine/network-client",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"@esengine/ecs-framework": "file:../core",
@@ -11622,7 +11622,7 @@
},
"packages/network-server": {
"name": "@esengine/network-server",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"@esengine/ecs-framework": "file:../core",
@@ -11643,7 +11643,7 @@
},
"packages/network-shared": {
"name": "@esengine/network-shared",
"version": "1.0.0",
"version": "1.0.2",
"license": "MIT",
"dependencies": {
"@esengine/ecs-framework": "file:../core",

View File

@@ -2,7 +2,6 @@
"name": "@esengine/ecs-framework-math",
"version": "1.0.5",
"description": "ECS框架2D数学库 - 提供向量、矩阵、几何形状和碰撞检测功能",
"type": "module",
"main": "bin/index.js",
"types": "bin/index.d.ts",
"files": [
@@ -22,7 +21,7 @@
"typescript"
],
"scripts": {
"clean": "rimraf bin dist",
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
"build:ts": "tsc",
"prebuild": "npm run clean",
"build": "npm run build:ts",

View File

@@ -6,6 +6,7 @@
"declaration": true,
"sourceMap": true,
"strict": true,
"composite": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",

View File

@@ -1,101 +0,0 @@
# ECS Framework 网络库 - 客户端
该包提供了完整的网络客户端功能,包括连接管理、预测、插值等现代网络游戏必需的特性。
## 主要功能
-**传输层支持**: WebSocket 和 HTTP 两种传输方式
-**网络客户端**: 完整的连接、认证、房间管理
-**网络行为**: ClientNetworkBehaviour 基类和 NetworkIdentity 组件
-**装饰器系统**: @SyncVar, @ClientRpc, @ServerRpc 装饰器
-**客户端预测**: 减少网络延迟感知的预测系统
-**插值系统**: 平滑的网络对象状态同步
-**TypeScript**: 完整的类型支持
## 安装
```bash
npm install @esengine/ecs-framework-network-client
```
## 快速开始
```typescript
import {
NetworkClient,
WebSocketClientTransport,
ClientNetworkBehaviour,
SyncVar,
ServerRpc
} from '@esengine/ecs-framework-network-client';
// 创建网络客户端
const client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080,
secure: false
}
});
// 连接到服务器
await client.connect();
// 认证
const userInfo = await client.authenticate('username', 'password');
// 获取房间列表
const rooms = await client.getRoomList();
// 加入房间
const roomInfo = await client.joinRoom('room-id');
```
## 网络行为示例
```typescript
class PlayerController extends ClientNetworkBehaviour {
@SyncVar({ clientCanModify: true })
position: { x: number; y: number } = { x: 0, y: 0 };
@SyncVar()
health: number = 100;
@ServerRpc({ requireLocalPlayer: true })
async move(direction: string): Promise<void> {
// 这个方法会被发送到服务器执行
}
@ClientRpc()
onDamaged(damage: number): void {
// 这个方法会被服务器调用
console.log(`Received damage: ${damage}`);
}
}
```
## 预测和插值
```typescript
import { PredictionSystem, InterpolationSystem } from '@esengine/ecs-framework-network-client';
// 启用预测系统
const predictionSystem = new PredictionSystem(scene, 64, 500);
scene.addSystem(predictionSystem);
// 启用插值系统
const interpolationSystem = new InterpolationSystem(scene, {
delay: 100,
enableExtrapolation: false
});
scene.addSystem(interpolationSystem);
```
## 编译状态
**编译成功** - 所有 TypeScript 错误已修复,包生成完成
## License
MIT

View File

@@ -2,27 +2,32 @@ const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
console.log('🚀 使用 Rollup 构建 network-client 包...');
console.log('🚀 使用 Rollup 构建 @esengine/network-client 包...');
async function main() {
try {
// 清理旧的dist目录
if (fs.existsSync('./dist')) {
console.log('🧹 清理旧的构建文件...');
execSync('rimraf ./dist', { stdio: 'inherit' });
}
// 执行Rollup构建
console.log('📦 执行 Rollup 构建...');
execSync('rollup -c rollup.config.cjs', { stdio: 'inherit' });
execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' });
// 生成package.json
console.log('📋 生成 package.json...');
generatePackageJson();
// 复制其他文件
console.log('📁 复制必要文件...');
copyFiles();
// 输出构建结果
showBuildResults();
console.log('✅ network-client 构建完成!');
console.log('✅ @esengine/network-client 构建完成!');
console.log('\n🚀 发布命令:');
console.log('cd dist && npm publish');
@@ -63,18 +68,19 @@ function generatePackageJson() {
],
keywords: [
'ecs',
'networking',
'network',
'client',
'prediction',
'interpolation',
'game-engine',
'multiplayer',
'game',
'browser',
'cocos',
'typescript'
],
author: sourcePackage.author,
license: sourcePackage.license,
repository: sourcePackage.repository,
dependencies: sourcePackage.dependencies,
peerDependencies: sourcePackage.peerDependencies,
publishConfig: sourcePackage.publishConfig,
engines: {
node: '>=16.0.0'
},
@@ -87,7 +93,7 @@ function generatePackageJson() {
function copyFiles() {
const filesToCopy = [
{ src: './README.md', dest: './dist/README.md' },
{ src: '../../LICENSE', dest: './dist/LICENSE' }
{ src: './LICENSE', dest: './dist/LICENSE' }
];
filesToCopy.forEach(({ src, dest }) => {

View File

@@ -1,7 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom', // 客户端使用 jsdom 环境
testEnvironment: 'jsdom', // 客户端使用jsdom环境
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testPathIgnorePatterns: ['/node_modules/'],
@@ -18,16 +18,10 @@ module.exports = {
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 60,
branches: 70,
functions: 70,
lines: 70,
statements: 70
},
'./src/core/': {
branches: 70,
functions: 80,
lines: 80,
statements: 80
}
},
verbose: true,

View File

@@ -1,8 +1,7 @@
{
"name": "@esengine/ecs-framework-network-client",
"version": "1.0.17",
"description": "ECS Framework 网络 - 客户端实现",
"type": "module",
"name": "@esengine/network-client",
"version": "1.0.1",
"description": "ECS Framework网络 - 客户端实现",
"main": "bin/index.js",
"types": "bin/index.d.ts",
"exports": {
@@ -22,15 +21,16 @@
],
"keywords": [
"ecs",
"networking",
"network",
"client",
"prediction",
"interpolation",
"game-engine",
"multiplayer",
"game",
"browser",
"cocos",
"typescript"
],
"scripts": {
"clean": "rimraf bin dist",
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
"build:ts": "tsc",
"prebuild": "npm run clean",
"build": "npm run build:ts",
@@ -45,32 +45,21 @@
"test": "jest --config jest.config.cjs",
"test:watch": "jest --watch --config jest.config.cjs",
"test:coverage": "jest --coverage --config jest.config.cjs",
"test:ci": "jest --ci --coverage --config jest.config.cjs",
"test:clear": "jest --clearCache"
"test:ci": "jest --ci --coverage --config jest.config.cjs"
},
"author": "yhh",
"license": "MIT",
"dependencies": {
"ws": "^8.18.0"
},
"peerDependencies": {
"@esengine/ecs-framework": ">=2.1.29",
"@esengine/ecs-framework-network-shared": ">=1.0.0"
"@esengine/ecs-framework": "file:../core",
"@esengine/network-shared": "file:../network-shared",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@esengine/ecs-framework": "*",
"@esengine/ecs-framework-network-shared": "*",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/node": "^20.19.0",
"@types/ws": "^8.5.13",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"rimraf": "^5.0.0",
"rollup": "^4.42.0",
"rollup-plugin-dts": "^6.2.1",
"ts-jest": "^29.4.0",
"typescript": "^5.8.3"
},

View File

@@ -7,17 +7,18 @@ const { readFileSync } = require('fs');
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
const banner = `/**
* @esengine/ecs-framework-network-client v${pkg.version}
* ECS Framework 网络库 - 客户端实现
* @esengine/network-client v${pkg.version}
* ECS网络层客户端实现
*
* @author ${pkg.author}
* @license ${pkg.license}
*/`;
// 外部依赖不打包进bundle
const external = [
'ws',
'@esengine/ecs-framework',
'@esengine/ecs-framework-network-shared'
'@esengine/network-shared',
'reflect-metadata'
];
const commonPlugins = [
@@ -81,7 +82,7 @@ module.exports = [
}
},
// UMD构建
// UMD构建 - 用于浏览器直接使用
{
input: 'bin/index.js',
output: {
@@ -92,10 +93,9 @@ module.exports = [
sourcemap: true,
exports: 'named',
globals: {
'ws': 'WebSocket',
'uuid': 'uuid',
'@esengine/ecs-framework': 'ECS',
'@esengine/ecs-framework-network-shared': 'ECSNetworkShared'
'@esengine/network-shared': 'ECSNetworkShared',
'reflect-metadata': 'Reflect'
}
},
plugins: [
@@ -119,7 +119,7 @@ module.exports = [
file: 'dist/index.d.ts',
format: 'es',
banner: `/**
* @esengine/ecs-framework-network-client v${pkg.version}
* @esengine/network-client v${pkg.version}
* TypeScript definitions
*/`
},

View File

@@ -1,179 +0,0 @@
/**
* 客户端网络行为基类
*
* 类似Unity Mirror的NetworkBehaviour提供网络功能
*/
import { Component, Entity } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { NetworkClient } from './NetworkClient';
import { NetworkIdentity } from './NetworkIdentity';
/**
* 客户端网络行为基类
*/
export abstract class ClientNetworkBehaviour extends Component {
/** 网络标识组件 */
protected networkIdentity: NetworkIdentity | null = null;
/** 网络客户端实例 */
protected networkClient: NetworkClient | null = null;
/**
* 组件初始化
*/
initialize(): void {
// 获取网络标识组件
this.networkIdentity = this.entity.getComponent(NetworkIdentity);
if (!this.networkIdentity) {
throw new Error('NetworkBehaviour requires NetworkIdentity component');
}
// 从全局获取网络客户端实例
this.networkClient = this.getNetworkClient();
}
/**
* 获取网络客户端实例
*/
protected getNetworkClient(): NetworkClient | null {
// 这里需要实现从全局管理器获取客户端实例的逻辑
// 暂时返回null在实际使用时需要通过单例模式或依赖注入获取
return null;
}
/**
* 是否为本地玩家
*/
get isLocalPlayer(): boolean {
return this.networkIdentity?.isLocalPlayer ?? false;
}
/**
* 是否为服务器权威
*/
get hasAuthority(): boolean {
return this.networkIdentity?.hasAuthority ?? false;
}
/**
* 网络ID
*/
get networkId(): string {
return this.networkIdentity?.networkId ?? '';
}
/**
* 是否已连接
*/
get isConnected(): boolean {
return this.networkClient?.isInRoom() ?? false;
}
/**
* 发送RPC到服务器
*/
protected async sendServerRpc(methodName: string, ...args: NetworkValue[]): Promise<NetworkValue> {
if (!this.networkClient || !this.networkIdentity) {
throw new Error('Network client or identity not available');
}
return this.networkClient.sendRpc(this.networkIdentity.networkId, methodName, args, true);
}
/**
* 发送不可靠RPC到服务器
*/
protected async sendServerRpcUnreliable(methodName: string, ...args: NetworkValue[]): Promise<void> {
if (!this.networkClient || !this.networkIdentity) {
throw new Error('Network client or identity not available');
}
await this.networkClient.sendRpc(this.networkIdentity.networkId, methodName, args, false);
}
/**
* 更新SyncVar
*/
protected async updateSyncVar(fieldName: string, value: NetworkValue): Promise<void> {
if (!this.networkClient || !this.networkIdentity) {
throw new Error('Network client or identity not available');
}
await this.networkClient.updateSyncVar(this.networkIdentity.networkId, fieldName, value);
}
/**
* 当收到RPC调用时
*/
onRpcReceived(methodName: string, args: NetworkValue[]): void {
// 尝试调用对应的方法
const method = (this as any)[methodName];
if (typeof method === 'function') {
try {
method.apply(this, args);
} catch (error) {
console.error(`Error calling RPC method ${methodName}:`, error);
}
} else {
console.warn(`RPC method ${methodName} not found on ${this.constructor.name}`);
}
}
/**
* 当SyncVar更新时
*/
onSyncVarChanged(fieldName: string, oldValue: NetworkValue, newValue: NetworkValue): void {
// 子类可以重写此方法来处理SyncVar变化
}
/**
* 当获得权威时
*/
onStartAuthority(): void {
// 子类可以重写此方法
}
/**
* 当失去权威时
*/
onStopAuthority(): void {
// 子类可以重写此方法
}
/**
* 当成为本地玩家时
*/
onStartLocalPlayer(): void {
// 子类可以重写此方法
}
/**
* 当不再是本地玩家时
*/
onStopLocalPlayer(): void {
// 子类可以重写此方法
}
/**
* 网络启动时调用
*/
onNetworkStart(): void {
// 子类可以重写此方法
}
/**
* 网络停止时调用
*/
onNetworkStop(): void {
// 子类可以重写此方法
}
/**
* 组件销毁
*/
onDestroy(): void {
this.networkIdentity = null;
this.networkClient = null;
}
}

View File

@@ -1,638 +0,0 @@
/**
* 网络客户端主类
*
* 管理连接、认证、房间加入等功能
*/
import { Scene, EntityManager, Emitter, ITimer, Core } from '@esengine/ecs-framework';
import {
NetworkIdentity as SharedNetworkIdentity,
NetworkValue,
RpcMessage,
SyncVarMessage
} from '@esengine/ecs-framework-network-shared';
import {
ClientTransport,
WebSocketClientTransport,
HttpClientTransport,
ConnectionState,
ClientMessage,
ClientTransportConfig,
WebSocketClientConfig,
HttpClientConfig
} from '../transport';
/**
* 网络客户端配置
*/
export interface NetworkClientConfig {
/** 传输类型 */
transport: 'websocket' | 'http';
/** 传输配置 */
transportConfig: WebSocketClientConfig | HttpClientConfig;
/** 是否启用预测 */
enablePrediction?: boolean;
/** 预测缓冲区大小 */
predictionBuffer?: number;
/** 是否启用插值 */
enableInterpolation?: boolean;
/** 插值延迟(毫秒) */
interpolationDelay?: number;
/** 网络对象同步间隔(毫秒) */
syncInterval?: number;
/** 是否启用调试 */
debug?: boolean;
}
/**
* 用户信息
*/
export interface UserInfo {
/** 用户ID */
userId: string;
/** 用户名 */
username: string;
/** 用户数据 */
data?: NetworkValue;
}
/**
* 房间信息
*/
export interface RoomInfo {
/** 房间ID */
roomId: string;
/** 房间名称 */
name: string;
/** 当前人数 */
playerCount: number;
/** 最大人数 */
maxPlayers: number;
/** 房间元数据 */
metadata?: NetworkValue;
/** 是否私有房间 */
isPrivate?: boolean;
}
/**
* 认证消息
*/
export interface AuthMessage {
action: string;
username: string;
password?: string;
userData?: NetworkValue;
}
/**
* 房间消息
*/
export interface RoomMessage {
action: string;
roomId?: string;
name?: string;
maxPlayers?: number;
metadata?: NetworkValue;
isPrivate?: boolean;
password?: string;
}
/**
* 网络客户端事件
*/
export interface NetworkClientEvents {
/** 连接建立 */
'connected': () => void;
/** 连接断开 */
'disconnected': (reason: string) => void;
/** 认证成功 */
'authenticated': (userInfo: UserInfo) => void;
/** 加入房间成功 */
'joined-room': (roomInfo: RoomInfo) => void;
/** 离开房间 */
'left-room': (roomId: string) => void;
/** 房间列表更新 */
'room-list-updated': (rooms: RoomInfo[]) => void;
/** 玩家加入房间 */
'player-joined': (userId: string, userInfo: UserInfo) => void;
/** 玩家离开房间 */
'player-left': (userId: string) => void;
/** 网络对象创建 */
'network-object-created': (networkId: string, data: NetworkValue) => void;
/** 网络对象销毁 */
'network-object-destroyed': (networkId: string) => void;
/** SyncVar 更新 */
'syncvar-updated': (networkId: string, fieldName: string, value: NetworkValue) => void;
/** RPC 调用 */
'rpc-received': (networkId: string, methodName: string, args: NetworkValue[]) => void;
/** 错误发生 */
'error': (error: Error) => void;
}
/**
* 网络客户端主类
*/
export class NetworkClient {
private transport: ClientTransport;
private config: NetworkClientConfig;
private currentUser: UserInfo | null = null;
private currentRoom: RoomInfo | null = null;
private availableRooms: Map<string, RoomInfo> = new Map();
private networkObjects: Map<string, SharedNetworkIdentity> = new Map();
private pendingRpcs: Map<string, { resolve: Function; reject: Function; timeout: ITimer<any> }> = new Map();
private scene: Scene | null = null;
private eventEmitter: Emitter<keyof NetworkClientEvents, any>;
constructor(config: NetworkClientConfig) {
this.eventEmitter = new Emitter();
this.config = {
enablePrediction: true,
predictionBuffer: 64,
enableInterpolation: true,
interpolationDelay: 100,
syncInterval: 50,
debug: false,
...config
};
this.transport = this.createTransport();
this.setupTransportEvents();
}
/**
* 创建传输层
*/
private createTransport(): ClientTransport {
switch (this.config.transport) {
case 'websocket':
return new WebSocketClientTransport(this.config.transportConfig as WebSocketClientConfig);
case 'http':
return new HttpClientTransport(this.config.transportConfig as HttpClientConfig);
default:
throw new Error(`Unsupported transport type: ${this.config.transport}`);
}
}
/**
* 设置传输层事件监听
*/
private setupTransportEvents(): void {
this.transport.on('connected', () => {
this.eventEmitter.emit('connected');
});
this.transport.on('disconnected', (reason) => {
this.handleDisconnected(reason);
});
this.transport.on('message', (message) => {
this.handleMessage(message);
});
this.transport.on('error', (error) => {
this.eventEmitter.emit('error', error);
});
}
/**
* 连接到服务器
*/
async connect(): Promise<void> {
return this.transport.connect();
}
/**
* 断开连接
*/
async disconnect(): Promise<void> {
await this.transport.disconnect();
this.cleanup();
}
/**
* 用户认证
*/
async authenticate(username: string, password?: string, userData?: NetworkValue): Promise<UserInfo> {
if (!this.transport.isConnected()) {
throw new Error('Not connected to server');
}
const authMessage: AuthMessage = {
action: 'login',
username,
password,
userData
};
const response = await this.sendRequestWithResponse('system', authMessage as any);
if (response.success && response.userInfo) {
this.currentUser = response.userInfo as UserInfo;
this.eventEmitter.emit('authenticated', this.currentUser);
return this.currentUser;
} else {
throw new Error(response.error || 'Authentication failed');
}
}
/**
* 获取房间列表
*/
async getRoomList(): Promise<RoomInfo[]> {
if (!this.isAuthenticated()) {
throw new Error('Not authenticated');
}
const roomMessage: RoomMessage = {
action: 'list-rooms'
};
const response = await this.sendRequestWithResponse('system', roomMessage as any);
if (response.success && response.rooms) {
this.availableRooms.clear();
response.rooms.forEach((room: RoomInfo) => {
this.availableRooms.set(room.roomId, room);
});
this.eventEmitter.emit('room-list-updated', response.rooms);
return response.rooms;
} else {
throw new Error(response.error || 'Failed to get room list');
}
}
/**
* 创建房间
*/
async createRoom(name: string, maxPlayers: number = 8, metadata?: NetworkValue, isPrivate = false): Promise<RoomInfo> {
if (!this.isAuthenticated()) {
throw new Error('Not authenticated');
}
const roomMessage: RoomMessage = {
action: 'create-room',
name,
maxPlayers,
metadata,
isPrivate
};
const response = await this.sendRequestWithResponse('system', roomMessage as any);
if (response.success && response.room) {
this.currentRoom = response.room as RoomInfo;
this.eventEmitter.emit('joined-room', this.currentRoom);
return this.currentRoom;
} else {
throw new Error(response.error || 'Failed to create room');
}
}
/**
* 加入房间
*/
async joinRoom(roomId: string, password?: string): Promise<RoomInfo> {
if (!this.isAuthenticated()) {
throw new Error('Not authenticated');
}
const roomMessage: RoomMessage = {
action: 'join-room',
roomId,
password
};
const response = await this.sendRequestWithResponse('system', roomMessage as any);
if (response.success && response.room) {
this.currentRoom = response.room as RoomInfo;
this.eventEmitter.emit('joined-room', this.currentRoom);
return this.currentRoom;
} else {
throw new Error(response.error || 'Failed to join room');
}
}
/**
* 离开房间
*/
async leaveRoom(): Promise<void> {
if (!this.currentRoom) {
return;
}
const roomMessage: RoomMessage = {
action: 'leave-room',
roomId: this.currentRoom.roomId
};
try {
await this.sendRequestWithResponse('system', roomMessage as any);
} finally {
const roomId = this.currentRoom.roomId;
this.currentRoom = null;
this.networkObjects.clear();
this.eventEmitter.emit('left-room', roomId);
}
}
/**
* 发送RPC调用
*/
async sendRpc(networkId: string, methodName: string, args: NetworkValue[] = [], reliable = true): Promise<NetworkValue> {
if (!this.isInRoom()) {
throw new Error('Not in a room');
}
const rpcMessage: any = {
networkId,
methodName,
args,
isServer: false,
messageId: this.generateMessageId()
};
if (reliable) {
return this.sendRequestWithResponse('rpc', rpcMessage);
} else {
await this.transport.sendMessage({
type: 'rpc',
data: rpcMessage as NetworkValue,
reliable: false
});
return {};
}
}
/**
* 更新SyncVar
*/
async updateSyncVar(networkId: string, fieldName: string, value: NetworkValue): Promise<void> {
if (!this.isInRoom()) {
throw new Error('Not in a room');
}
const syncMessage: any = {
networkId,
propertyName: fieldName,
value,
isServer: false
};
await this.transport.sendMessage({
type: 'syncvar',
data: syncMessage as NetworkValue,
reliable: true
});
}
/**
* 设置ECS场景
*/
setScene(scene: Scene): void {
this.scene = scene;
}
/**
* 获取当前用户信息
*/
getCurrentUser(): UserInfo | null {
return this.currentUser;
}
/**
* 获取当前房间信息
*/
getCurrentRoom(): RoomInfo | null {
return this.currentRoom;
}
/**
* 获取连接状态
*/
getConnectionState(): ConnectionState {
return this.transport.getState();
}
/**
* 是否已认证
*/
isAuthenticated(): boolean {
return this.currentUser !== null && this.transport.isConnected();
}
/**
* 是否在房间中
*/
isInRoom(): boolean {
return this.isAuthenticated() && this.currentRoom !== null;
}
/**
* 获取网络对象
*/
getNetworkObject(networkId: string): SharedNetworkIdentity | null {
return this.networkObjects.get(networkId) || null;
}
/**
* 获取所有网络对象
*/
getAllNetworkObjects(): SharedNetworkIdentity[] {
return Array.from(this.networkObjects.values());
}
/**
* 处理断开连接
*/
private handleDisconnected(reason: string): void {
this.cleanup();
this.eventEmitter.emit('disconnected', reason);
}
/**
* 处理接收到的消息
*/
private handleMessage(message: ClientMessage): void {
try {
switch (message.type) {
case 'system':
this.handleSystemMessage(message);
break;
case 'rpc':
this.handleRpcMessage(message);
break;
case 'syncvar':
this.handleSyncVarMessage(message);
break;
case 'custom':
this.handleCustomMessage(message);
break;
}
} catch (error) {
console.error('Error handling message:', error);
this.eventEmitter.emit('error', error as Error);
}
}
/**
* 处理系统消息
*/
private handleSystemMessage(message: ClientMessage): void {
const data = message.data as any;
// 处理响应消息
if (message.messageId && this.pendingRpcs.has(message.messageId)) {
const pending = this.pendingRpcs.get(message.messageId)!;
pending.timeout.stop();
this.pendingRpcs.delete(message.messageId);
if (data.success) {
pending.resolve(data);
} else {
pending.reject(new Error(data.error || 'Request failed'));
}
return;
}
// 处理广播消息
switch (data.action) {
case 'player-joined':
this.eventEmitter.emit('player-joined', data.userId, data.userInfo);
break;
case 'player-left':
this.eventEmitter.emit('player-left', data.userId);
break;
case 'network-object-created':
this.handleNetworkObjectCreated(data);
break;
case 'network-object-destroyed':
this.handleNetworkObjectDestroyed(data);
break;
}
}
/**
* 处理RPC消息
*/
private handleRpcMessage(message: ClientMessage): void {
const rpcData = message.data as any;
this.eventEmitter.emit('rpc-received', rpcData.networkId, rpcData.methodName, rpcData.args || []);
}
/**
* 处理SyncVar消息
*/
private handleSyncVarMessage(message: ClientMessage): void {
const syncData = message.data as any;
this.eventEmitter.emit('syncvar-updated', syncData.networkId, syncData.propertyName, syncData.value);
}
/**
* 处理自定义消息
*/
private handleCustomMessage(message: ClientMessage): void {
// 可扩展的自定义消息处理
}
/**
* 处理网络对象创建
*/
private handleNetworkObjectCreated(data: any): void {
const networkObject = new SharedNetworkIdentity();
this.networkObjects.set(data.networkId, networkObject);
this.eventEmitter.emit('network-object-created', data.networkId, data.data || {});
}
/**
* 处理网络对象销毁
*/
private handleNetworkObjectDestroyed(data: any): void {
this.networkObjects.delete(data.networkId);
this.eventEmitter.emit('network-object-destroyed', data.networkId);
}
/**
* 发送请求并等待响应
*/
private sendRequestWithResponse(type: ClientMessage['type'], data: NetworkValue, timeout = 30000): Promise<any> {
return new Promise((resolve, reject) => {
const messageId = this.generateMessageId();
const timeoutTimer = Core.schedule(timeout / 1000, false, this, () => {
this.pendingRpcs.delete(messageId);
reject(new Error('Request timeout'));
});
this.pendingRpcs.set(messageId, {
resolve,
reject,
timeout: timeoutTimer
});
this.transport.sendMessage({
type,
data,
messageId,
reliable: true
}).catch(reject);
});
}
/**
* 生成消息ID
*/
private generateMessageId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
/**
* 清理资源
*/
private cleanup(): void {
this.currentUser = null;
this.currentRoom = null;
this.availableRooms.clear();
this.networkObjects.clear();
// 取消所有待处理的RPC
this.pendingRpcs.forEach(pending => {
pending.timeout.stop();
pending.reject(new Error('Connection closed'));
});
this.pendingRpcs.clear();
}
/**
* 销毁客户端
*/
destroy(): void {
this.disconnect();
this.transport.destroy();
// 清理事件监听器由于Emitter没有clear方法我们重新创建一个
this.eventEmitter = new Emitter();
}
/**
* 类型安全的事件监听
*/
on<K extends keyof NetworkClientEvents>(event: K, listener: NetworkClientEvents[K]): void {
this.eventEmitter.addObserver(event, listener, this);
}
/**
* 移除事件监听
*/
off<K extends keyof NetworkClientEvents>(event: K, listener: NetworkClientEvents[K]): void {
this.eventEmitter.removeObserver(event, listener);
}
/**
* 类型安全的事件触发
*/
emit<K extends keyof NetworkClientEvents>(event: K, ...args: Parameters<NetworkClientEvents[K]>): void {
this.eventEmitter.emit(event, ...args);
}
}

View File

@@ -1,378 +0,0 @@
/**
* 客户端网络标识组件
*
* 标识网络对象并管理其状态
*/
import { Component, Entity } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientNetworkBehaviour } from './ClientNetworkBehaviour';
/**
* 网络权威类型
*/
export enum NetworkAuthority {
/** 服务器权威 */
SERVER = 'server',
/** 客户端权威 */
CLIENT = 'client',
/** 所有者权威 */
OWNER = 'owner'
}
/**
* SyncVar信息
*/
export interface SyncVarInfo {
/** 字段名 */
fieldName: string;
/** 当前值 */
currentValue: NetworkValue;
/** 上一个值 */
previousValue: NetworkValue;
/** 最后更新时间 */
lastUpdateTime: number;
/** 是否已变更 */
isDirty: boolean;
}
/**
* 网络标识组件
*/
export class NetworkIdentity extends Component {
/** 网络ID */
private _networkId: string = '';
/** 所有者用户ID */
private _ownerId: string = '';
/** 是否为本地玩家 */
private _isLocalPlayer: boolean = false;
/** 权威类型 */
private _authority: NetworkAuthority = NetworkAuthority.SERVER;
/** 是否有权威 */
private _hasAuthority: boolean = false;
/** 网络行为组件列表 */
private networkBehaviours: ClientNetworkBehaviour[] = [];
/** SyncVar信息映射 */
private syncVars: Map<string, SyncVarInfo> = new Map();
/** 预测状态 */
private predictionEnabled: boolean = false;
/** 插值状态 */
private interpolationEnabled: boolean = true;
/**
* 网络ID
*/
get networkId(): string {
return this._networkId;
}
set networkId(value: string) {
this._networkId = value;
}
/**
* 所有者用户ID
*/
get ownerId(): string {
return this._ownerId;
}
set ownerId(value: string) {
this._ownerId = value;
}
/**
* 是否为本地玩家
*/
get isLocalPlayer(): boolean {
return this._isLocalPlayer;
}
set isLocalPlayer(value: boolean) {
if (this._isLocalPlayer !== value) {
this._isLocalPlayer = value;
this.notifyLocalPlayerChanged();
}
}
/**
* 权威类型
*/
get authority(): NetworkAuthority {
return this._authority;
}
set authority(value: NetworkAuthority) {
if (this._authority !== value) {
this._authority = value;
this.updateAuthorityStatus();
}
}
/**
* 是否有权威
*/
get hasAuthority(): boolean {
return this._hasAuthority;
}
/**
* 是否启用预测
*/
get isPredictionEnabled(): boolean {
return this.predictionEnabled;
}
set isPredictionEnabled(value: boolean) {
this.predictionEnabled = value;
}
/**
* 是否启用插值
*/
get isInterpolationEnabled(): boolean {
return this.interpolationEnabled;
}
set isInterpolationEnabled(value: boolean) {
this.interpolationEnabled = value;
}
/**
* 组件初始化
*/
initialize(): void {
this.collectNetworkBehaviours();
this.notifyNetworkStart();
}
/**
* 收集网络行为组件
*/
private collectNetworkBehaviours(): void {
// 暂时留空,等待实际集成时实现
this.networkBehaviours = [];
}
/**
* 更新权威状态
*/
private updateAuthorityStatus(): void {
const oldHasAuthority = this._hasAuthority;
// 根据权威类型计算是否有权威
switch (this._authority) {
case NetworkAuthority.SERVER:
this._hasAuthority = false; // 客户端永远没有服务器权威
break;
case NetworkAuthority.CLIENT:
this._hasAuthority = true; // 客户端权威
break;
case NetworkAuthority.OWNER:
this._hasAuthority = this._isLocalPlayer; // 本地玩家才有权威
break;
}
// 通知权威变化
if (oldHasAuthority !== this._hasAuthority) {
this.notifyAuthorityChanged();
}
}
/**
* 通知权威变化
*/
private notifyAuthorityChanged(): void {
this.networkBehaviours.forEach(behaviour => {
if (this._hasAuthority) {
behaviour.onStartAuthority();
} else {
behaviour.onStopAuthority();
}
});
}
/**
* 通知本地玩家状态变化
*/
private notifyLocalPlayerChanged(): void {
this.updateAuthorityStatus(); // 本地玩家状态影响权威
this.networkBehaviours.forEach(behaviour => {
if (this._isLocalPlayer) {
behaviour.onStartLocalPlayer();
} else {
behaviour.onStopLocalPlayer();
}
});
}
/**
* 通知网络启动
*/
private notifyNetworkStart(): void {
this.networkBehaviours.forEach(behaviour => {
behaviour.onNetworkStart();
});
}
/**
* 通知网络停止
*/
private notifyNetworkStop(): void {
this.networkBehaviours.forEach(behaviour => {
behaviour.onNetworkStop();
});
}
/**
* 处理RPC调用
*/
handleRpcCall(methodName: string, args: NetworkValue[]): void {
// 将RPC调用分发给所有网络行为组件
this.networkBehaviours.forEach(behaviour => {
behaviour.onRpcReceived(methodName, args);
});
}
/**
* 注册SyncVar
*/
registerSyncVar(fieldName: string, initialValue: NetworkValue): void {
this.syncVars.set(fieldName, {
fieldName,
currentValue: initialValue,
previousValue: initialValue,
lastUpdateTime: Date.now(),
isDirty: false
});
}
/**
* 更新SyncVar
*/
updateSyncVar(fieldName: string, newValue: NetworkValue): void {
const syncVar = this.syncVars.get(fieldName);
if (!syncVar) {
console.warn(`SyncVar ${fieldName} not registered on ${this._networkId}`);
return;
}
const oldValue = syncVar.currentValue;
syncVar.previousValue = oldValue;
syncVar.currentValue = newValue;
syncVar.lastUpdateTime = Date.now();
syncVar.isDirty = true;
// 通知所有网络行为组件
this.networkBehaviours.forEach(behaviour => {
behaviour.onSyncVarChanged(fieldName, oldValue, newValue);
});
}
/**
* 获取SyncVar值
*/
getSyncVar(fieldName: string): NetworkValue | undefined {
return this.syncVars.get(fieldName)?.currentValue;
}
/**
* 获取所有SyncVar
*/
getAllSyncVars(): Map<string, SyncVarInfo> {
return new Map(this.syncVars);
}
/**
* 获取脏SyncVar
*/
getDirtySyncVars(): SyncVarInfo[] {
return Array.from(this.syncVars.values()).filter(syncVar => syncVar.isDirty);
}
/**
* 清除脏标记
*/
clearDirtyFlags(): void {
this.syncVars.forEach(syncVar => {
syncVar.isDirty = false;
});
}
/**
* 序列化网络状态
*/
serializeState(): NetworkValue {
const state: any = {
networkId: this._networkId,
ownerId: this._ownerId,
isLocalPlayer: this._isLocalPlayer,
authority: this._authority,
syncVars: {}
};
// 序列化SyncVar
this.syncVars.forEach((syncVar, fieldName) => {
state.syncVars[fieldName] = syncVar.currentValue;
});
return state;
}
/**
* 反序列化网络状态
*/
deserializeState(state: any): void {
if (state.networkId) this._networkId = state.networkId;
if (state.ownerId) this._ownerId = state.ownerId;
if (typeof state.isLocalPlayer === 'boolean') this.isLocalPlayer = state.isLocalPlayer;
if (state.authority) this.authority = state.authority;
// 反序列化SyncVar
if (state.syncVars) {
Object.entries(state.syncVars).forEach(([fieldName, value]) => {
if (this.syncVars.has(fieldName)) {
this.updateSyncVar(fieldName, value as NetworkValue);
}
});
}
}
/**
* 设置预测状态
*/
setPredictionState(enabled: boolean): void {
this.predictionEnabled = enabled;
}
/**
* 设置插值状态
*/
setInterpolationState(enabled: boolean): void {
this.interpolationEnabled = enabled;
}
/**
* 检查是否可以发送RPC
*/
canSendRpc(): boolean {
return this._hasAuthority || this._isLocalPlayer;
}
/**
* 检查是否可以更新SyncVar
*/
canUpdateSyncVar(): boolean {
return this._hasAuthority;
}
/**
* 组件销毁
*/
onDestroy(): void {
this.notifyNetworkStop();
this.networkBehaviours = [];
this.syncVars.clear();
}
}

View File

@@ -1,7 +0,0 @@
/**
* 核心模块导出
*/
export * from './NetworkClient';
export * from './ClientNetworkBehaviour';
export * from './NetworkIdentity';

View File

@@ -1,108 +0,0 @@
/**
* ClientRpc装饰器 - 客户端版本
*
* 用于标记可以从服务器调用的客户端方法
*/
import 'reflect-metadata';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* ClientRpc配置选项
*/
export interface ClientRpcOptions {
/** 是否可靠传输 */
reliable?: boolean;
/** 超时时间(毫秒) */
timeout?: number;
/** 是否仅发送给所有者 */
ownerOnly?: boolean;
/** 是否包含发送者 */
includeSender?: boolean;
/** 权限要求 */
requireAuthority?: boolean;
}
/**
* ClientRpc元数据键
*/
export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc');
/**
* ClientRpc元数据
*/
export interface ClientRpcMetadata {
/** 方法名 */
methodName: string;
/** 配置选项 */
options: ClientRpcOptions;
/** 原始方法 */
originalMethod: Function;
}
/**
* ClientRpc装饰器
*/
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
const methodName = propertyKey as string;
const originalMethod = descriptor.value;
// 获取已有的ClientRpc元数据
const existingMetadata: ClientRpcMetadata[] = Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target.constructor) || [];
// 添加新的ClientRpc元数据
existingMetadata.push({
methodName,
options: {
reliable: true,
timeout: 30000,
ownerOnly: false,
includeSender: false,
requireAuthority: false,
...options
},
originalMethod
});
// 设置元数据
Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, existingMetadata, target.constructor);
// 包装原方法客户端接收RPC调用时执行
descriptor.value = function (this: any, ...args: NetworkValue[]) {
try {
// 直接调用原方法客户端接收RPC调用
return originalMethod.apply(this, args);
} catch (error) {
console.error(`Error executing ClientRpc ${methodName}:`, error);
throw error;
}
};
return descriptor;
};
}
/**
* 获取类的所有ClientRpc元数据
*/
export function getClientRpcMetadata(target: any): ClientRpcMetadata[] {
return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || [];
}
/**
* 检查方法是否为ClientRpc
*/
export function isClientRpc(target: any, methodName: string): boolean {
const metadata = getClientRpcMetadata(target);
return metadata.some(meta => meta.methodName === methodName);
}
/**
* 获取特定方法的ClientRpc选项
*/
export function getClientRpcOptions(target: any, methodName: string): ClientRpcOptions | null {
const metadata = getClientRpcMetadata(target);
const rpc = metadata.find(meta => meta.methodName === methodName);
return rpc ? rpc.options : null;
}

View File

@@ -1,138 +0,0 @@
/**
* ServerRpc装饰器 - 客户端版本
*
* 用于标记可以向服务器发送的RPC方法
*/
import 'reflect-metadata';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientNetworkBehaviour } from '../core/ClientNetworkBehaviour';
/**
* ServerRpc配置选项
*/
export interface ServerRpcOptions {
/** 是否可靠传输 */
reliable?: boolean;
/** 超时时间(毫秒) */
timeout?: number;
/** 是否需要权威 */
requireAuthority?: boolean;
/** 是否需要是本地玩家 */
requireLocalPlayer?: boolean;
}
/**
* ServerRpc元数据键
*/
export const SERVER_RPC_METADATA_KEY = Symbol('server_rpc');
/**
* ServerRpc元数据
*/
export interface ServerRpcMetadata {
/** 方法名 */
methodName: string;
/** 配置选项 */
options: ServerRpcOptions;
/** 原始方法 */
originalMethod: Function;
}
/**
* ServerRpc装饰器
*/
export function ServerRpc(options: ServerRpcOptions = {}): MethodDecorator {
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
const methodName = propertyKey as string;
const originalMethod = descriptor.value;
// 获取已有的ServerRpc元数据
const existingMetadata: ServerRpcMetadata[] = Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target.constructor) || [];
// 添加新的ServerRpc元数据
existingMetadata.push({
methodName,
options: {
reliable: true,
timeout: 30000,
requireAuthority: false,
requireLocalPlayer: false,
...options
},
originalMethod
});
// 设置元数据
Reflect.defineMetadata(SERVER_RPC_METADATA_KEY, existingMetadata, target.constructor);
// 替换方法实现为发送RPC调用
descriptor.value = async function (this: ClientNetworkBehaviour, ...args: NetworkValue[]) {
try {
// 获取NetworkIdentity
const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any);
if (!networkIdentity) {
throw new Error('NetworkIdentity component not found');
}
// 检查权限要求
if (options.requireAuthority && !(networkIdentity as any).hasAuthority) {
throw new Error(`ServerRpc ${methodName} requires authority`);
}
if (options.requireLocalPlayer && !(networkIdentity as any).isLocalPlayer) {
throw new Error(`ServerRpc ${methodName} requires local player`);
}
// 发送RPC到服务器
if (options.reliable) {
const result = await this.sendServerRpc(methodName, ...args);
return result;
} else {
await this.sendServerRpcUnreliable(methodName, ...args);
return null;
}
} catch (error) {
console.error(`Error sending ServerRpc ${methodName}:`, error);
throw error;
}
};
// 保存原方法到特殊属性,用于本地预测或调试
(descriptor.value as any).__originalMethod = originalMethod;
return descriptor;
};
}
/**
* 获取类的所有ServerRpc元数据
*/
export function getServerRpcMetadata(target: any): ServerRpcMetadata[] {
return Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target) || [];
}
/**
* 检查方法是否为ServerRpc
*/
export function isServerRpc(target: any, methodName: string): boolean {
const metadata = getServerRpcMetadata(target);
return metadata.some(meta => meta.methodName === methodName);
}
/**
* 获取特定方法的ServerRpc选项
*/
export function getServerRpcOptions(target: any, methodName: string): ServerRpcOptions | null {
const metadata = getServerRpcMetadata(target);
const rpc = metadata.find(meta => meta.methodName === methodName);
return rpc ? rpc.options : null;
}
/**
* 获取方法的原始实现(未被装饰器修改的版本)
*/
export function getOriginalMethod(method: Function): Function | null {
return (method as any).__originalMethod || null;
}

View File

@@ -1,146 +0,0 @@
/**
* SyncVar装饰器 - 客户端版本
*
* 用于标记需要同步的变量
*/
import 'reflect-metadata';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientNetworkBehaviour } from '../core/ClientNetworkBehaviour';
/**
* SyncVar配置选项
*/
export interface SyncVarOptions {
/** 是否可从客户端修改 */
clientCanModify?: boolean;
/** 同步间隔(毫秒)0表示立即同步 */
syncInterval?: number;
/** 是否仅同步给所有者 */
ownerOnly?: boolean;
/** 自定义序列化器 */
serializer?: (value: any) => NetworkValue;
/** 自定义反序列化器 */
deserializer?: (value: NetworkValue) => any;
}
/**
* SyncVar元数据键
*/
export const SYNCVAR_METADATA_KEY = Symbol('syncvar');
/**
* SyncVar元数据
*/
export interface SyncVarMetadata {
/** 属性名 */
propertyKey: string;
/** 配置选项 */
options: SyncVarOptions;
}
/**
* SyncVar装饰器
*/
export function SyncVar(options: SyncVarOptions = {}): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
const key = propertyKey as string;
// 获取已有的SyncVar元数据
const existingMetadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, target.constructor) || [];
// 添加新的SyncVar元数据
existingMetadata.push({
propertyKey: key,
options: {
clientCanModify: false,
syncInterval: 0,
ownerOnly: false,
...options
}
});
// 设置元数据
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, existingMetadata, target.constructor);
// 存储原始属性名(用于内部存储)
const privateKey = `_syncvar_${key}`;
// 创建属性访问器
Object.defineProperty(target, key, {
get: function (this: ClientNetworkBehaviour) {
// 从NetworkIdentity获取SyncVar值
const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any);
if (networkIdentity) {
const syncVarValue = (networkIdentity as any).getSyncVar(key);
if (syncVarValue !== undefined) {
return options.deserializer ? options.deserializer(syncVarValue) : syncVarValue;
}
}
// 如果网络值不存在,返回本地存储的值
return (this as any)[privateKey];
},
set: function (this: ClientNetworkBehaviour, value: any) {
const oldValue = (this as any)[privateKey];
const newValue = options.serializer ? options.serializer(value) : value;
// 存储到本地
(this as any)[privateKey] = value;
// 获取NetworkIdentity
const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any);
if (!networkIdentity) {
return;
}
// 检查是否可以修改
if (!options.clientCanModify && !(networkIdentity as any).hasAuthority) {
console.warn(`Cannot modify SyncVar ${key} without authority`);
return;
}
// 注册SyncVar如果尚未注册
(networkIdentity as any).registerSyncVar(key, newValue);
// 更新NetworkIdentity中的值
(networkIdentity as any).updateSyncVar(key, newValue);
// 如果有权威且值发生变化,发送到服务器
if ((networkIdentity as any).hasAuthority && oldValue !== value) {
this.updateSyncVar(key, newValue).catch(error => {
console.error(`Failed to sync variable ${key}:`, error);
});
}
},
enumerable: true,
configurable: true
});
};
}
/**
* 获取类的所有SyncVar元数据
*/
export function getSyncVarMetadata(target: any): SyncVarMetadata[] {
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
}
/**
* 检查属性是否为SyncVar
*/
export function isSyncVar(target: any, propertyKey: string): boolean {
const metadata = getSyncVarMetadata(target);
return metadata.some(meta => meta.propertyKey === propertyKey);
}
/**
* 获取特定属性的SyncVar选项
*/
export function getSyncVarOptions(target: any, propertyKey: string): SyncVarOptions | null {
const metadata = getSyncVarMetadata(target);
const syncVar = metadata.find(meta => meta.propertyKey === propertyKey);
return syncVar ? syncVar.options : null;
}

View File

@@ -1,7 +0,0 @@
/**
* 装饰器导出
*/
export * from './SyncVar';
export * from './ClientRpc';
export * from './ServerRpc';

View File

@@ -1,23 +1,24 @@
/**
* ECS Framework 网络库 - 客户端
*
* 提供网络客户端功能,包括连接管理、预测、插值等
* @esengine/network-client
* ECS Framework网络层 - 客户端实现
*/
// 核心模块
export * from './core';
// 核心客户端 (待实现)
// export * from './core/NetworkClient';
// export * from './core/ServerConnection';
// 传输层
export * from './transport';
// 传输层 (待实现)
// export * from './transport/WebSocketClient';
// export * from './transport/HttpClient';
// 装饰器
export * from './decorators';
// 系统层 (待实现)
// export * from './systems/ClientSyncSystem';
// export * from './systems/ClientRpcSystem';
// export * from './systems/InterpolationSystem';
// 系统
export * from './systems';
// 平台适配器 (待实现)
// export * from './adapters/BrowserAdapter';
// export * from './adapters/CocosAdapter';
// 接口
export * from './interfaces';
// 版本信息
export const VERSION = '1.0.11';
// 重新导出shared包的类型
export * from '@esengine/network-shared';

View File

@@ -1,34 +0,0 @@
/**
* 网络系统相关接口
*/
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 可预测组件接口
*
* 实现此接口的组件可以参与客户端预测系统
*/
export interface IPredictable {
/**
* 预测更新
*
* @param inputs 输入数据
* @param timestamp 时间戳
*/
predictUpdate(inputs: NetworkValue, timestamp: number): void;
}
/**
* 可插值组件接口
*
* 实现此接口的组件可以参与插值系统
*/
export interface IInterpolatable {
/**
* 应用插值状态
*
* @param state 插值后的状态数据
*/
applyInterpolatedState(state: NetworkValue): void;
}

View File

@@ -1,5 +0,0 @@
/**
* 接口导出
*/
export * from './NetworkInterfaces';

View File

@@ -1,520 +0,0 @@
/**
* 客户端插值系统
*
* 实现网络对象的平滑插值
*/
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { NetworkIdentity } from '../core/NetworkIdentity';
import { IInterpolatable } from '../interfaces/NetworkInterfaces';
/**
* 插值状态快照
*/
export interface InterpolationSnapshot {
/** 时间戳 */
timestamp: number;
/** 网络ID */
networkId: string;
/** 状态数据 */
state: NetworkValue;
}
/**
* 插值目标
*/
export interface InterpolationTarget {
/** 网络ID */
networkId: string;
/** 起始状态 */
fromState: NetworkValue;
/** 目标状态 */
toState: NetworkValue;
/** 起始时间 */
fromTime: number;
/** 结束时间 */
toTime: number;
/** 当前插值进度 (0-1) */
progress: number;
}
/**
* 插值配置
*/
export interface InterpolationConfig {
/** 插值延迟(毫秒) */
delay: number;
/** 最大插值时间(毫秒) */
maxTime: number;
/** 插值缓冲区大小 */
bufferSize: number;
/** 外推是否启用 */
enableExtrapolation: boolean;
/** 最大外推时间(毫秒) */
maxExtrapolationTime: number;
}
/**
* 插值算法类型
*/
export enum InterpolationType {
/** 线性插值 */
LINEAR = 'linear',
/** 平滑插值 */
SMOOTHSTEP = 'smoothstep',
/** 三次贝塞尔插值 */
CUBIC = 'cubic'
}
/**
* 客户端插值系统
*/
export class InterpolationSystem extends EntitySystem {
/** 插值状态缓冲区 */
private stateBuffer: Map<string, InterpolationSnapshot[]> = new Map();
/** 当前插值目标 */
private interpolationTargets: Map<string, InterpolationTarget> = new Map();
/** 插值配置 */
private config: InterpolationConfig;
/** 当前时间 */
private currentTime: number = 0;
constructor(config?: Partial<InterpolationConfig>) {
// 使用Matcher查询具有NetworkIdentity的实体
super(Matcher.all(NetworkIdentity));
this.config = {
delay: 100,
maxTime: 500,
bufferSize: 32,
enableExtrapolation: false,
maxExtrapolationTime: 50,
...config
};
this.currentTime = Date.now();
}
/**
* 系统初始化
*/
override initialize(): void {
super.initialize();
this.currentTime = Date.now();
}
/**
* 系统更新
*/
override update(): void {
this.currentTime = Date.now();
this.cleanupOldStates();
// 调用父类update会自动调用process方法处理匹配的实体
super.update();
}
/**
* 处理匹配的实体
*/
protected override process(entities: Entity[]): void {
const interpolationTime = this.currentTime - this.config.delay;
for (const entity of entities) {
const networkIdentity = entity.getComponent(NetworkIdentity);
if (networkIdentity && networkIdentity.isInterpolationEnabled) {
const networkId = networkIdentity.networkId;
const target = this.interpolationTargets.get(networkId);
if (target) {
// 计算插值进度
const duration = target.toTime - target.fromTime;
if (duration > 0) {
const elapsed = interpolationTime - target.fromTime;
target.progress = Math.max(0, Math.min(1, elapsed / duration));
// 执行插值
const interpolatedState = this.interpolateStates(
target.fromState,
target.toState,
target.progress,
InterpolationType.LINEAR
);
// 应用插值状态
this.applyInterpolatedState(entity, interpolatedState);
// 检查是否需要外推
if (target.progress >= 1 && this.config.enableExtrapolation) {
this.performExtrapolation(entity, target, interpolationTime);
}
}
}
}
}
}
/**
* 添加网络状态快照
*/
addStateSnapshot(networkId: string, state: NetworkValue, timestamp: number): void {
// 获取或创建缓冲区
if (!this.stateBuffer.has(networkId)) {
this.stateBuffer.set(networkId, []);
}
const buffer = this.stateBuffer.get(networkId)!;
const snapshot: InterpolationSnapshot = {
timestamp,
networkId,
state
};
// 插入到正确的位置(按时间戳排序)
const insertIndex = this.findInsertIndex(buffer, timestamp);
buffer.splice(insertIndex, 0, snapshot);
// 保持缓冲区大小
if (buffer.length > this.config.bufferSize) {
buffer.shift();
}
// 更新插值目标
this.updateInterpolationTarget(networkId);
}
/**
* 更新插值目标
*/
private updateInterpolationTarget(networkId: string): void {
const buffer = this.stateBuffer.get(networkId);
if (!buffer || buffer.length < 2) {
return;
}
const interpolationTime = this.currentTime - this.config.delay;
// 查找插值区间
const { from, to } = this.findInterpolationRange(buffer, interpolationTime);
if (!from || !to) {
return;
}
// 更新或创建插值目标
this.interpolationTargets.set(networkId, {
networkId,
fromState: from.state,
toState: to.state,
fromTime: from.timestamp,
toTime: to.timestamp,
progress: 0
});
}
/**
* 查找插值区间
*/
private findInterpolationRange(buffer: InterpolationSnapshot[], time: number): {
from: InterpolationSnapshot | null;
to: InterpolationSnapshot | null;
} {
let from: InterpolationSnapshot | null = null;
let to: InterpolationSnapshot | null = null;
for (let i = 0; i < buffer.length - 1; i++) {
const current = buffer[i];
const next = buffer[i + 1];
if (time >= current.timestamp && time <= next.timestamp) {
from = current;
to = next;
break;
}
}
// 如果没有找到区间,使用最近的两个状态
if (!from && !to && buffer.length >= 2) {
if (time < buffer[0].timestamp) {
// 时间过早,使用前两个状态
from = buffer[0];
to = buffer[1];
} else if (time > buffer[buffer.length - 1].timestamp) {
// 时间过晚,使用后两个状态
from = buffer[buffer.length - 2];
to = buffer[buffer.length - 1];
}
}
return { from, to };
}
/**
* 状态插值
*/
private interpolateStates(
fromState: NetworkValue,
toState: NetworkValue,
progress: number,
type: InterpolationType
): NetworkValue {
// 调整插值进度曲线
const adjustedProgress = this.adjustProgress(progress, type);
try {
return this.interpolateValue(fromState, toState, adjustedProgress);
} catch (error) {
console.error('Error interpolating states:', error);
return toState; // 出错时返回目标状态
}
}
/**
* 递归插值值
*/
private interpolateValue(from: NetworkValue, to: NetworkValue, progress: number): NetworkValue {
// 如果类型不同,直接返回目标值
if (typeof from !== typeof to) {
return to;
}
// 数字插值
if (typeof from === 'number' && typeof to === 'number') {
return from + (to - from) * progress;
}
// 字符串插值(直接切换)
if (typeof from === 'string' && typeof to === 'string') {
return progress < 0.5 ? from : to;
}
// 布尔插值(直接切换)
if (typeof from === 'boolean' && typeof to === 'boolean') {
return progress < 0.5 ? from : to;
}
// 数组插值
if (Array.isArray(from) && Array.isArray(to)) {
const result: NetworkValue[] = [];
const maxLength = Math.max(from.length, to.length);
for (let i = 0; i < maxLength; i++) {
const fromValue = i < from.length ? from[i] : to[i];
const toValue = i < to.length ? to[i] : from[i];
result[i] = this.interpolateValue(fromValue, toValue, progress);
}
return result;
}
// 对象插值
if (from && to && typeof from === 'object' && typeof to === 'object') {
const result: any = {};
const allKeys = new Set([...Object.keys(from), ...Object.keys(to)]);
for (const key of allKeys) {
const fromValue = (from as any)[key];
const toValue = (to as any)[key];
if (fromValue !== undefined && toValue !== undefined) {
result[key] = this.interpolateValue(fromValue, toValue, progress);
} else {
result[key] = toValue !== undefined ? toValue : fromValue;
}
}
return result;
}
// 其他类型直接返回目标值
return to;
}
/**
* 调整插值进度曲线
*/
private adjustProgress(progress: number, type: InterpolationType): number {
switch (type) {
case InterpolationType.LINEAR:
return progress;
case InterpolationType.SMOOTHSTEP:
return progress * progress * (3 - 2 * progress);
case InterpolationType.CUBIC:
return progress < 0.5
? 4 * progress * progress * progress
: 1 - Math.pow(-2 * progress + 2, 3) / 2;
default:
return progress;
}
}
/**
* 应用插值状态到实体
*/
private applyInterpolatedState(entity: Entity, state: NetworkValue): void {
// 获取所有可插值的组件
const components: any[] = [];
for (const component of components) {
if (this.isInterpolatable(component)) {
try {
(component as IInterpolatable).applyInterpolatedState(state);
} catch (error) {
console.error('Error applying interpolated state:', error);
}
}
}
// 更新NetworkIdentity中的状态
const networkIdentity = entity.getComponent(NetworkIdentity);
if (networkIdentity && typeof networkIdentity.deserializeState === 'function') {
try {
networkIdentity.deserializeState(state);
} catch (error) {
console.error('Error deserializing interpolated state:', error);
}
}
}
/**
* 检查组件是否实现了IInterpolatable接口
*/
private isInterpolatable(component: any): component is IInterpolatable {
return component && typeof component.applyInterpolatedState === 'function';
}
/**
* 执行外推
*/
private performExtrapolation(entity: Entity, target: InterpolationTarget, currentTime: number): void {
if (!this.config.enableExtrapolation) {
return;
}
const extrapolationTime = currentTime - target.toTime;
if (extrapolationTime > this.config.maxExtrapolationTime) {
return;
}
// 计算外推状态
const extrapolationProgress = extrapolationTime / (target.toTime - target.fromTime);
const extrapolatedState = this.extrapolateState(
target.fromState,
target.toState,
1 + extrapolationProgress
);
// 应用外推状态
this.applyInterpolatedState(entity, extrapolatedState);
}
/**
* 状态外推
*/
private extrapolateState(fromState: NetworkValue, toState: NetworkValue, progress: number): NetworkValue {
// 简单的线性外推
return this.interpolateValue(fromState, toState, progress);
}
/**
* 查找插入位置
*/
private findInsertIndex(buffer: InterpolationSnapshot[], timestamp: number): number {
let left = 0;
let right = buffer.length;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (buffer[mid].timestamp < timestamp) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
/**
* 清理过期状态
*/
private cleanupOldStates(): void {
const cutoffTime = this.currentTime - this.config.maxTime;
this.stateBuffer.forEach((buffer, networkId) => {
// 移除过期的状态
const validStates = buffer.filter(snapshot => snapshot.timestamp > cutoffTime);
if (validStates.length !== buffer.length) {
this.stateBuffer.set(networkId, validStates);
}
// 如果缓冲区为空,移除它
if (validStates.length === 0) {
this.stateBuffer.delete(networkId);
this.interpolationTargets.delete(networkId);
}
});
}
/**
* 根据网络ID查找实体
*/
private findEntityByNetworkId(networkId: string): Entity | null {
// 使用系统的entities属性来查找
for (const entity of this.entities) {
const networkIdentity = entity.getComponent(NetworkIdentity);
if (networkIdentity && networkIdentity.networkId === networkId) {
return entity;
}
}
return null;
}
/**
* 设置插值配置
*/
setInterpolationConfig(config: Partial<InterpolationConfig>): void {
this.config = { ...this.config, ...config };
}
/**
* 获取插值统计信息
*/
getInterpolationStats(): { [networkId: string]: { bufferSize: number; progress: number } } {
const stats: { [networkId: string]: { bufferSize: number; progress: number } } = {};
this.stateBuffer.forEach((buffer, networkId) => {
const target = this.interpolationTargets.get(networkId);
stats[networkId] = {
bufferSize: buffer.length,
progress: target ? target.progress : 0
};
});
return stats;
}
/**
* 清空所有插值数据
*/
clearInterpolationData(): void {
this.stateBuffer.clear();
this.interpolationTargets.clear();
}
/**
* 系统销毁
*/
onDestroy(): void {
this.clearInterpolationData();
}
}

View File

@@ -1,362 +0,0 @@
/**
* 客户端预测系统
*
* 实现客户端预测和服务器和解
*/
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { NetworkIdentity } from '../core/NetworkIdentity';
import { IPredictable } from '../interfaces/NetworkInterfaces';
/**
* 预测状态快照
*/
export interface PredictionSnapshot {
/** 时间戳 */
timestamp: number;
/** 网络ID */
networkId: string;
/** 状态数据 */
state: NetworkValue;
/** 输入数据 */
inputs?: NetworkValue;
}
/**
* 预测输入
*/
export interface PredictionInput {
/** 时间戳 */
timestamp: number;
/** 输入数据 */
data: NetworkValue;
}
/**
* 客户端预测系统
*/
export class PredictionSystem extends EntitySystem {
/** 预测状态缓冲区 */
private predictionBuffer: Map<string, PredictionSnapshot[]> = new Map();
/** 输入缓冲区 */
private inputBuffer: PredictionInput[] = [];
/** 最大缓冲区大小 */
private maxBufferSize: number = 64;
/** 预测时间窗口(毫秒) */
private predictionWindow: number = 500;
/** 当前预测时间戳 */
private currentPredictionTime: number = 0;
constructor(maxBufferSize = 64, predictionWindow = 500) {
// 使用Matcher查询具有NetworkIdentity的实体
super(Matcher.all(NetworkIdentity));
this.maxBufferSize = maxBufferSize;
this.predictionWindow = predictionWindow;
this.currentPredictionTime = Date.now();
}
/**
* 系统初始化
*/
override initialize(): void {
super.initialize();
this.currentPredictionTime = Date.now();
}
/**
* 系统更新
*/
override update(): void {
this.currentPredictionTime = Date.now();
this.cleanupOldSnapshots();
// 调用父类update会自动调用process方法处理匹配的实体
super.update();
}
/**
* 处理匹配的实体
*/
protected override process(entities: Entity[]): void {
for (const entity of entities) {
const networkIdentity = entity.getComponent(NetworkIdentity);
if (networkIdentity &&
networkIdentity.isPredictionEnabled &&
networkIdentity.isLocalPlayer) {
// 保存当前状态快照
this.saveSnapshot(entity);
// 应用当前输入进行预测
const currentInputs = this.getCurrentInputs();
if (currentInputs) {
this.applyInputs(entity, currentInputs, this.currentPredictionTime);
}
}
}
}
/**
* 添加预测输入
*/
addInput(input: PredictionInput): void {
this.inputBuffer.push(input);
// 保持输入缓冲区大小
if (this.inputBuffer.length > this.maxBufferSize) {
this.inputBuffer.shift();
}
// 按时间戳排序
this.inputBuffer.sort((a, b) => a.timestamp - b.timestamp);
}
/**
* 保存预测状态快照
*/
saveSnapshot(entity: Entity): void {
const networkIdentity = entity.getComponent(NetworkIdentity);
if (!networkIdentity || !networkIdentity.isPredictionEnabled) {
return;
}
const networkId = networkIdentity.networkId;
const snapshot: PredictionSnapshot = {
timestamp: this.currentPredictionTime,
networkId,
state: networkIdentity.serializeState(),
inputs: this.getCurrentInputs() || undefined
};
// 获取或创建缓冲区
if (!this.predictionBuffer.has(networkId)) {
this.predictionBuffer.set(networkId, []);
}
const buffer = this.predictionBuffer.get(networkId)!;
buffer.push(snapshot);
// 保持缓冲区大小
if (buffer.length > this.maxBufferSize) {
buffer.shift();
}
}
/**
* 从服务器接收权威状态进行和解
*/
reconcileWithServer(networkId: string, serverState: NetworkValue, serverTimestamp: number): void {
const buffer = this.predictionBuffer.get(networkId);
if (!buffer || buffer.length === 0) {
return;
}
// 查找对应时间戳的预测状态
const predictionSnapshot = this.findSnapshot(buffer, serverTimestamp);
if (!predictionSnapshot) {
return;
}
// 比较预测状态和服务器状态
if (this.statesMatch(predictionSnapshot.state, serverState)) {
// 预测正确,移除已确认的快照
this.removeSnapshotsBeforeTimestamp(buffer, serverTimestamp);
return;
}
// 预测错误,需要进行和解
this.performReconciliation(networkId, serverState, serverTimestamp);
}
/**
* 执行预测和解
*/
private performReconciliation(networkId: string, serverState: NetworkValue, serverTimestamp: number): void {
const entity = this.findEntityByNetworkId(networkId);
if (!entity) {
return;
}
const networkIdentity = entity.getComponent(NetworkIdentity);
if (!networkIdentity) {
return;
}
// 回滚到服务器状态
if (typeof networkIdentity.deserializeState === 'function') {
networkIdentity.deserializeState(serverState);
}
// 重新应用服务器时间戳之后的输入
const buffer = this.predictionBuffer.get(networkId)!;
const snapshotsToReplay = buffer.filter(snapshot => snapshot.timestamp > serverTimestamp);
for (const snapshot of snapshotsToReplay) {
if (snapshot.inputs) {
this.applyInputs(entity, snapshot.inputs, snapshot.timestamp);
}
}
// 清理已和解的快照
this.removeSnapshotsBeforeTimestamp(buffer, serverTimestamp);
}
/**
* 应用输入进行预测计算
*/
private applyInputs(entity: Entity, inputs: NetworkValue, timestamp: number): void {
const networkIdentity = entity.getComponent(NetworkIdentity);
if (!networkIdentity) return;
// 获取实体的所有组件并检查是否实现了IPredictable接口
const components: any[] = [];
for (const component of components) {
if (this.isPredictable(component)) {
try {
(component as IPredictable).predictUpdate(inputs, timestamp);
} catch (error) {
console.error('Error applying prediction:', error);
}
}
}
}
/**
* 检查组件是否实现了IPredictable接口
*/
private isPredictable(component: any): component is IPredictable {
return component && typeof component.predictUpdate === 'function';
}
/**
* 获取当前输入
*/
private getCurrentInputs(): NetworkValue | null {
if (this.inputBuffer.length === 0) {
return null;
}
// 获取最新的输入
return this.inputBuffer[this.inputBuffer.length - 1].data;
}
/**
* 查找指定时间戳的快照
*/
private findSnapshot(buffer: PredictionSnapshot[], timestamp: number): PredictionSnapshot | null {
// 查找最接近的快照
let closest: PredictionSnapshot | null = null;
let minDiff = Number.MAX_SAFE_INTEGER;
for (const snapshot of buffer) {
const diff = Math.abs(snapshot.timestamp - timestamp);
if (diff < minDiff) {
minDiff = diff;
closest = snapshot;
}
}
return closest;
}
/**
* 比较两个状态是否匹配
*/
private statesMatch(predictedState: NetworkValue, serverState: NetworkValue): boolean {
try {
// 简单的JSON比较实际应用中可能需要更精确的比较
return JSON.stringify(predictedState) === JSON.stringify(serverState);
} catch (error) {
return false;
}
}
/**
* 移除指定时间戳之前的快照
*/
private removeSnapshotsBeforeTimestamp(buffer: PredictionSnapshot[], timestamp: number): void {
for (let i = buffer.length - 1; i >= 0; i--) {
if (buffer[i].timestamp < timestamp) {
buffer.splice(0, i + 1);
break;
}
}
}
/**
* 清理过期的快照
*/
private cleanupOldSnapshots(): void {
const cutoffTime = this.currentPredictionTime - this.predictionWindow;
this.predictionBuffer.forEach((buffer, networkId) => {
this.removeSnapshotsBeforeTimestamp(buffer, cutoffTime);
// 如果缓冲区为空,移除它
if (buffer.length === 0) {
this.predictionBuffer.delete(networkId);
}
});
// 清理过期的输入
this.inputBuffer = this.inputBuffer.filter(input =>
input.timestamp > cutoffTime
);
}
/**
* 根据网络ID查找实体
*/
private findEntityByNetworkId(networkId: string): Entity | null {
// 使用系统的entities属性来查找
for (const entity of this.entities) {
const networkIdentity = entity.getComponent(NetworkIdentity);
if (networkIdentity && networkIdentity.networkId === networkId) {
return entity;
}
}
return null;
}
/**
* 设置预测配置
*/
setPredictionConfig(maxBufferSize: number, predictionWindow: number): void {
this.maxBufferSize = maxBufferSize;
this.predictionWindow = predictionWindow;
}
/**
* 获取预测统计信息
*/
getPredictionStats(): { [networkId: string]: number } {
const stats: { [networkId: string]: number } = {};
this.predictionBuffer.forEach((buffer, networkId) => {
stats[networkId] = buffer.length;
});
return stats;
}
/**
* 清空所有预测数据
*/
clearPredictionData(): void {
this.predictionBuffer.clear();
this.inputBuffer = [];
}
/**
* 系统销毁
*/
onDestroy(): void {
this.clearPredictionData();
}
}

View File

@@ -1,6 +0,0 @@
/**
* 系统导出
*/
export * from './PredictionSystem';
export * from './InterpolationSystem';

View File

@@ -1,445 +0,0 @@
/**
* 客户端传输层抽象接口
*/
import { Emitter, ITimer, Core } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 客户端传输配置
*/
export interface ClientTransportConfig {
/** 服务器地址 */
host: string;
/** 服务器端口 */
port: number;
/** 是否使用安全连接 */
secure?: boolean;
/** 连接超时时间(毫秒) */
connectionTimeout?: number;
/** 重连间隔(毫秒) */
reconnectInterval?: number;
/** 最大重连次数 */
maxReconnectAttempts?: number;
/** 心跳间隔(毫秒) */
heartbeatInterval?: number;
/** 消息队列最大大小 */
maxQueueSize?: number;
}
/**
* 连接状态
*/
export enum ConnectionState {
/** 断开连接 */
DISCONNECTED = 'disconnected',
/** 连接中 */
CONNECTING = 'connecting',
/** 已连接 */
CONNECTED = 'connected',
/** 认证中 */
AUTHENTICATING = 'authenticating',
/** 已认证 */
AUTHENTICATED = 'authenticated',
/** 重连中 */
RECONNECTING = 'reconnecting',
/** 连接错误 */
ERROR = 'error'
}
/**
* 客户端消息
*/
export interface ClientMessage {
/** 消息类型 */
type: 'rpc' | 'syncvar' | 'system' | 'custom';
/** 消息数据 */
data: NetworkValue;
/** 消息ID用于响应匹配 */
messageId?: string;
/** 是否可靠传输 */
reliable?: boolean;
/** 时间戳 */
timestamp?: number;
}
/**
* 连接统计信息
*/
export interface ConnectionStats {
/** 连接时间 */
connectedAt: Date | null;
/** 连接持续时间(毫秒) */
connectionDuration: number;
/** 发送消息数 */
messagesSent: number;
/** 接收消息数 */
messagesReceived: number;
/** 发送字节数 */
bytesSent: number;
/** 接收字节数 */
bytesReceived: number;
/** 重连次数 */
reconnectCount: number;
/** 丢失消息数 */
messagesLost: number;
/** 平均延迟(毫秒) */
averageLatency: number;
}
/**
* 客户端传输事件
*/
export interface ClientTransportEvents {
/** 连接建立 */
'connected': () => void;
/** 连接断开 */
'disconnected': (reason: string) => void;
/** 连接状态变化 */
'state-changed': (oldState: ConnectionState, newState: ConnectionState) => void;
/** 收到消息 */
'message': (message: ClientMessage) => void;
/** 连接错误 */
'error': (error: Error) => void;
/** 重连开始 */
'reconnecting': (attempt: number, maxAttempts: number) => void;
/** 重连成功 */
'reconnected': () => void;
/** 重连失败 */
'reconnect-failed': () => void;
/** 延迟更新 */
'latency-updated': (latency: number) => void;
}
/**
* 客户端传输层抽象类
*/
export abstract class ClientTransport {
protected config: ClientTransportConfig;
protected state: ConnectionState = ConnectionState.DISCONNECTED;
protected stats: ConnectionStats;
protected messageQueue: ClientMessage[] = [];
protected reconnectAttempts = 0;
protected reconnectTimer: ITimer<any> | null = null;
protected heartbeatTimer: ITimer<any> | null = null;
private latencyMeasurements: number[] = [];
private eventEmitter: Emitter<keyof ClientTransportEvents, any>;
constructor(config: ClientTransportConfig) {
this.eventEmitter = new Emitter<keyof ClientTransportEvents, any>();
this.config = {
secure: false,
connectionTimeout: 10000, // 10秒
reconnectInterval: 3000, // 3秒
maxReconnectAttempts: 10,
heartbeatInterval: 30000, // 30秒
maxQueueSize: 1000,
...config
};
this.stats = {
connectedAt: null,
connectionDuration: 0,
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0,
reconnectCount: 0,
messagesLost: 0,
averageLatency: 0
};
}
/**
* 连接到服务器
*/
abstract connect(): Promise<void>;
/**
* 断开连接
*/
abstract disconnect(): Promise<void>;
/**
* 发送消息
*/
abstract sendMessage(message: ClientMessage): Promise<boolean>;
/**
* 获取当前连接状态
*/
getState(): ConnectionState {
return this.state;
}
/**
* 检查是否已连接
*/
isConnected(): boolean {
return this.state === ConnectionState.CONNECTED ||
this.state === ConnectionState.AUTHENTICATED;
}
/**
* 获取连接统计信息
*/
getStats(): ConnectionStats {
if (this.stats.connectedAt) {
this.stats.connectionDuration = Date.now() - this.stats.connectedAt.getTime();
}
return { ...this.stats };
}
/**
* 获取配置
*/
getConfig(): Readonly<ClientTransportConfig> {
return this.config;
}
/**
* 设置状态
*/
protected setState(newState: ConnectionState): void {
if (this.state !== newState) {
const oldState = this.state;
this.state = newState;
this.eventEmitter.emit('state-changed', oldState, newState);
// 特殊状态处理
if (newState === ConnectionState.CONNECTED) {
this.stats.connectedAt = new Date();
this.reconnectAttempts = 0;
this.startHeartbeat();
this.processMessageQueue();
this.eventEmitter.emit('connected');
if (oldState === ConnectionState.RECONNECTING) {
this.eventEmitter.emit('reconnected');
}
} else if (newState === ConnectionState.DISCONNECTED) {
this.stats.connectedAt = null;
this.stopHeartbeat();
}
}
}
/**
* 处理接收到的消息
*/
protected handleMessage(message: ClientMessage): void {
this.stats.messagesReceived++;
if (message.data) {
try {
const messageSize = JSON.stringify(message.data).length;
this.stats.bytesReceived += messageSize;
} catch (error) {
// 忽略序列化错误
}
}
// 处理系统消息
if (message.type === 'system') {
this.handleSystemMessage(message);
return;
}
this.eventEmitter.emit('message', message);
}
/**
* 处理系统消息
*/
protected handleSystemMessage(message: ClientMessage): void {
const data = message.data as any;
switch (data.action) {
case 'ping':
// 响应ping
this.sendMessage({
type: 'system',
data: { action: 'pong', timestamp: data.timestamp }
});
break;
case 'pong':
// 计算延迟
if (data.timestamp) {
const latency = Date.now() - data.timestamp;
this.updateLatency(latency);
}
break;
}
}
/**
* 处理连接错误
*/
protected handleError(error: Error): void {
console.error('Transport error:', error.message);
this.eventEmitter.emit('error', error);
if (this.isConnected()) {
this.setState(ConnectionState.ERROR);
this.startReconnect();
}
}
/**
* 开始重连
*/
protected startReconnect(): void {
if (this.reconnectAttempts >= this.config.maxReconnectAttempts!) {
this.eventEmitter.emit('reconnect-failed');
return;
}
this.setState(ConnectionState.RECONNECTING);
this.reconnectAttempts++;
this.stats.reconnectCount++;
this.eventEmitter.emit('reconnecting', this.reconnectAttempts, this.config.maxReconnectAttempts!);
this.reconnectTimer = Core.schedule(this.config.reconnectInterval! / 1000, false, this, async () => {
try {
await this.connect();
} catch (error) {
this.startReconnect(); // 继续重连
}
});
}
/**
* 停止重连
*/
protected stopReconnect(): void {
if (this.reconnectTimer) {
this.reconnectTimer.stop();
this.reconnectTimer = null;
}
}
/**
* 将消息加入队列
*/
protected queueMessage(message: ClientMessage): boolean {
if (this.messageQueue.length >= this.config.maxQueueSize!) {
this.stats.messagesLost++;
return false;
}
this.messageQueue.push(message);
return true;
}
/**
* 处理消息队列
*/
protected async processMessageQueue(): Promise<void> {
while (this.messageQueue.length > 0 && this.isConnected()) {
const message = this.messageQueue.shift()!;
await this.sendMessage(message);
}
}
/**
* 开始心跳
*/
protected startHeartbeat(): void {
if (this.config.heartbeatInterval && this.config.heartbeatInterval > 0) {
this.heartbeatTimer = Core.schedule(this.config.heartbeatInterval / 1000, true, this, () => {
this.sendHeartbeat();
});
}
}
/**
* 停止心跳
*/
protected stopHeartbeat(): void {
if (this.heartbeatTimer) {
this.heartbeatTimer.stop();
this.heartbeatTimer = null;
}
}
/**
* 发送心跳
*/
protected sendHeartbeat(): void {
this.sendMessage({
type: 'system',
data: { action: 'ping', timestamp: Date.now() }
}).catch(() => {
// 心跳发送失败,可能连接有问题
});
}
/**
* 更新延迟统计
*/
protected updateLatency(latency: number): void {
this.latencyMeasurements.push(latency);
// 只保留最近的10个测量值
if (this.latencyMeasurements.length > 10) {
this.latencyMeasurements.shift();
}
// 计算平均延迟
const sum = this.latencyMeasurements.reduce((a, b) => a + b, 0);
this.stats.averageLatency = sum / this.latencyMeasurements.length;
this.eventEmitter.emit('latency-updated', latency);
}
/**
* 更新发送统计
*/
protected updateSendStats(message: ClientMessage): void {
this.stats.messagesSent++;
if (message.data) {
try {
const messageSize = JSON.stringify(message.data).length;
this.stats.bytesSent += messageSize;
} catch (error) {
// 忽略序列化错误
}
}
}
/**
* 销毁传输层
*/
destroy(): void {
this.stopReconnect();
this.stopHeartbeat();
this.messageQueue = [];
}
/**
* 类型安全的事件监听
*/
on<K extends keyof ClientTransportEvents>(event: K, listener: ClientTransportEvents[K]): this {
this.eventEmitter.addObserver(event, listener, this);
return this;
}
/**
* 移除事件监听
*/
off<K extends keyof ClientTransportEvents>(event: K, listener: ClientTransportEvents[K]): this {
this.eventEmitter.removeObserver(event, listener);
return this;
}
/**
* 类型安全的事件触发
*/
emit<K extends keyof ClientTransportEvents>(event: K, ...args: Parameters<ClientTransportEvents[K]>): void {
this.eventEmitter.emit(event, ...args);
}
}

View File

@@ -1,427 +0,0 @@
/**
* HTTP 客户端传输实现
*
* 支持 REST API 和长轮询
*/
import { Core, ITimer } from '@esengine/ecs-framework';
import {
ClientTransport,
ClientTransportConfig,
ConnectionState,
ClientMessage
} from './ClientTransport';
/**
* HTTP 客户端配置
*/
export interface HttpClientConfig extends ClientTransportConfig {
/** API 路径前缀 */
apiPrefix?: string;
/** 请求超时时间(毫秒) */
requestTimeout?: number;
/** 长轮询超时时间(毫秒) */
longPollTimeout?: number;
/** 是否启用长轮询 */
enableLongPolling?: boolean;
/** 额外的请求头 */
headers?: Record<string, string>;
/** 认证令牌 */
authToken?: string;
}
/**
* HTTP 响应接口
*/
interface HttpResponse {
success: boolean;
data?: any;
error?: string;
messages?: ClientMessage[];
}
/**
* HTTP 客户端传输
*/
export class HttpClientTransport extends ClientTransport {
private connectionId: string | null = null;
private longPollController: AbortController | null = null;
private longPollRunning = false;
private connectPromise: Promise<void> | null = null;
private requestTimers: Set<ITimer<any>> = new Set();
protected override config: HttpClientConfig;
constructor(config: HttpClientConfig) {
super(config);
this.config = {
apiPrefix: '/api',
requestTimeout: 30000, // 30秒
longPollTimeout: 25000, // 25秒
enableLongPolling: true,
headers: {
'Content-Type': 'application/json'
},
...config
};
}
/**
* 连接到服务器
*/
async connect(): Promise<void> {
if (this.state === ConnectionState.CONNECTING ||
this.state === ConnectionState.CONNECTED) {
return this.connectPromise || Promise.resolve();
}
this.setState(ConnectionState.CONNECTING);
this.stopReconnect();
this.connectPromise = this.performConnect();
return this.connectPromise;
}
/**
* 执行连接
*/
private async performConnect(): Promise<void> {
try {
// 发送连接请求
const response = await this.makeRequest('/connect', 'POST', {});
if (response.success && response.data.connectionId) {
this.connectionId = response.data.connectionId;
this.setState(ConnectionState.CONNECTED);
// 启动长轮询
if (this.config.enableLongPolling) {
this.startLongPolling();
}
} else {
throw new Error(response.error || 'Connection failed');
}
} catch (error) {
this.setState(ConnectionState.ERROR);
throw error;
}
}
/**
* 断开连接
*/
async disconnect(): Promise<void> {
this.stopReconnect();
this.stopLongPolling();
if (this.connectionId) {
try {
await this.makeRequest('/disconnect', 'POST', {
connectionId: this.connectionId
});
} catch (error) {
// 忽略断开连接时的错误
}
this.connectionId = null;
}
this.setState(ConnectionState.DISCONNECTED);
this.connectPromise = null;
}
/**
* 发送消息
*/
async sendMessage(message: ClientMessage): Promise<boolean> {
if (!this.connectionId) {
// 如果未连接,将消息加入队列
if (this.state === ConnectionState.CONNECTING ||
this.state === ConnectionState.RECONNECTING) {
return this.queueMessage(message);
}
return false;
}
try {
const response = await this.makeRequest('/send', 'POST', {
connectionId: this.connectionId,
message: {
...message,
timestamp: message.timestamp || Date.now()
}
});
if (response.success) {
this.updateSendStats(message);
return true;
} else {
console.error('Send message failed:', response.error);
return false;
}
} catch (error) {
this.handleError(error as Error);
return false;
}
}
/**
* 启动长轮询
*/
private startLongPolling(): void {
if (this.longPollRunning || !this.connectionId) {
return;
}
this.longPollRunning = true;
this.performLongPoll();
}
/**
* 停止长轮询
*/
private stopLongPolling(): void {
this.longPollRunning = false;
if (this.longPollController) {
this.longPollController.abort();
this.longPollController = null;
}
}
/**
* 执行长轮询
*/
private async performLongPoll(): Promise<void> {
while (this.longPollRunning && this.connectionId) {
try {
this.longPollController = new AbortController();
const response = await this.makeRequest('/poll', 'GET', {
connectionId: this.connectionId
}, {
signal: this.longPollController.signal,
timeout: this.config.longPollTimeout
});
if (response.success && response.messages && response.messages.length > 0) {
// 处理接收到的消息
for (const message of response.messages) {
this.handleMessage(message);
}
}
// 如果服务器指示断开连接
if (response.data && response.data.disconnected) {
this.handleServerDisconnect();
break;
}
} catch (error) {
if ((error as any).name === 'AbortError') {
// 被主动取消,正常情况
break;
}
console.warn('Long polling error:', (error as Error).message);
// 如果是网络错误,尝试重连
if (this.isNetworkError(error as Error)) {
this.handleError(error as Error);
break;
}
// 短暂等待后重试
await this.delay(1000);
}
this.longPollController = null;
}
}
/**
* 处理服务器主动断开连接
*/
private handleServerDisconnect(): void {
this.connectionId = null;
this.stopLongPolling();
this.emit('disconnected', 'Server disconnect');
if (this.reconnectAttempts < this.config.maxReconnectAttempts!) {
this.startReconnect();
} else {
this.setState(ConnectionState.DISCONNECTED);
}
}
/**
* 发送 HTTP 请求
*/
private async makeRequest(
path: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
data?: any,
options: {
signal?: AbortSignal;
timeout?: number;
} = {}
): Promise<HttpResponse> {
const url = this.buildUrl(path);
const headers = this.buildHeaders();
const requestOptions: RequestInit = {
method,
headers,
signal: options.signal
};
// 添加请求体
if (method !== 'GET' && data) {
requestOptions.body = JSON.stringify(data);
} else if (method === 'GET' && data) {
// GET 请求将数据作为查询参数
const params = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => {
params.append(key, String(value));
});
const separator = url.includes('?') ? '&' : '?';
return this.fetchWithTimeout(`${url}${separator}${params}`, requestOptions, options.timeout);
}
return this.fetchWithTimeout(url, requestOptions, options.timeout);
}
/**
* 带超时的 fetch 请求
*/
private async fetchWithTimeout(
url: string,
options: RequestInit,
timeout?: number
): Promise<HttpResponse> {
const actualTimeout = timeout || this.config.requestTimeout!;
const controller = new AbortController();
let timeoutTimer: ITimer<any> | null = null;
// 创建超时定时器
timeoutTimer = Core.schedule(actualTimeout / 1000, false, this, () => {
controller.abort();
if (timeoutTimer) {
this.requestTimers.delete(timeoutTimer);
}
});
this.requestTimers.add(timeoutTimer);
try {
const response = await fetch(url, {
...options,
signal: options.signal || controller.signal
});
// 清理定时器
if (timeoutTimer) {
timeoutTimer.stop();
this.requestTimers.delete(timeoutTimer);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
return result as HttpResponse;
} catch (error) {
// 清理定时器
if (timeoutTimer) {
timeoutTimer.stop();
this.requestTimers.delete(timeoutTimer);
}
throw error;
}
}
/**
* 构建请求URL
*/
private buildUrl(path: string): string {
const protocol = this.config.secure ? 'https' : 'http';
const basePath = this.config.apiPrefix || '';
const cleanPath = path.startsWith('/') ? path : `/${path}`;
return `${protocol}://${this.config.host}:${this.config.port}${basePath}${cleanPath}`;
}
/**
* 构建请求头
*/
private buildHeaders(): Record<string, string> {
const headers = { ...this.config.headers };
if (this.config.authToken) {
headers['Authorization'] = `Bearer ${this.config.authToken}`;
}
return headers;
}
/**
* 检查是否为网络错误
*/
private isNetworkError(error: Error): boolean {
return error.message.includes('fetch') ||
error.message.includes('network') ||
error.message.includes('timeout') ||
error.name === 'TypeError';
}
/**
* 延迟函数
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => {
const timer = Core.schedule(ms / 1000, false, this, () => {
this.requestTimers.delete(timer);
resolve();
});
this.requestTimers.add(timer);
});
}
/**
* 设置认证令牌
*/
setAuthToken(token: string): void {
this.config.authToken = token;
}
/**
* 获取连接ID
*/
getConnectionId(): string | null {
return this.connectionId;
}
/**
* 检查是否支持 Fetch API
*/
static isSupported(): boolean {
return typeof fetch !== 'undefined';
}
/**
* 销毁传输层
*/
override destroy(): void {
// 清理所有请求定时器
this.requestTimers.forEach(timer => timer.stop());
this.requestTimers.clear();
this.disconnect();
super.destroy();
}
}

View File

@@ -1,282 +0,0 @@
/**
* WebSocket 客户端传输实现
*/
import { Core, ITimer } from '@esengine/ecs-framework';
import {
ClientTransport,
ClientTransportConfig,
ConnectionState,
ClientMessage
} from './ClientTransport';
/**
* WebSocket 客户端配置
*/
export interface WebSocketClientConfig extends ClientTransportConfig {
/** WebSocket 路径 */
path?: string;
/** 协议列表 */
protocols?: string | string[];
/** 额外的请求头 */
headers?: Record<string, string>;
/** 是否启用二进制消息 */
binaryType?: 'blob' | 'arraybuffer';
/** WebSocket 扩展 */
extensions?: any;
}
/**
* WebSocket 客户端传输
*/
export class WebSocketClientTransport extends ClientTransport {
private websocket: WebSocket | null = null;
private connectionPromise: Promise<void> | null = null;
private connectionTimeoutTimer: ITimer<any> | null = null;
protected override config: WebSocketClientConfig;
constructor(config: WebSocketClientConfig) {
super(config);
this.config = {
path: '/ws',
protocols: [],
headers: {},
binaryType: 'arraybuffer',
...config
};
}
/**
* 连接到服务器
*/
async connect(): Promise<void> {
if (this.state === ConnectionState.CONNECTING ||
this.state === ConnectionState.CONNECTED) {
return this.connectionPromise || Promise.resolve();
}
this.setState(ConnectionState.CONNECTING);
this.stopReconnect(); // 停止任何正在进行的重连
this.connectionPromise = new Promise((resolve, reject) => {
try {
// 构建WebSocket URL
const protocol = this.config.secure ? 'wss' : 'ws';
const url = `${protocol}://${this.config.host}:${this.config.port}${this.config.path}`;
// 创建WebSocket连接
this.websocket = new WebSocket(url, this.config.protocols);
if (this.config.binaryType) {
this.websocket.binaryType = this.config.binaryType;
}
// 设置连接超时
this.connectionTimeoutTimer = Core.schedule(this.config.connectionTimeout! / 1000, false, this, () => {
if (this.websocket && this.websocket.readyState === WebSocket.CONNECTING) {
this.websocket.close();
reject(new Error('Connection timeout'));
}
});
// WebSocket 事件处理
this.websocket.onopen = () => {
if (this.connectionTimeoutTimer) {
this.connectionTimeoutTimer.stop();
this.connectionTimeoutTimer = null;
}
this.setState(ConnectionState.CONNECTED);
resolve();
};
this.websocket.onclose = (event) => {
if (this.connectionTimeoutTimer) {
this.connectionTimeoutTimer.stop();
this.connectionTimeoutTimer = null;
}
this.handleClose(event.code, event.reason);
if (this.state === ConnectionState.CONNECTING) {
reject(new Error(`Connection failed: ${event.reason || 'Unknown error'}`));
}
};
this.websocket.onerror = (event) => {
if (this.connectionTimeoutTimer) {
this.connectionTimeoutTimer.stop();
this.connectionTimeoutTimer = null;
}
const error = new Error('WebSocket error');
this.handleError(error);
if (this.state === ConnectionState.CONNECTING) {
reject(error);
}
};
this.websocket.onmessage = (event) => {
this.handleWebSocketMessage(event);
};
} catch (error) {
this.setState(ConnectionState.ERROR);
reject(error);
}
});
return this.connectionPromise;
}
/**
* 断开连接
*/
async disconnect(): Promise<void> {
this.stopReconnect();
if (this.websocket) {
// 设置状态为断开连接,避免触发重连
this.setState(ConnectionState.DISCONNECTED);
if (this.websocket.readyState === WebSocket.OPEN ||
this.websocket.readyState === WebSocket.CONNECTING) {
this.websocket.close(1000, 'Client disconnect');
}
this.websocket = null;
}
this.connectionPromise = null;
}
/**
* 发送消息
*/
async sendMessage(message: ClientMessage): Promise<boolean> {
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
// 如果未连接,将消息加入队列
if (this.state === ConnectionState.CONNECTING ||
this.state === ConnectionState.RECONNECTING) {
return this.queueMessage(message);
}
return false;
}
try {
// 序列化消息
const serialized = JSON.stringify({
...message,
timestamp: message.timestamp || Date.now()
});
// 发送消息
this.websocket.send(serialized);
this.updateSendStats(message);
return true;
} catch (error) {
this.handleError(error as Error);
return false;
}
}
/**
* 处理 WebSocket 消息
*/
private handleWebSocketMessage(event: MessageEvent): void {
try {
let data: string;
if (event.data instanceof ArrayBuffer) {
// 处理二进制数据
data = new TextDecoder().decode(event.data);
} else if (event.data instanceof Blob) {
// Blob 需要异步处理
event.data.text().then(text => {
this.processMessage(text);
});
return;
} else {
// 字符串数据
data = event.data;
}
this.processMessage(data);
} catch (error) {
console.error('Error processing WebSocket message:', error);
}
}
/**
* 处理消息内容
*/
private processMessage(data: string): void {
try {
const message: ClientMessage = JSON.parse(data);
this.handleMessage(message);
} catch (error) {
console.error('Error parsing message:', error);
}
}
/**
* 处理连接关闭
*/
private handleClose(code: number, reason: string): void {
this.websocket = null;
this.connectionPromise = null;
const wasConnected = this.isConnected();
// 根据关闭代码决定是否重连
if (code === 1000) {
// 正常关闭,不重连
this.setState(ConnectionState.DISCONNECTED);
this.emit('disconnected', reason || 'Normal closure');
} else if (wasConnected && this.reconnectAttempts < this.config.maxReconnectAttempts!) {
// 异常关闭,尝试重连
this.emit('disconnected', reason || `Abnormal closure (${code})`);
this.startReconnect();
} else {
// 达到最大重连次数或其他情况
this.setState(ConnectionState.DISCONNECTED);
this.emit('disconnected', reason || `Connection lost (${code})`);
}
}
/**
* 获取 WebSocket 就绪状态
*/
getReadyState(): number {
return this.websocket?.readyState ?? WebSocket.CLOSED;
}
/**
* 获取 WebSocket 实例
*/
getWebSocket(): WebSocket | null {
return this.websocket;
}
/**
* 检查是否支持 WebSocket
*/
static isSupported(): boolean {
return typeof WebSocket !== 'undefined';
}
/**
* 销毁传输层
*/
override destroy(): void {
if (this.connectionTimeoutTimer) {
this.connectionTimeoutTimer.stop();
this.connectionTimeoutTimer = null;
}
this.disconnect();
super.destroy();
}
}

View File

@@ -1,7 +0,0 @@
/**
* 传输层导出
*/
export * from './ClientTransport';
export * from './WebSocketClientTransport';
export * from './HttpClientTransport';

View File

@@ -1,384 +0,0 @@
/**
* NetworkClient 集成测试
* 测试网络客户端的完整功能,包括依赖注入和错误处理
*/
import { NetworkClient } from '../src/core/NetworkClient';
// Mock 所有外部依赖
jest.mock('@esengine/ecs-framework', () => ({
Core: {
scene: null,
schedule: {
scheduleRepeating: jest.fn((callback: Function, interval: number) => ({
stop: jest.fn()
}))
}
},
Emitter: jest.fn().mockImplementation(() => ({
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
removeAllListeners: jest.fn()
}))
}));
jest.mock('@esengine/ecs-framework-network-shared', () => ({
NetworkValue: {},
generateMessageId: jest.fn(() => 'test-message-id-123'),
generateNetworkId: jest.fn(() => 12345),
NetworkUtils: {
generateMessageId: jest.fn(() => 'test-message-id-456'),
calculateDistance: jest.fn(() => 100),
isNodeEnvironment: jest.fn(() => false),
isBrowserEnvironment: jest.fn(() => true)
}
}));
// Mock WebSocket
class MockWebSocket {
public readyState: number = WebSocket.CONNECTING;
public onopen: ((event: Event) => void) | null = null;
public onclose: ((event: CloseEvent) => void) | null = null;
public onmessage: ((event: MessageEvent) => void) | null = null;
public onerror: ((event: Event) => void) | null = null;
constructor(public url: string, public protocols?: string | string[]) {}
send(data: string | ArrayBuffer | Blob): void {}
close(code?: number, reason?: string): void {
this.readyState = WebSocket.CLOSED;
if (this.onclose) {
this.onclose(new CloseEvent('close', { code: code || 1000, reason: reason || '' }));
}
}
}
(global as any).WebSocket = MockWebSocket;
(global as any).WebSocket.CONNECTING = 0;
(global as any).WebSocket.OPEN = 1;
(global as any).WebSocket.CLOSING = 2;
(global as any).WebSocket.CLOSED = 3;
describe('NetworkClient 集成测试', () => {
let client: NetworkClient;
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
if (client) {
client.disconnect().catch(() => {});
client = null as any;
}
});
describe('依赖注入测试', () => {
it('应该正确处理所有依赖模块', () => {
expect(() => {
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
}).not.toThrow();
expect(client).toBeInstanceOf(NetworkClient);
});
it('应该正确使用network-shared中的工具函数', () => {
const { generateMessageId, NetworkUtils } = require('@esengine/ecs-framework-network-shared');
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
// 验证network-shared模块被正确导入
expect(generateMessageId).toBeDefined();
expect(NetworkUtils).toBeDefined();
});
it('应该正确使用ecs-framework中的Core模块', () => {
const { Core } = require('@esengine/ecs-framework');
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
expect(Core).toBeDefined();
expect(Core.schedule).toBeDefined();
});
});
describe('构造函数错误处理', () => {
it('应该处理network-shared模块导入失败', () => {
// 重置模块并模拟导入失败
jest.resetModules();
jest.doMock('@esengine/ecs-framework-network-shared', () => {
throw new Error('network-shared模块导入失败');
});
expect(() => {
const { NetworkClient } = require('../src/core/NetworkClient');
new NetworkClient({
transportType: 'websocket',
host: 'localhost',
port: 8080
});
}).toThrow();
});
it('应该处理ecs-framework模块导入失败', () => {
// 重置模块并模拟导入失败
jest.resetModules();
jest.doMock('@esengine/ecs-framework', () => {
throw new Error('ecs-framework模块导入失败');
});
expect(() => {
const { NetworkClient } = require('../src/core/NetworkClient');
new NetworkClient({
transportType: 'websocket',
host: 'localhost',
port: 8080
});
}).toThrow();
});
it('应该处理传输层构造失败', () => {
// Mock传输层构造函数抛出异常
const originalWebSocket = (global as any).WebSocket;
(global as any).WebSocket = jest.fn(() => {
throw new Error('WebSocket不可用');
});
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
expect(client.connect()).rejects.toThrow();
// 恢复原始WebSocket
(global as any).WebSocket = originalWebSocket;
});
});
describe('功能测试', () => {
beforeEach(() => {
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
});
it('应该能够成功连接', async () => {
const connectPromise = client.connect();
// 模拟连接成功
setTimeout(() => {
const transport = (client as any).transport;
if (transport && transport.websocket && transport.websocket.onopen) {
transport.websocket.readyState = WebSocket.OPEN;
transport.websocket.onopen(new Event('open'));
}
}, 0);
await expect(connectPromise).resolves.toBeUndefined();
});
it('应该能够发送消息', async () => {
// 先连接
const connectPromise = client.connect();
setTimeout(() => {
const transport = (client as any).transport;
if (transport && transport.websocket && transport.websocket.onopen) {
transport.websocket.readyState = WebSocket.OPEN;
transport.websocket.onopen(new Event('open'));
}
}, 0);
await connectPromise;
// 发送消息
const message = {
type: 'custom' as const,
data: { test: 'message' },
reliable: true
};
// NetworkClient没有直接的sendMessage方法它通过RPC调用
});
it('应该能够正确断开连接', async () => {
await expect(client.disconnect()).resolves.toBeUndefined();
});
it('应该返回正确的认证状态', () => {
expect(client.isAuthenticated()).toBe(false);
});
it('应该能够获取网络对象列表', () => {
const networkObjects = client.getAllNetworkObjects();
expect(Array.isArray(networkObjects)).toBe(true);
expect(networkObjects.length).toBe(0);
});
});
describe('消息ID生成测试', () => {
beforeEach(() => {
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
});
it('应该能够生成唯一的消息ID', () => {
const messageId1 = (client as any).generateMessageId();
const messageId2 = (client as any).generateMessageId();
expect(typeof messageId1).toBe('string');
expect(typeof messageId2).toBe('string');
expect(messageId1).not.toBe(messageId2);
});
it('生成的消息ID应该符合预期格式', () => {
const messageId = (client as any).generateMessageId();
// 检查消息ID格式时间戳 + 随机字符串)
expect(messageId).toMatch(/^[a-z0-9]+$/);
expect(messageId.length).toBeGreaterThan(10);
});
});
describe('错误恢复测试', () => {
beforeEach(() => {
client = new NetworkClient({
transportType: 'websocket',
host: 'localhost',
port: 8080,
maxReconnectAttempts: 2,
reconnectInterval: 100
});
});
it('连接失败后应该尝试重连', async () => {
let connectAttempts = 0;
const originalWebSocket = (global as any).WebSocket;
(global as any).WebSocket = jest.fn().mockImplementation(() => {
connectAttempts++;
const ws = new originalWebSocket('ws://localhost:8080');
// 模拟连接失败
setTimeout(() => {
if (ws.onerror) {
ws.onerror(new Event('error'));
}
}, 0);
return ws;
});
await expect(client.connect()).rejects.toThrow();
// 等待重连尝试
await new Promise(resolve => setTimeout(resolve, 300));
expect(connectAttempts).toBeGreaterThan(1);
// 恢复原始WebSocket
(global as any).WebSocket = originalWebSocket;
});
it('达到最大重连次数后应该停止重连', async () => {
const maxAttempts = 2;
client = new NetworkClient({
transportType: 'websocket',
host: 'localhost',
port: 8080,
maxReconnectAttempts: maxAttempts,
reconnectInterval: 50
});
let connectAttempts = 0;
const originalWebSocket = (global as any).WebSocket;
(global as any).WebSocket = jest.fn().mockImplementation(() => {
connectAttempts++;
const ws = new originalWebSocket('ws://localhost:8080');
setTimeout(() => {
if (ws.onerror) {
ws.onerror(new Event('error'));
}
}, 0);
return ws;
});
await expect(client.connect()).rejects.toThrow();
// 等待所有重连尝试完成
await new Promise(resolve => setTimeout(resolve, 200));
expect(connectAttempts).toBeLessThanOrEqual(maxAttempts + 1);
// 恢复原始WebSocket
(global as any).WebSocket = originalWebSocket;
});
});
describe('内存泄漏防护测试', () => {
it('断开连接时应该清理所有资源', async () => {
client = new NetworkClient({
transport: 'websocket',
transportConfig: {
host: 'localhost',
port: 8080
}
});
const { Emitter } = require('@esengine/ecs-framework');
const emitterInstance = Emitter.mock.results[Emitter.mock.results.length - 1].value;
await client.disconnect();
expect(emitterInstance.removeAllListeners).toHaveBeenCalled();
});
it('多次创建和销毁客户端不应该造成内存泄漏', () => {
const initialEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length;
// 创建和销毁多个客户端实例
for (let i = 0; i < 5; i++) {
const tempClient = new NetworkClient({
transportType: 'websocket',
host: 'localhost',
port: 8080
});
tempClient.disconnect().catch(() => {});
}
const finalEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length;
// 验证Emitter实例数量符合预期
expect(finalEmitterCallCount - initialEmitterCallCount).toBe(5);
});
});
});

View File

@@ -1,27 +1,65 @@
/**
* Jest测试环境设置 - 客户端
*/
// 导入reflect-metadata以支持装饰器
import 'reflect-metadata';
// Mock WebSocket for testing
(global as any).WebSocket = class MockWebSocket {
// 模拟浏览器环境的WebSocket
Object.defineProperty(global, 'WebSocket', {
value: class MockWebSocket {
static CONNECTING = 0;
static OPEN = 1;
static CLOSING = 2;
static CLOSED = 3;
readyState = MockWebSocket.CONNECTING;
url: string;
onopen: ((event: Event) => void) | null = null;
onmessage: ((event: MessageEvent) => void) | null = null;
onclose: ((event: CloseEvent) => void) | null = null;
onmessage: ((event: MessageEvent) => void) | null = null;
onerror: ((event: Event) => void) | null = null;
constructor(public url: string) {}
constructor(url: string) {
this.url = url;
// 模拟异步连接
setTimeout(() => {
this.readyState = MockWebSocket.OPEN;
if (this.onopen) {
this.onopen(new Event('open'));
}
}, 0);
}
send(data: string | ArrayBuffer | Blob) {
// Mock implementation
send(data: string | ArrayBuffer) {
// 模拟发送
}
close() {
// Mock implementation
this.readyState = MockWebSocket.CLOSED;
if (this.onclose) {
this.onclose(new CloseEvent('close'));
}
}
}
};
global.beforeEach(() => {
jest.clearAllMocks();
});
global.afterEach(() => {
jest.restoreAllMocks();
// 全局测试配置
beforeAll(() => {
// 设置测试环境
process.env.NODE_ENV = 'test';
process.env.NETWORK_ENV = 'client';
});
afterAll(() => {
// 清理测试环境
});
beforeEach(() => {
// 每个测试前的准备工作
});
afterEach(() => {
// 每个测试后的清理工作
// 清理可能的网络连接、定时器等
});

View File

@@ -1,374 +0,0 @@
/**
* ClientTransport 基类测试
* 测试客户端传输层基类的构造函数和依赖问题
*/
import { ClientTransport, ClientTransportConfig, ConnectionState } from '../../src/transport/ClientTransport';
// Mock Emitter 和 Core
jest.mock('@esengine/ecs-framework', () => ({
Emitter: jest.fn().mockImplementation(() => ({
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
removeAllListeners: jest.fn()
})),
Core: {
schedule: {
scheduleRepeating: jest.fn((callback: Function, interval: number) => ({
stop: jest.fn()
}))
}
}
}));
// Mock network-shared
jest.mock('@esengine/ecs-framework-network-shared', () => ({
NetworkValue: {}
}));
// 创建测试用的具体实现类
class TestClientTransport extends ClientTransport {
async connect(): Promise<void> {
return Promise.resolve();
}
async disconnect(): Promise<void> {
return Promise.resolve();
}
async sendMessage(message: any): Promise<void> {
return Promise.resolve();
}
}
describe('ClientTransport', () => {
let transport: TestClientTransport;
const defaultConfig: ClientTransportConfig = {
host: 'localhost',
port: 8080
};
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
if (transport) {
transport = null as any;
}
});
describe('构造函数测试', () => {
it('应该能够成功创建ClientTransport实例', () => {
expect(() => {
transport = new TestClientTransport(defaultConfig);
}).not.toThrow();
expect(transport).toBeInstanceOf(ClientTransport);
});
it('应该正确设置默认配置', () => {
transport = new TestClientTransport(defaultConfig);
const config = (transport as any).config;
expect(config.host).toBe('localhost');
expect(config.port).toBe(8080);
expect(config.secure).toBe(false);
expect(config.connectionTimeout).toBe(10000);
expect(config.reconnectInterval).toBe(3000);
expect(config.maxReconnectAttempts).toBe(10);
expect(config.heartbeatInterval).toBe(30000);
expect(config.maxQueueSize).toBe(1000);
});
it('应该允许自定义配置覆盖默认值', () => {
const customConfig: ClientTransportConfig = {
host: 'example.com',
port: 9090,
secure: true,
connectionTimeout: 15000,
reconnectInterval: 5000,
maxReconnectAttempts: 5,
heartbeatInterval: 60000,
maxQueueSize: 500
};
transport = new TestClientTransport(customConfig);
const config = (transport as any).config;
expect(config.host).toBe('example.com');
expect(config.port).toBe(9090);
expect(config.secure).toBe(true);
expect(config.connectionTimeout).toBe(15000);
expect(config.reconnectInterval).toBe(5000);
expect(config.maxReconnectAttempts).toBe(5);
expect(config.heartbeatInterval).toBe(60000);
expect(config.maxQueueSize).toBe(500);
});
it('应该正确初始化内部状态', () => {
transport = new TestClientTransport(defaultConfig);
expect((transport as any).state).toBe(ConnectionState.DISCONNECTED);
expect((transport as any).messageQueue).toEqual([]);
expect((transport as any).reconnectAttempts).toBe(0);
expect((transport as any).reconnectTimer).toBeNull();
expect((transport as any).heartbeatTimer).toBeNull();
expect((transport as any).latencyMeasurements).toEqual([]);
});
it('应该正确初始化统计信息', () => {
transport = new TestClientTransport(defaultConfig);
const stats = transport.getStats();
expect(stats.connectedAt).toBeNull();
expect(stats.connectionDuration).toBe(0);
expect(stats.messagesSent).toBe(0);
expect(stats.messagesReceived).toBe(0);
expect(stats.bytesSent).toBe(0);
expect(stats.bytesReceived).toBe(0);
expect(stats.averageLatency).toBe(0);
expect(stats.averageLatency).toBe(0);
expect(stats.reconnectCount).toBe(0);
});
});
describe('依赖注入测试', () => {
it('应该正确处理@esengine/ecs-framework中的Emitter', () => {
const { Emitter } = require('@esengine/ecs-framework');
expect(() => {
transport = new TestClientTransport(defaultConfig);
}).not.toThrow();
expect(Emitter).toHaveBeenCalled();
});
it('构造函数中Emitter初始化失败应该抛出异常', () => {
// Mock Emitter构造函数抛出异常
const { Emitter } = require('@esengine/ecs-framework');
Emitter.mockImplementation(() => {
throw new Error('Emitter初始化失败');
});
expect(() => {
transport = new TestClientTransport(defaultConfig);
}).toThrow('Emitter初始化失败');
});
it('应该正确处理@esengine/ecs-framework-network-shared依赖', () => {
const networkShared = require('@esengine/ecs-framework-network-shared');
expect(() => {
transport = new TestClientTransport(defaultConfig);
}).not.toThrow();
expect(networkShared).toBeDefined();
expect(networkShared.NetworkValue).toBeDefined();
});
});
describe('事件系统测试', () => {
beforeEach(() => {
transport = new TestClientTransport(defaultConfig);
});
it('应该能够注册事件监听器', () => {
const mockCallback = jest.fn();
const { Emitter } = require('@esengine/ecs-framework');
const emitterInstance = Emitter.mock.results[0].value;
transport.on('connected', mockCallback);
expect(emitterInstance.on).toHaveBeenCalledWith('connected', mockCallback);
});
it('应该能够移除事件监听器', () => {
const mockCallback = jest.fn();
const { Emitter } = require('@esengine/ecs-framework');
const emitterInstance = Emitter.mock.results[0].value;
transport.off('connected', mockCallback);
expect(emitterInstance.off).toHaveBeenCalledWith('connected', mockCallback);
});
it('应该能够发出事件', () => {
const { Emitter } = require('@esengine/ecs-framework');
const emitterInstance = Emitter.mock.results[0].value;
(transport as any).emit('connected');
expect(emitterInstance.emit).toHaveBeenCalledWith('connected');
});
});
describe('消息队列测试', () => {
beforeEach(() => {
transport = new TestClientTransport(defaultConfig);
});
it('应该能够将消息加入队列', async () => {
const message = {
type: 'custom' as const,
data: { test: 'data' },
reliable: true,
timestamp: Date.now()
};
await transport.sendMessage(message);
const messageQueue = (transport as any).messageQueue;
expect(messageQueue).toHaveLength(1);
expect(messageQueue[0]).toEqual(message);
});
it('消息队列达到最大大小时应该移除旧消息', async () => {
// 设置较小的队列大小
const smallQueueConfig = { ...defaultConfig, maxQueueSize: 2 };
transport = new TestClientTransport(smallQueueConfig);
const message1 = { type: 'custom' as const, data: { id: 1 }, reliable: true, timestamp: Date.now() };
const message2 = { type: 'custom' as const, data: { id: 2 }, reliable: true, timestamp: Date.now() };
const message3 = { type: 'custom' as const, data: { id: 3 }, reliable: true, timestamp: Date.now() };
await transport.sendMessage(message1);
await transport.sendMessage(message2);
await transport.sendMessage(message3);
const messageQueue = (transport as any).messageQueue;
expect(messageQueue).toHaveLength(2);
expect(messageQueue[0]).toEqual(message2);
expect(messageQueue[1]).toEqual(message3);
});
});
describe('连接状态测试', () => {
beforeEach(() => {
transport = new TestClientTransport(defaultConfig);
});
it('应该正确获取连接状态', () => {
expect(transport.getState()).toBe(ConnectionState.DISCONNECTED);
});
it('应该正确检查连接状态', () => {
expect(transport.isConnected()).toBe(false);
(transport as any).state = ConnectionState.CONNECTED;
expect(transport.isConnected()).toBe(true);
(transport as any).state = ConnectionState.AUTHENTICATED;
expect(transport.isConnected()).toBe(true);
});
it('状态变化时应该发出事件', () => {
const { Emitter } = require('@esengine/ecs-framework');
const emitterInstance = Emitter.mock.results[0].value;
(transport as any).setState(ConnectionState.CONNECTING);
expect(emitterInstance.emit).toHaveBeenCalledWith(
'state-changed',
ConnectionState.DISCONNECTED,
ConnectionState.CONNECTING
);
});
});
describe('延迟测量测试', () => {
beforeEach(() => {
transport = new TestClientTransport(defaultConfig);
});
it('应该能够更新延迟测量', () => {
(transport as any).updateLatency(100);
(transport as any).updateLatency(200);
(transport as any).updateLatency(150);
const stats = transport.getStats();
expect(stats.averageLatency).toBe(150);
});
it('应该限制延迟测量样本数量', () => {
// 添加超过最大样本数的测量
for (let i = 0; i < 150; i++) {
(transport as any).updateLatency(i * 10);
}
const latencyMeasurements = (transport as any).latencyMeasurements;
expect(latencyMeasurements.length).toBeLessThanOrEqual(100);
});
});
describe('配置验证测试', () => {
it('应该拒绝无效的主机名', () => {
expect(() => {
transport = new TestClientTransport({ host: '', port: 8080 });
}).toThrow();
});
it('应该拒绝无效的端口号', () => {
expect(() => {
transport = new TestClientTransport({ host: 'localhost', port: 0 });
}).toThrow();
expect(() => {
transport = new TestClientTransport({ host: 'localhost', port: 65536 });
}).toThrow();
});
it('应该拒绝负数的超时配置', () => {
expect(() => {
transport = new TestClientTransport({
host: 'localhost',
port: 8080,
connectionTimeout: -1000
});
}).toThrow();
});
});
describe('资源清理测试', () => {
beforeEach(() => {
transport = new TestClientTransport(defaultConfig);
});
it('应该能够清理所有定时器', () => {
const { Core } = require('@esengine/ecs-framework');
const mockTimer = { stop: jest.fn() };
Core.schedule.scheduleRepeating.mockReturnValue(mockTimer);
// 设置一些定时器
(transport as any).reconnectTimer = mockTimer;
(transport as any).heartbeatTimer = mockTimer;
// 调用清理方法
(transport as any).cleanup();
expect(mockTimer.stop).toHaveBeenCalledTimes(2);
expect((transport as any).reconnectTimer).toBeNull();
expect((transport as any).heartbeatTimer).toBeNull();
});
it('应该能够清理消息队列', () => {
(transport as any).messageQueue = [
{ type: 'custom', data: {}, reliable: true, timestamp: Date.now() }
];
(transport as any).cleanup();
expect((transport as any).messageQueue).toHaveLength(0);
});
it('应该能够移除所有事件监听器', () => {
const { Emitter } = require('@esengine/ecs-framework');
const emitterInstance = Emitter.mock.results[0].value;
(transport as any).cleanup();
expect(emitterInstance.removeAllListeners).toHaveBeenCalled();
});
});
});

View File

@@ -1,348 +0,0 @@
/**
* WebSocketClientTransport 测试
* 测试WebSocket客户端传输层的构造函数和依赖问题
*/
import { WebSocketClientTransport, WebSocketClientConfig } from '../../src/transport/WebSocketClientTransport';
import { ConnectionState } from '../../src/transport/ClientTransport';
// Mock WebSocket
class MockWebSocket {
public readyState: number = WebSocket.CONNECTING;
public onopen: ((event: Event) => void) | null = null;
public onclose: ((event: CloseEvent) => void) | null = null;
public onmessage: ((event: MessageEvent) => void) | null = null;
public onerror: ((event: Event) => void) | null = null;
constructor(public url: string, public protocols?: string | string[]) {}
send(data: string | ArrayBuffer | Blob): void {}
close(code?: number, reason?: string): void {
this.readyState = WebSocket.CLOSED;
if (this.onclose) {
this.onclose(new CloseEvent('close', { code: code || 1000, reason: reason || '' }));
}
}
}
// Mock依赖 - 直接创建mock对象而不依赖外部模块
const mockCore = {
schedule: {
scheduleRepeating: jest.fn((callback: Function, interval: number) => ({
stop: jest.fn()
}))
}
};
const mockEmitter = {
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
removeAllListeners: jest.fn()
};
const mockNetworkShared = {
NetworkValue: {},
generateMessageId: jest.fn(() => 'mock-message-id-123')
};
// 设置模块mock
jest.doMock('@esengine/ecs-framework', () => ({
Core: mockCore,
Emitter: jest.fn(() => mockEmitter)
}));
jest.doMock('@esengine/ecs-framework-network-shared', () => mockNetworkShared);
// 设置全局WebSocket mock
(global as any).WebSocket = MockWebSocket;
(global as any).WebSocket.CONNECTING = 0;
(global as any).WebSocket.OPEN = 1;
(global as any).WebSocket.CLOSING = 2;
(global as any).WebSocket.CLOSED = 3;
describe('WebSocketClientTransport', () => {
let transport: WebSocketClientTransport;
const defaultConfig: WebSocketClientConfig = {
host: 'localhost',
port: 8080,
secure: false,
connectionTimeout: 5000,
reconnectInterval: 1000,
maxReconnectAttempts: 3,
heartbeatInterval: 30000
};
beforeEach(() => {
jest.clearAllMocks();
});
afterEach(() => {
if (transport) {
transport.disconnect().catch(() => {});
transport = null as any;
}
});
describe('构造函数测试', () => {
it('应该能够成功创建WebSocketClientTransport实例', () => {
expect(() => {
transport = new WebSocketClientTransport(defaultConfig);
}).not.toThrow();
expect(transport).toBeInstanceOf(WebSocketClientTransport);
});
it('应该正确合并默认配置', () => {
transport = new WebSocketClientTransport(defaultConfig);
const config = (transport as any).config;
expect(config.path).toBe('/ws');
expect(config.protocols).toEqual([]);
expect(config.headers).toEqual({});
expect(config.binaryType).toBe('arraybuffer');
expect(config.host).toBe('localhost');
expect(config.port).toBe(8080);
});
it('应该允许自定义配置覆盖默认值', () => {
const customConfig: WebSocketClientConfig = {
...defaultConfig,
path: '/custom-ws',
protocols: ['custom-protocol'],
headers: { 'X-Custom': 'value' },
binaryType: 'blob'
};
transport = new WebSocketClientTransport(customConfig);
const config = (transport as any).config;
expect(config.path).toBe('/custom-ws');
expect(config.protocols).toEqual(['custom-protocol']);
expect(config.headers).toEqual({ 'X-Custom': 'value' });
expect(config.binaryType).toBe('blob');
});
it('应该正确初始化内部状态', () => {
transport = new WebSocketClientTransport(defaultConfig);
expect((transport as any).websocket).toBeNull();
expect((transport as any).connectionPromise).toBeNull();
expect((transport as any).connectionTimeoutTimer).toBeNull();
expect((transport as any).state).toBe(ConnectionState.DISCONNECTED);
});
});
describe('依赖注入测试', () => {
it('应该正确处理@esengine/ecs-framework依赖', () => {
const { Core } = require('@esengine/ecs-framework');
expect(() => {
transport = new WebSocketClientTransport(defaultConfig);
}).not.toThrow();
expect(Core).toBeDefined();
});
it('应该正确处理@esengine/ecs-framework-network-shared依赖', () => {
const { generateMessageId } = require('@esengine/ecs-framework-network-shared');
expect(() => {
transport = new WebSocketClientTransport(defaultConfig);
}).not.toThrow();
expect(generateMessageId).toBeDefined();
expect(typeof generateMessageId).toBe('function');
});
});
describe('连接功能测试', () => {
beforeEach(() => {
transport = new WebSocketClientTransport(defaultConfig);
});
it('应该能够发起连接', async () => {
const connectPromise = transport.connect();
expect((transport as any).websocket).toBeInstanceOf(MockWebSocket);
expect((transport as any).state).toBe(ConnectionState.CONNECTING);
// 模拟连接成功
const ws = (transport as any).websocket as MockWebSocket;
ws.readyState = WebSocket.OPEN;
if (ws.onopen) {
ws.onopen(new Event('open'));
}
await expect(connectPromise).resolves.toBeUndefined();
});
it('应该构造正确的WebSocket URL', async () => {
transport.connect();
const ws = (transport as any).websocket as MockWebSocket;
expect(ws.url).toBe('ws://localhost:8080/ws');
});
it('使用安全连接时应该构造HTTPS URL', async () => {
const secureConfig = { ...defaultConfig, secure: true };
transport = new WebSocketClientTransport(secureConfig);
transport.connect();
const ws = (transport as any).websocket as MockWebSocket;
expect(ws.url).toBe('wss://localhost:8080/ws');
});
it('应该设置WebSocket事件处理器', async () => {
transport.connect();
const ws = (transport as any).websocket as MockWebSocket;
expect(ws.onopen).toBeDefined();
expect(ws.onclose).toBeDefined();
expect(ws.onmessage).toBeDefined();
expect(ws.onerror).toBeDefined();
});
it('连接超时应该被正确处理', async () => {
const shortTimeoutConfig = { ...defaultConfig, connectionTimeout: 100 };
transport = new WebSocketClientTransport(shortTimeoutConfig);
const connectPromise = transport.connect();
// 不触发onopen事件让连接超时
await expect(connectPromise).rejects.toThrow('连接超时');
});
it('应该能够正确断开连接', async () => {
transport.connect();
// 模拟连接成功
const ws = (transport as any).websocket as MockWebSocket;
ws.readyState = WebSocket.OPEN;
if (ws.onopen) {
ws.onopen(new Event('open'));
}
await transport.disconnect();
expect((transport as any).state).toBe(ConnectionState.DISCONNECTED);
});
});
describe('消息发送测试', () => {
beforeEach(async () => {
transport = new WebSocketClientTransport(defaultConfig);
});
it('未连接时发送消息应该加入队列', async () => {
const message = {
type: 'custom' as const,
data: { test: 'data' },
reliable: true,
timestamp: Date.now()
};
await transport.sendMessage(message);
const messageQueue = (transport as any).messageQueue;
expect(messageQueue).toHaveLength(1);
expect(messageQueue[0]).toEqual(message);
});
it('连接后应该发送队列中的消息', async () => {
const message = {
type: 'custom' as const,
data: { test: 'data' },
reliable: true,
timestamp: Date.now()
};
// 先发送消息到队列
await transport.sendMessage(message);
// 然后连接
transport.connect();
const ws = (transport as any).websocket as MockWebSocket;
const sendSpy = jest.spyOn(ws, 'send');
// 模拟连接成功
ws.readyState = WebSocket.OPEN;
if (ws.onopen) {
ws.onopen(new Event('open'));
}
expect(sendSpy).toHaveBeenCalled();
expect((transport as any).messageQueue).toHaveLength(0);
});
});
describe('错误处理测试', () => {
it('应该处理WebSocket构造函数异常', () => {
// Mock WebSocket构造函数抛出异常
const originalWebSocket = (global as any).WebSocket;
(global as any).WebSocket = jest.fn(() => {
throw new Error('WebSocket构造失败');
});
transport = new WebSocketClientTransport(defaultConfig);
expect(transport.connect()).rejects.toThrow('WebSocket构造失败');
// 恢复原始WebSocket
(global as any).WebSocket = originalWebSocket;
});
it('应该处理网络连接错误', async () => {
transport = new WebSocketClientTransport(defaultConfig);
const connectPromise = transport.connect();
// 模拟连接错误
const ws = (transport as any).websocket as MockWebSocket;
if (ws.onerror) {
ws.onerror(new Event('error'));
}
await expect(connectPromise).rejects.toThrow();
});
it('应该处理意外的连接关闭', () => {
transport = new WebSocketClientTransport(defaultConfig);
transport.connect();
const ws = (transport as any).websocket as MockWebSocket;
// 模拟连接意外关闭
if (ws.onclose) {
ws.onclose(new CloseEvent('close', { code: 1006, reason: '意外关闭' }));
}
expect((transport as any).state).toBe(ConnectionState.DISCONNECTED);
});
});
describe('统计信息测试', () => {
it('应该正确计算连接统计信息', async () => {
transport = new WebSocketClientTransport(defaultConfig);
const initialStats = transport.getStats();
expect(initialStats.connectedAt).toBeNull();
expect(initialStats.messagesSent).toBe(0);
expect(initialStats.messagesReceived).toBe(0);
});
it('连接后应该更新统计信息', async () => {
transport = new WebSocketClientTransport(defaultConfig);
transport.connect();
const ws = (transport as any).websocket as MockWebSocket;
ws.readyState = WebSocket.OPEN;
if (ws.onopen) {
ws.onopen(new Event('open'));
}
const stats = transport.getStats();
expect(stats.connectedAt).toBeInstanceOf(Date);
});
});
});

View File

@@ -4,10 +4,11 @@
"module": "ES2020",
"moduleResolution": "node",
"allowImportingTsExtensions": false,
"lib": ["ES2020", "DOM"],
"lib": ["ES2020", "DOM", "WebWorker"],
"outDir": "./bin",
"rootDir": "./src",
"strict": true,
"composite": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
@@ -41,5 +42,13 @@
"bin",
"**/*.test.ts",
"**/*.spec.ts"
],
"references": [
{
"path": "../core"
},
{
"path": "../network-shared"
}
]
}

View File

@@ -1,132 +0,0 @@
# @esengine/ecs-framework-network-server
ECS Framework 网络库 - 服务端实现
## 概述
这是 ECS Framework 网络库的服务端包,提供了:
- 权威服务端实现
- 客户端会话管理
- 房间和匹配系统
- 反作弊验证
- 网络同步权威控制
## 特性
- **权威服务端**: 所有网络状态由服务端权威控制
- **客户端验证**: 验证客户端输入和操作的合法性
- **房间系统**: 支持多房间和实例管理
- **反作弊**: 内置反作弊验证机制
- **高性能**: 针对大量客户端连接进行优化
## 安装
```bash
npm install @esengine/ecs-framework-network-server
```
## 基本用法
```typescript
import { NetworkServerManager } from '@esengine/ecs-framework-network-server';
import { NetworkComponent, SyncVar, ServerRpc } from '@esengine/ecs-framework-network-shared';
// 启动服务端
const server = new NetworkServerManager();
await server.startServer({
port: 7777,
maxConnections: 100
});
// 创建权威网络组件
@NetworkComponent()
class ServerPlayerController extends NetworkBehaviour {
@SyncVar()
public position: Vector3 = { x: 0, y: 0, z: 0 };
@SyncVar()
public health: number = 100;
@ServerRpc({ requiresOwnership: true, rateLimit: 10 })
public movePlayer(direction: Vector3): void {
// 服务端权威的移动处理
if (this.validateMovement(direction)) {
this.position.add(direction);
}
}
@ServerRpc({ requiresAuth: true })
public takeDamage(damage: number, attackerId: number): void {
// 服务端权威的伤害处理
if (this.validateDamage(damage, attackerId)) {
this.health -= damage;
if (this.health <= 0) {
this.handlePlayerDeath();
}
}
}
}
```
## 房间系统
```typescript
import { RoomManager, Room } from '@esengine/ecs-framework-network-server';
// 创建房间管理器
const roomManager = new RoomManager();
// 创建房间
const gameRoom = roomManager.createRoom({
name: 'Game Room 1',
maxPlayers: 4,
isPrivate: false
});
// 玩家加入房间
gameRoom.addPlayer(clientId, playerData);
// 房间事件处理
gameRoom.onPlayerJoined((player) => {
console.log(`Player ${player.name} joined room ${gameRoom.name}`);
});
gameRoom.onPlayerLeft((player) => {
console.log(`Player ${player.name} left room ${gameRoom.name}`);
});
```
## 权限验证
```typescript
import { AuthSystem } from '@esengine/ecs-framework-network-server';
// 配置认证系统
const authSystem = new AuthSystem({
tokenSecret: 'your-secret-key',
sessionTimeout: 30 * 60 * 1000, // 30分钟
maxLoginAttempts: 5
});
// 客户端认证
authSystem.onClientAuth(async (clientId, credentials) => {
const user = await validateCredentials(credentials);
if (user) {
return { userId: user.id, permissions: user.permissions };
}
return null;
});
// RPC 权限检查
@ServerRpc({ requiresAuth: true, requiresOwnership: true })
public adminCommand(command: string): void {
// 只有已认证且拥有权限的客户端可以调用
this.executeAdminCommand(command);
}
```
## License
MIT

View File

@@ -2,27 +2,32 @@ const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
console.log('🚀 使用 Rollup 构建 network-server 包...');
console.log('🚀 使用 Rollup 构建 @esengine/network-server 包...');
async function main() {
try {
// 清理旧的dist目录
if (fs.existsSync('./dist')) {
console.log('🧹 清理旧的构建文件...');
execSync('rimraf ./dist', { stdio: 'inherit' });
}
// 执行Rollup构建
console.log('📦 执行 Rollup 构建...');
execSync('rollup -c rollup.config.cjs', { stdio: 'inherit' });
execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' });
// 生成package.json
console.log('📋 生成 package.json...');
generatePackageJson();
// 复制其他文件
console.log('📁 复制必要文件...');
copyFiles();
// 输出构建结果
showBuildResults();
console.log('✅ network-server 构建完成!');
console.log('✅ @esengine/network-server 构建完成!');
console.log('\n🚀 发布命令:');
console.log('cd dist && npm publish');
@@ -60,19 +65,18 @@ function generatePackageJson() {
],
keywords: [
'ecs',
'networking',
'network',
'server',
'authority',
'validation',
'rooms',
'game-server',
'multiplayer',
'game',
'nodejs',
'typescript'
],
author: sourcePackage.author,
license: sourcePackage.license,
repository: sourcePackage.repository,
dependencies: sourcePackage.dependencies,
peerDependencies: sourcePackage.peerDependencies,
publishConfig: sourcePackage.publishConfig,
engines: {
node: '>=16.0.0'
},
@@ -85,7 +89,7 @@ function generatePackageJson() {
function copyFiles() {
const filesToCopy = [
{ src: './README.md', dest: './dist/README.md' },
{ src: '../../LICENSE', dest: './dist/LICENSE' }
{ src: './LICENSE', dest: './dist/LICENSE' }
];
filesToCopy.forEach(({ src, dest }) => {

View File

@@ -1,7 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node', // 服务端库使用 node 环境
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testPathIgnorePatterns: ['/node_modules/'],
@@ -18,16 +18,10 @@ module.exports = {
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 60,
branches: 70,
functions: 70,
lines: 70,
statements: 70
},
'./src/core/': {
branches: 70,
functions: 80,
lines: 80,
statements: 80
}
},
verbose: true,
@@ -42,7 +36,7 @@ module.exports = {
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
testTimeout: 10000,
testTimeout: 15000, // 服务端测试可能需要更长时间
clearMocks: true,
restoreMocks: true,
modulePathIgnorePatterns: [

View File

@@ -1,8 +1,7 @@
{
"name": "@esengine/ecs-framework-network-server",
"version": "1.0.5",
"description": "ECS Framework 网络 - 服务端实现",
"type": "module",
"name": "@esengine/network-server",
"version": "1.0.1",
"description": "ECS Framework网络 - 服务端实现",
"main": "bin/index.js",
"types": "bin/index.d.ts",
"exports": {
@@ -22,16 +21,15 @@
],
"keywords": [
"ecs",
"networking",
"network",
"server",
"authority",
"validation",
"rooms",
"game-server",
"multiplayer",
"game",
"nodejs",
"typescript"
],
"scripts": {
"clean": "rimraf bin dist",
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
"build:ts": "tsc",
"prebuild": "npm run clean",
"build": "npm run build:ts",
@@ -43,38 +41,29 @@
"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",
"dev": "ts-node src/dev-server.ts",
"start": "node bin/index.js",
"test": "jest --config jest.config.cjs",
"test:watch": "jest --watch --config jest.config.cjs",
"test:coverage": "jest --coverage --config jest.config.cjs",
"test:ci": "jest --ci --coverage --config jest.config.cjs",
"test:clear": "jest --clearCache"
"test:ci": "jest --ci --coverage --config jest.config.cjs"
},
"author": "yhh",
"license": "MIT",
"dependencies": {
"ws": "^8.18.0",
"uuid": "^10.0.0"
},
"peerDependencies": {
"@esengine/ecs-framework": ">=2.1.29",
"@esengine/ecs-framework-network-shared": ">=1.0.0"
"@esengine/ecs-framework": "file:../core",
"@esengine/network-shared": "file:../network-shared",
"ws": "^8.18.2",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@esengine/ecs-framework": "*",
"@esengine/ecs-framework-network-shared": "*",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/node": "^20.19.0",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.13",
"@types/ws": "^8.18.1",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"rimraf": "^5.0.0",
"rollup": "^4.42.0",
"rollup-plugin-dts": "^6.2.1",
"ts-jest": "^29.4.0",
"ts-node": "^10.9.0",
"typescript": "^5.8.3"
},
"publishConfig": {

View File

@@ -7,23 +7,33 @@ const { readFileSync } = require('fs');
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
const banner = `/**
* @esengine/ecs-framework-network-server v${pkg.version}
* ECS Framework 网络库 - 服务端实现
* @esengine/network-server v${pkg.version}
* ECS网络层服务端实现
*
* @author ${pkg.author}
* @license ${pkg.license}
*/`;
// 外部依赖不打包进bundle (Node.js环境保持依赖外部化)
const external = [
'ws',
'uuid',
'@esengine/ecs-framework',
'@esengine/ecs-framework-network-shared'
'@esengine/network-shared',
'ws',
'reflect-metadata',
'http',
'https',
'crypto',
'events',
'stream',
'util',
'fs',
'path'
];
const commonPlugins = [
resolve({
preferBuiltins: true
preferBuiltins: true,
browser: false
}),
commonjs({
include: /node_modules/
@@ -57,7 +67,7 @@ module.exports = [
}
},
// CommonJS构建
// CommonJS构建 (Node.js主要格式)
{
input: 'bin/index.js',
output: {
@@ -88,7 +98,7 @@ module.exports = [
file: 'dist/index.d.ts',
format: 'es',
banner: `/**
* @esengine/ecs-framework-network-server v${pkg.version}
* @esengine/network-server v${pkg.version}
* TypeScript definitions
*/`
},

View File

@@ -1,622 +0,0 @@
/**
* 身份验证管理器
*
* 处理客户端身份验证、令牌验证等功能
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
/**
* 认证配置
*/
export interface AuthConfig {
/** 令牌过期时间(毫秒) */
tokenExpirationTime?: number;
/** 最大登录尝试次数 */
maxLoginAttempts?: number;
/** 登录尝试重置时间(毫秒) */
loginAttemptResetTime?: number;
/** 是否启用令牌刷新 */
enableTokenRefresh?: boolean;
/** 令牌刷新阈值(毫秒) */
tokenRefreshThreshold?: number;
/** 是否启用IP限制 */
enableIpRestriction?: boolean;
/** 密码哈希算法 */
passwordHashAlgorithm?: 'sha256' | 'sha512';
}
/**
* 用户信息
*/
export interface UserInfo {
/** 用户ID */
id: string;
/** 用户名 */
username: string;
/** 密码哈希 */
passwordHash: string;
/** 用户角色 */
roles: string[];
/** 用户元数据 */
metadata: Record<string, NetworkValue>;
/** 创建时间 */
createdAt: Date;
/** 最后登录时间 */
lastLoginAt?: Date;
/** 是否激活 */
isActive: boolean;
/** 允许的IP地址列表 */
allowedIps?: string[];
}
/**
* 认证令牌
*/
export interface AuthToken {
/** 令牌ID */
id: string;
/** 用户ID */
userId: string;
/** 令牌值 */
token: string;
/** 创建时间 */
createdAt: Date;
/** 过期时间 */
expiresAt: Date;
/** 是否已撤销 */
isRevoked: boolean;
/** 令牌元数据 */
metadata: Record<string, NetworkValue>;
}
/**
* 登录尝试记录
*/
interface LoginAttempt {
/** IP地址 */
ip: string;
/** 用户名 */
username: string;
/** 尝试次数 */
attempts: number;
/** 最后尝试时间 */
lastAttempt: Date;
}
/**
* 认证结果
*/
export interface AuthResult {
/** 是否成功 */
success: boolean;
/** 用户信息 */
user?: UserInfo;
/** 认证令牌 */
token?: AuthToken;
/** 错误信息 */
error?: string;
/** 错误代码 */
errorCode?: string;
}
/**
* 认证管理器事件
*/
export interface AuthManagerEvents {
/** 用户登录成功 */
'login-success': (user: UserInfo, token: AuthToken, clientId: string) => void;
/** 用户登录失败 */
'login-failed': (username: string, reason: string, clientId: string) => void;
/** 用户注销 */
'logout': (userId: string, clientId: string) => void;
/** 令牌过期 */
'token-expired': (userId: string, tokenId: string) => void;
/** 令牌刷新 */
'token-refreshed': (userId: string, oldTokenId: string, newTokenId: string) => void;
/** 认证错误 */
'auth-error': (error: Error, clientId?: string) => void;
}
/**
* 身份验证管理器
*/
export class AuthenticationManager extends EventEmitter {
private config: AuthConfig;
private users = new Map<string, UserInfo>();
private tokens = new Map<string, AuthToken>();
private loginAttempts = new Map<string, LoginAttempt>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: AuthConfig = {}) {
super();
this.config = {
tokenExpirationTime: 24 * 60 * 60 * 1000, // 24小时
maxLoginAttempts: 5,
loginAttemptResetTime: 15 * 60 * 1000, // 15分钟
enableTokenRefresh: true,
tokenRefreshThreshold: 60 * 60 * 1000, // 1小时
enableIpRestriction: false,
passwordHashAlgorithm: 'sha256',
...config
};
this.initialize();
}
/**
* 注册用户
*/
async registerUser(userData: {
username: string;
password: string;
roles?: string[];
metadata?: Record<string, NetworkValue>;
allowedIps?: string[];
}): Promise<UserInfo> {
const { username, password, roles = ['user'], metadata = {}, allowedIps } = userData;
// 检查用户名是否已存在
if (this.findUserByUsername(username)) {
throw new Error('Username already exists');
}
const userId = this.generateId();
const passwordHash = this.hashPassword(password);
const user: UserInfo = {
id: userId,
username,
passwordHash,
roles,
metadata,
createdAt: new Date(),
isActive: true,
allowedIps
};
this.users.set(userId, user);
console.log(`User registered: ${username} (${userId})`);
return user;
}
/**
* 用户登录
*/
async login(
username: string,
password: string,
client: ClientConnection
): Promise<AuthResult> {
try {
const clientIp = client.remoteAddress;
const attemptKey = `${clientIp}-${username}`;
// 检查登录尝试次数
if (this.isLoginBlocked(attemptKey)) {
const result: AuthResult = {
success: false,
error: 'Too many login attempts. Please try again later.',
errorCode: 'LOGIN_BLOCKED'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
// 查找用户
const user = this.findUserByUsername(username);
if (!user || !user.isActive) {
this.recordLoginAttempt(attemptKey);
const result: AuthResult = {
success: false,
error: 'Invalid username or password',
errorCode: 'INVALID_CREDENTIALS'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
// 验证密码
const passwordHash = this.hashPassword(password);
if (user.passwordHash !== passwordHash) {
this.recordLoginAttempt(attemptKey);
const result: AuthResult = {
success: false,
error: 'Invalid username or password',
errorCode: 'INVALID_CREDENTIALS'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
// IP限制检查
if (this.config.enableIpRestriction && user.allowedIps && user.allowedIps.length > 0) {
if (!user.allowedIps.includes(clientIp)) {
const result: AuthResult = {
success: false,
error: 'Access denied from this IP address',
errorCode: 'IP_RESTRICTED'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
}
// 创建认证令牌
const token = this.createToken(user.id);
// 更新用户最后登录时间
user.lastLoginAt = new Date();
// 清除登录尝试记录
this.loginAttempts.delete(attemptKey);
const result: AuthResult = {
success: true,
user,
token
};
console.log(`User logged in: ${username} (${user.id}) from ${clientIp}`);
this.emit('login-success', user, token, client.id);
return result;
} catch (error) {
const result: AuthResult = {
success: false,
error: (error as Error).message,
errorCode: 'INTERNAL_ERROR'
};
this.emit('auth-error', error as Error, client.id);
return result;
}
}
/**
* 用户注销
*/
async logout(tokenValue: string, client: ClientConnection): Promise<boolean> {
try {
const token = this.findTokenByValue(tokenValue);
if (!token) {
return false;
}
// 撤销令牌
token.isRevoked = true;
console.log(`User logged out: ${token.userId} from ${client.remoteAddress}`);
this.emit('logout', token.userId, client.id);
return true;
} catch (error) {
this.emit('auth-error', error as Error, client.id);
return false;
}
}
/**
* 验证令牌
*/
async validateToken(tokenValue: string): Promise<AuthResult> {
try {
const token = this.findTokenByValue(tokenValue);
if (!token || token.isRevoked) {
return {
success: false,
error: 'Invalid token',
errorCode: 'INVALID_TOKEN'
};
}
if (token.expiresAt < new Date()) {
token.isRevoked = true;
this.emit('token-expired', token.userId, token.id);
return {
success: false,
error: 'Token expired',
errorCode: 'TOKEN_EXPIRED'
};
}
const user = this.users.get(token.userId);
if (!user || !user.isActive) {
return {
success: false,
error: 'User not found or inactive',
errorCode: 'USER_NOT_FOUND'
};
}
return {
success: true,
user,
token
};
} catch (error) {
this.emit('auth-error', error as Error);
return {
success: false,
error: (error as Error).message,
errorCode: 'INTERNAL_ERROR'
};
}
}
/**
* 刷新令牌
*/
async refreshToken(tokenValue: string): Promise<AuthResult> {
try {
const validationResult = await this.validateToken(tokenValue);
if (!validationResult.success || !validationResult.user || !validationResult.token) {
return validationResult;
}
const token = validationResult.token;
const timeUntilExpiration = token.expiresAt.getTime() - Date.now();
// 检查是否需要刷新
if (timeUntilExpiration > this.config.tokenRefreshThreshold!) {
return validationResult; // 不需要刷新
}
// 创建新令牌
const newToken = this.createToken(token.userId, token.metadata);
// 撤销旧令牌
token.isRevoked = true;
console.log(`Token refreshed for user: ${token.userId}`);
this.emit('token-refreshed', token.userId, token.id, newToken.id);
return {
success: true,
user: validationResult.user,
token: newToken
};
} catch (error) {
this.emit('auth-error', error as Error);
return {
success: false,
error: (error as Error).message,
errorCode: 'INTERNAL_ERROR'
};
}
}
/**
* 获取用户信息
*/
getUserById(userId: string): UserInfo | undefined {
return this.users.get(userId);
}
/**
* 获取用户信息(通过用户名)
*/
getUserByUsername(username: string): UserInfo | undefined {
return this.findUserByUsername(username);
}
/**
* 更新用户信息
*/
async updateUser(userId: string, updates: Partial<UserInfo>): Promise<boolean> {
const user = this.users.get(userId);
if (!user) {
return false;
}
// 不允许更新某些字段
const { id, createdAt, ...allowedUpdates } = updates as any;
Object.assign(user, allowedUpdates);
return true;
}
/**
* 撤销所有用户令牌
*/
async revokeAllUserTokens(userId: string): Promise<number> {
let revokedCount = 0;
for (const token of this.tokens.values()) {
if (token.userId === userId && !token.isRevoked) {
token.isRevoked = true;
revokedCount++;
}
}
return revokedCount;
}
/**
* 获取活跃令牌数量
*/
getActiveTokenCount(): number {
return Array.from(this.tokens.values())
.filter(token => !token.isRevoked && token.expiresAt > new Date()).length;
}
/**
* 清理过期令牌和登录尝试记录
*/
cleanup(): void {
const now = new Date();
let cleanedTokens = 0;
let cleanedAttempts = 0;
// 清理过期令牌
for (const [tokenId, token] of this.tokens.entries()) {
if (token.expiresAt < now || token.isRevoked) {
this.tokens.delete(tokenId);
cleanedTokens++;
}
}
// 清理过期的登录尝试记录
const resetTime = this.config.loginAttemptResetTime!;
for (const [attemptKey, attempt] of this.loginAttempts.entries()) {
if (now.getTime() - attempt.lastAttempt.getTime() > resetTime) {
this.loginAttempts.delete(attemptKey);
cleanedAttempts++;
}
}
if (cleanedTokens > 0 || cleanedAttempts > 0) {
console.log(`Auth cleanup: ${cleanedTokens} tokens, ${cleanedAttempts} login attempts`);
}
}
/**
* 销毁认证管理器
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.users.clear();
this.tokens.clear();
this.loginAttempts.clear();
this.removeAllListeners();
}
/**
* 初始化
*/
private initialize(): void {
// 启动清理定时器(每小时清理一次)
this.cleanupTimer = setInterval(() => {
this.cleanup();
}, 60 * 60 * 1000);
}
/**
* 查找用户(通过用户名)
*/
private findUserByUsername(username: string): UserInfo | undefined {
return Array.from(this.users.values())
.find(user => user.username === username);
}
/**
* 查找令牌(通过令牌值)
*/
private findTokenByValue(tokenValue: string): AuthToken | undefined {
return Array.from(this.tokens.values())
.find(token => token.token === tokenValue);
}
/**
* 生成ID
*/
private generateId(): string {
return randomBytes(16).toString('hex');
}
/**
* 哈希密码
*/
private hashPassword(password: string): string {
return createHash(this.config.passwordHashAlgorithm!)
.update(password)
.digest('hex');
}
/**
* 创建认证令牌
*/
private createToken(userId: string, metadata: Record<string, NetworkValue> = {}): AuthToken {
const tokenId = this.generateId();
const tokenValue = randomBytes(32).toString('hex');
const now = new Date();
const expiresAt = new Date(now.getTime() + this.config.tokenExpirationTime!);
const token: AuthToken = {
id: tokenId,
userId,
token: tokenValue,
createdAt: now,
expiresAt,
isRevoked: false,
metadata
};
this.tokens.set(tokenId, token);
return token;
}
/**
* 检查登录是否被阻止
*/
private isLoginBlocked(attemptKey: string): boolean {
const attempt = this.loginAttempts.get(attemptKey);
if (!attempt) {
return false;
}
const now = new Date();
const resetTime = this.config.loginAttemptResetTime!;
// 检查重置时间
if (now.getTime() - attempt.lastAttempt.getTime() > resetTime) {
this.loginAttempts.delete(attemptKey);
return false;
}
return attempt.attempts >= this.config.maxLoginAttempts!;
}
/**
* 记录登录尝试
*/
private recordLoginAttempt(attemptKey: string): void {
const now = new Date();
const [ip, username] = attemptKey.split('-', 2);
const existingAttempt = this.loginAttempts.get(attemptKey);
if (existingAttempt) {
// 检查是否需要重置
if (now.getTime() - existingAttempt.lastAttempt.getTime() > this.config.loginAttemptResetTime!) {
existingAttempt.attempts = 1;
} else {
existingAttempt.attempts++;
}
existingAttempt.lastAttempt = now;
} else {
this.loginAttempts.set(attemptKey, {
ip,
username,
attempts: 1,
lastAttempt: now
});
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof AuthManagerEvents>(event: K, listener: AuthManagerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof AuthManagerEvents>(event: K, ...args: Parameters<AuthManagerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,684 +0,0 @@
/**
* 权限管理器
*
* 处理用户权限、角色管理、访问控制等功能
*/
import { EventEmitter } from 'events';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { UserInfo } from './AuthenticationManager';
/**
* 权限类型
*/
export type Permission = string;
/**
* 角色定义
*/
export interface Role {
/** 角色ID */
id: string;
/** 角色名称 */
name: string;
/** 角色描述 */
description?: string;
/** 权限列表 */
permissions: Permission[];
/** 父角色ID */
parentRoleId?: string;
/** 是否系统角色 */
isSystemRole: boolean;
/** 角色元数据 */
metadata: Record<string, NetworkValue>;
/** 创建时间 */
createdAt: Date;
}
/**
* 权限检查上下文
*/
export interface PermissionContext {
/** 用户ID */
userId: string;
/** 用户角色 */
userRoles: string[];
/** 请求的权限 */
permission: Permission;
/** 资源ID可选 */
resourceId?: string;
/** 附加上下文数据 */
context?: Record<string, NetworkValue>;
}
/**
* 权限检查结果
*/
export interface PermissionResult {
/** 是否允许 */
granted: boolean;
/** 原因 */
reason?: string;
/** 匹配的角色 */
matchingRole?: string;
/** 使用的权限 */
usedPermission?: Permission;
}
/**
* 权限管理器配置
*/
export interface AuthorizationConfig {
/** 是否启用权限继承 */
enableInheritance?: boolean;
/** 是否启用权限缓存 */
enableCache?: boolean;
/** 缓存过期时间(毫秒) */
cacheExpirationTime?: number;
/** 默认权限策略 */
defaultPolicy?: 'deny' | 'allow';
}
/**
* 权限管理器事件
*/
export interface AuthorizationEvents {
/** 权限被授予 */
'permission-granted': (context: PermissionContext, result: PermissionResult) => void;
/** 权限被拒绝 */
'permission-denied': (context: PermissionContext, result: PermissionResult) => void;
/** 角色创建 */
'role-created': (role: Role) => void;
/** 角色更新 */
'role-updated': (roleId: string, updates: Partial<Role>) => void;
/** 角色删除 */
'role-deleted': (roleId: string) => void;
/** 权限错误 */
'authorization-error': (error: Error, context?: PermissionContext) => void;
}
/**
* 权限缓存项
*/
interface CacheItem {
result: PermissionResult;
expiresAt: Date;
}
/**
* 预定义权限
*/
export const Permissions = {
// 系统权限
SYSTEM_ADMIN: 'system:admin',
SYSTEM_CONFIG: 'system:config',
// 用户管理权限
USER_CREATE: 'user:create',
USER_READ: 'user:read',
USER_UPDATE: 'user:update',
USER_DELETE: 'user:delete',
USER_MANAGE_ROLES: 'user:manage-roles',
// 房间权限
ROOM_CREATE: 'room:create',
ROOM_JOIN: 'room:join',
ROOM_LEAVE: 'room:leave',
ROOM_MANAGE: 'room:manage',
ROOM_KICK_PLAYERS: 'room:kick-players',
// 网络权限
NETWORK_SEND_RPC: 'network:send-rpc',
NETWORK_SYNC_VARS: 'network:sync-vars',
NETWORK_BROADCAST: 'network:broadcast',
// 聊天权限
CHAT_SEND: 'chat:send',
CHAT_MODERATE: 'chat:moderate',
CHAT_PRIVATE: 'chat:private',
// 文件权限
FILE_UPLOAD: 'file:upload',
FILE_DOWNLOAD: 'file:download',
FILE_DELETE: 'file:delete'
} as const;
/**
* 预定义角色
*/
export const SystemRoles = {
ADMIN: 'admin',
MODERATOR: 'moderator',
USER: 'user',
GUEST: 'guest'
} as const;
/**
* 权限管理器
*/
export class AuthorizationManager extends EventEmitter {
private config: AuthorizationConfig;
private roles = new Map<string, Role>();
private permissionCache = new Map<string, CacheItem>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: AuthorizationConfig = {}) {
super();
this.config = {
enableInheritance: true,
enableCache: true,
cacheExpirationTime: 5 * 60 * 1000, // 5分钟
defaultPolicy: 'deny',
...config
};
this.initialize();
}
/**
* 创建角色
*/
async createRole(roleData: {
id: string;
name: string;
description?: string;
permissions: Permission[];
parentRoleId?: string;
metadata?: Record<string, NetworkValue>;
}): Promise<Role> {
const { id, name, description, permissions, parentRoleId, metadata = {} } = roleData;
if (this.roles.has(id)) {
throw new Error(`Role with id "${id}" already exists`);
}
// 验证父角色是否存在
if (parentRoleId && !this.roles.has(parentRoleId)) {
throw new Error(`Parent role "${parentRoleId}" not found`);
}
const role: Role = {
id,
name,
description,
permissions: [...permissions],
parentRoleId,
isSystemRole: false,
metadata,
createdAt: new Date()
};
this.roles.set(id, role);
this.clearPermissionCache(); // 清除缓存
console.log(`Role created: ${name} (${id})`);
this.emit('role-created', role);
return role;
}
/**
* 获取角色
*/
getRole(roleId: string): Role | undefined {
return this.roles.get(roleId);
}
/**
* 获取所有角色
*/
getAllRoles(): Role[] {
return Array.from(this.roles.values());
}
/**
* 更新角色
*/
async updateRole(roleId: string, updates: Partial<Role>): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
// 系统角色不允许修改某些字段
if (role.isSystemRole) {
const { permissions, parentRoleId, ...allowedUpdates } = updates;
Object.assign(role, allowedUpdates);
} else {
// 不允许更新某些字段
const { id, createdAt, isSystemRole, ...allowedUpdates } = updates as any;
Object.assign(role, allowedUpdates);
}
this.clearPermissionCache(); // 清除缓存
console.log(`Role updated: ${role.name} (${roleId})`);
this.emit('role-updated', roleId, updates);
return true;
}
/**
* 删除角色
*/
async deleteRole(roleId: string): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
if (role.isSystemRole) {
throw new Error('Cannot delete system role');
}
// 检查是否有子角色依赖此角色
const childRoles = Array.from(this.roles.values())
.filter(r => r.parentRoleId === roleId);
if (childRoles.length > 0) {
throw new Error(`Cannot delete role "${roleId}": ${childRoles.length} child roles depend on it`);
}
this.roles.delete(roleId);
this.clearPermissionCache(); // 清除缓存
console.log(`Role deleted: ${role.name} (${roleId})`);
this.emit('role-deleted', roleId);
return true;
}
/**
* 检查权限
*/
async checkPermission(context: PermissionContext): Promise<PermissionResult> {
try {
// 检查缓存
const cacheKey = this.getCacheKey(context);
if (this.config.enableCache) {
const cached = this.permissionCache.get(cacheKey);
if (cached && cached.expiresAt > new Date()) {
return cached.result;
}
}
const result = await this.performPermissionCheck(context);
// 缓存结果
if (this.config.enableCache) {
const expiresAt = new Date(Date.now() + this.config.cacheExpirationTime!);
this.permissionCache.set(cacheKey, { result, expiresAt });
}
// 触发事件
if (result.granted) {
this.emit('permission-granted', context, result);
} else {
this.emit('permission-denied', context, result);
}
return result;
} catch (error) {
this.emit('authorization-error', error as Error, context);
return {
granted: this.config.defaultPolicy === 'allow',
reason: `Authorization error: ${(error as Error).message}`
};
}
}
/**
* 检查用户是否有权限
*/
async hasPermission(user: UserInfo, permission: Permission, resourceId?: string): Promise<boolean> {
const context: PermissionContext = {
userId: user.id,
userRoles: user.roles,
permission,
resourceId
};
const result = await this.checkPermission(context);
return result.granted;
}
/**
* 获取用户的所有权限
*/
async getUserPermissions(user: UserInfo): Promise<Permission[]> {
const permissions = new Set<Permission>();
for (const roleId of user.roles) {
const rolePermissions = await this.getRolePermissions(roleId);
rolePermissions.forEach(p => permissions.add(p));
}
return Array.from(permissions);
}
/**
* 获取角色的所有权限(包括继承的权限)
*/
async getRolePermissions(roleId: string): Promise<Permission[]> {
const permissions = new Set<Permission>();
const visited = new Set<string>();
const collectPermissions = (currentRoleId: string) => {
if (visited.has(currentRoleId)) {
return; // 防止循环引用
}
visited.add(currentRoleId);
const role = this.roles.get(currentRoleId);
if (!role) {
return;
}
// 添加当前角色的权限
role.permissions.forEach(p => permissions.add(p));
// 递归添加父角色的权限
if (this.config.enableInheritance && role.parentRoleId) {
collectPermissions(role.parentRoleId);
}
};
collectPermissions(roleId);
return Array.from(permissions);
}
/**
* 为角色添加权限
*/
async addPermissionToRole(roleId: string, permission: Permission): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
if (!role.permissions.includes(permission)) {
role.permissions.push(permission);
this.clearPermissionCache();
console.log(`Permission "${permission}" added to role "${roleId}"`);
}
return true;
}
/**
* 从角色移除权限
*/
async removePermissionFromRole(roleId: string, permission: Permission): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
const index = role.permissions.indexOf(permission);
if (index !== -1) {
role.permissions.splice(index, 1);
this.clearPermissionCache();
console.log(`Permission "${permission}" removed from role "${roleId}"`);
}
return true;
}
/**
* 检查用户是否有指定角色
*/
hasRole(user: UserInfo, roleId: string): boolean {
return user.roles.includes(roleId);
}
/**
* 为用户添加角色
*/
async addRoleToUser(user: UserInfo, roleId: string): Promise<boolean> {
if (!this.roles.has(roleId)) {
return false;
}
if (!user.roles.includes(roleId)) {
user.roles.push(roleId);
this.clearUserPermissionCache(user.id);
console.log(`Role "${roleId}" added to user "${user.id}"`);
}
return true;
}
/**
* 从用户移除角色
*/
async removeRoleFromUser(user: UserInfo, roleId: string): Promise<boolean> {
const index = user.roles.indexOf(roleId);
if (index !== -1) {
user.roles.splice(index, 1);
this.clearUserPermissionCache(user.id);
console.log(`Role "${roleId}" removed from user "${user.id}"`);
return true;
}
return false;
}
/**
* 清除权限缓存
*/
clearPermissionCache(): void {
this.permissionCache.clear();
}
/**
* 清除指定用户的权限缓存
*/
clearUserPermissionCache(userId: string): void {
const keysToDelete: string[] = [];
for (const [key] of this.permissionCache) {
if (key.startsWith(`${userId}:`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.permissionCache.delete(key));
}
/**
* 销毁权限管理器
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.roles.clear();
this.permissionCache.clear();
this.removeAllListeners();
}
/**
* 初始化
*/
private initialize(): void {
// 创建系统角色
this.createSystemRoles();
// 启动缓存清理定时器每30分钟清理一次
if (this.config.enableCache) {
this.cleanupTimer = setInterval(() => {
this.cleanupCache();
}, 30 * 60 * 1000);
}
}
/**
* 创建系统角色
*/
private createSystemRoles(): void {
// 管理员角色
const adminRole: Role = {
id: SystemRoles.ADMIN,
name: 'Administrator',
description: 'Full system access',
permissions: Object.values(Permissions),
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
// 版主角色
const moderatorRole: Role = {
id: SystemRoles.MODERATOR,
name: 'Moderator',
description: 'Room and user management',
permissions: [
Permissions.USER_READ,
Permissions.ROOM_CREATE,
Permissions.ROOM_JOIN,
Permissions.ROOM_MANAGE,
Permissions.ROOM_KICK_PLAYERS,
Permissions.NETWORK_SEND_RPC,
Permissions.NETWORK_SYNC_VARS,
Permissions.CHAT_SEND,
Permissions.CHAT_MODERATE,
Permissions.CHAT_PRIVATE
],
parentRoleId: SystemRoles.USER,
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
// 普通用户角色
const userRole: Role = {
id: SystemRoles.USER,
name: 'User',
description: 'Basic user permissions',
permissions: [
Permissions.ROOM_JOIN,
Permissions.ROOM_LEAVE,
Permissions.NETWORK_SEND_RPC,
Permissions.NETWORK_SYNC_VARS,
Permissions.CHAT_SEND,
Permissions.FILE_DOWNLOAD
],
parentRoleId: SystemRoles.GUEST,
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
// 访客角色
const guestRole: Role = {
id: SystemRoles.GUEST,
name: 'Guest',
description: 'Limited access for guests',
permissions: [
Permissions.ROOM_JOIN
],
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
this.roles.set(adminRole.id, adminRole);
this.roles.set(moderatorRole.id, moderatorRole);
this.roles.set(userRole.id, userRole);
this.roles.set(guestRole.id, guestRole);
console.log('System roles created');
}
/**
* 执行权限检查
*/
private async performPermissionCheck(context: PermissionContext): Promise<PermissionResult> {
// 获取用户的所有角色权限
const userPermissions = new Set<Permission>();
for (const roleId of context.userRoles) {
const rolePermissions = await this.getRolePermissions(roleId);
rolePermissions.forEach(p => userPermissions.add(p));
}
// 直接权限匹配
if (userPermissions.has(context.permission)) {
return {
granted: true,
reason: 'Direct permission match',
usedPermission: context.permission
};
}
// 通配符权限匹配
const wildcardPermissions = Array.from(userPermissions)
.filter(p => p.endsWith('*'));
for (const wildcardPerm of wildcardPermissions) {
const prefix = wildcardPerm.slice(0, -1);
if (context.permission.startsWith(prefix)) {
return {
granted: true,
reason: 'Wildcard permission match',
usedPermission: wildcardPerm
};
}
}
// 如果没有匹配的权限
return {
granted: this.config.defaultPolicy === 'allow',
reason: this.config.defaultPolicy === 'allow'
? 'Default allow policy'
: 'No matching permissions found'
};
}
/**
* 获取缓存键
*/
private getCacheKey(context: PermissionContext): string {
const roleString = context.userRoles.sort().join(',');
const resourcePart = context.resourceId ? `:${context.resourceId}` : '';
return `${context.userId}:${roleString}:${context.permission}${resourcePart}`;
}
/**
* 清理过期缓存
*/
private cleanupCache(): void {
const now = new Date();
let cleanedCount = 0;
for (const [key, item] of this.permissionCache.entries()) {
if (item.expiresAt < now) {
this.permissionCache.delete(key);
cleanedCount++;
}
}
if (cleanedCount > 0) {
console.log(`Permission cache cleanup: ${cleanedCount} entries removed`);
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof AuthorizationEvents>(event: K, listener: AuthorizationEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof AuthorizationEvents>(event: K, ...args: Parameters<AuthorizationEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,6 +0,0 @@
/**
* 认证系统导出
*/
export * from './AuthenticationManager';
export * from './AuthorizationManager';

View File

@@ -1,478 +0,0 @@
/**
* 客户端连接管理
*/
import { EventEmitter } from 'events';
import { NetworkValue, NetworkMessage } from '@esengine/ecs-framework-network-shared';
import { TransportMessage } from './Transport';
/**
* 客户端连接状态
*/
export enum ClientConnectionState {
/** 连接中 */
CONNECTING = 'connecting',
/** 已连接 */
CONNECTED = 'connected',
/** 认证中 */
AUTHENTICATING = 'authenticating',
/** 已认证 */
AUTHENTICATED = 'authenticated',
/** 断开连接中 */
DISCONNECTING = 'disconnecting',
/** 已断开 */
DISCONNECTED = 'disconnected',
/** 错误状态 */
ERROR = 'error'
}
/**
* 客户端权限
*/
export interface ClientPermissions {
/** 是否可以加入房间 */
canJoinRooms?: boolean;
/** 是否可以创建房间 */
canCreateRooms?: boolean;
/** 是否可以发送RPC */
canSendRpc?: boolean;
/** 是否可以同步变量 */
canSyncVars?: boolean;
/** 自定义权限 */
customPermissions?: Record<string, boolean>;
}
/**
* 客户端连接事件
*/
export interface ClientConnectionEvents {
/** 状态变化 */
'state-changed': (oldState: ClientConnectionState, newState: ClientConnectionState) => void;
/** 收到消息 */
'message': (message: TransportMessage) => void;
/** 连接错误 */
'error': (error: Error) => void;
/** 连接超时 */
'timeout': () => void;
/** 身份验证成功 */
'authenticated': (userData: Record<string, NetworkValue>) => void;
/** 身份验证失败 */
'authentication-failed': (reason: string) => void;
}
/**
* 客户端统计信息
*/
export interface ClientStats {
/** 消息发送数 */
messagesSent: number;
/** 消息接收数 */
messagesReceived: number;
/** 字节发送数 */
bytesSent: number;
/** 字节接收数 */
bytesReceived: number;
/** 最后活跃时间 */
lastActivity: Date;
/** 连接时长(毫秒) */
connectionDuration: number;
}
/**
* 客户端连接管理类
*/
export class ClientConnection extends EventEmitter {
/** 连接ID */
public readonly id: string;
/** 客户端IP地址 */
public readonly remoteAddress: string;
/** 连接创建时间 */
public readonly connectedAt: Date;
/** 当前状态 */
private _state: ClientConnectionState = ClientConnectionState.CONNECTING;
/** 用户数据 */
private _userData: Record<string, NetworkValue> = {};
/** 权限信息 */
private _permissions: ClientPermissions = {};
/** 所在房间ID */
private _currentRoomId: string | null = null;
/** 统计信息 */
private _stats: ClientStats;
/** 最后活跃时间 */
private _lastActivity: Date;
/** 超时定时器 */
private _timeoutTimer: NodeJS.Timeout | null = null;
/** 连接超时时间(毫秒) */
private _connectionTimeout: number;
/** 发送消息回调 */
private _sendMessageCallback: (message: TransportMessage) => Promise<boolean>;
constructor(
id: string,
remoteAddress: string,
sendMessageCallback: (message: TransportMessage) => Promise<boolean>,
options: {
connectionTimeout?: number;
userData?: Record<string, NetworkValue>;
permissions?: ClientPermissions;
} = {}
) {
super();
this.id = id;
this.remoteAddress = remoteAddress;
this.connectedAt = new Date();
this._lastActivity = new Date();
this._connectionTimeout = options.connectionTimeout || 60000; // 1分钟
this._sendMessageCallback = sendMessageCallback;
if (options.userData) {
this._userData = { ...options.userData };
}
if (options.permissions) {
this._permissions = { ...options.permissions };
}
this._stats = {
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0,
lastActivity: this._lastActivity,
connectionDuration: 0
};
this.setState(ClientConnectionState.CONNECTED);
this.startTimeout();
}
/**
* 获取当前状态
*/
get state(): ClientConnectionState {
return this._state;
}
/**
* 获取用户数据
*/
get userData(): Readonly<Record<string, NetworkValue>> {
return this._userData;
}
/**
* 获取权限信息
*/
get permissions(): Readonly<ClientPermissions> {
return this._permissions;
}
/**
* 获取当前房间ID
*/
get currentRoomId(): string | null {
return this._currentRoomId;
}
/**
* 获取统计信息
*/
get stats(): Readonly<ClientStats> {
this._stats.connectionDuration = Date.now() - this.connectedAt.getTime();
this._stats.lastActivity = this._lastActivity;
return this._stats;
}
/**
* 获取最后活跃时间
*/
get lastActivity(): Date {
return this._lastActivity;
}
/**
* 是否已连接
*/
get isConnected(): boolean {
return this._state === ClientConnectionState.CONNECTED ||
this._state === ClientConnectionState.AUTHENTICATED;
}
/**
* 是否已认证
*/
get isAuthenticated(): boolean {
return this._state === ClientConnectionState.AUTHENTICATED;
}
/**
* 发送消息
*/
async sendMessage(message: TransportMessage): Promise<boolean> {
if (!this.isConnected) {
return false;
}
try {
const success = await this._sendMessageCallback(message);
if (success) {
this._stats.messagesSent++;
const messageSize = JSON.stringify(message).length;
this._stats.bytesSent += messageSize;
this.updateActivity();
}
return success;
} catch (error) {
this.handleError(error as Error);
return false;
}
}
/**
* 处理接收到的消息
*/
handleMessage(message: TransportMessage): void {
if (!this.isConnected) {
return;
}
this._stats.messagesReceived++;
const messageSize = JSON.stringify(message).length;
this._stats.bytesReceived += messageSize;
this.updateActivity();
this.emit('message', message);
}
/**
* 设置用户数据
*/
setUserData(key: string, value: NetworkValue): void {
this._userData[key] = value;
}
/**
* 获取用户数据
*/
getUserData<T extends NetworkValue = NetworkValue>(key: string): T | undefined {
return this._userData[key] as T;
}
/**
* 批量设置用户数据
*/
setUserDataBatch(data: Record<string, NetworkValue>): void {
Object.assign(this._userData, data);
}
/**
* 设置权限
*/
setPermission(permission: keyof ClientPermissions, value: boolean): void {
(this._permissions as any)[permission] = value;
}
/**
* 检查权限
*/
hasPermission(permission: keyof ClientPermissions): boolean {
return (this._permissions as any)[permission] || false;
}
/**
* 设置自定义权限
*/
setCustomPermission(permission: string, value: boolean): void {
if (!this._permissions.customPermissions) {
this._permissions.customPermissions = {};
}
this._permissions.customPermissions[permission] = value;
}
/**
* 检查自定义权限
*/
hasCustomPermission(permission: string): boolean {
return this._permissions.customPermissions?.[permission] || false;
}
/**
* 进行身份认证
*/
async authenticate(credentials: Record<string, NetworkValue>): Promise<boolean> {
if (this._state !== ClientConnectionState.CONNECTED) {
return false;
}
this.setState(ClientConnectionState.AUTHENTICATING);
try {
// 这里可以添加实际的认证逻辑
// 目前简单地认为所有认证都成功
this.setUserDataBatch(credentials);
this.setState(ClientConnectionState.AUTHENTICATED);
this.emit('authenticated', credentials);
return true;
} catch (error) {
this.setState(ClientConnectionState.CONNECTED);
this.emit('authentication-failed', (error as Error).message);
return false;
}
}
/**
* 加入房间
*/
joinRoom(roomId: string): void {
this._currentRoomId = roomId;
}
/**
* 离开房间
*/
leaveRoom(): void {
this._currentRoomId = null;
}
/**
* 断开连接
*/
disconnect(reason?: string): void {
if (this._state === ClientConnectionState.DISCONNECTED) {
return;
}
this.setState(ClientConnectionState.DISCONNECTING);
this.stopTimeout();
// 发送断开连接消息
this.sendMessage({
type: 'system',
data: {
action: 'disconnect',
reason: reason || 'server-disconnect'
}
}).finally(() => {
this.setState(ClientConnectionState.DISCONNECTED);
});
}
/**
* 更新活跃时间
*/
updateActivity(): void {
this._lastActivity = new Date();
this.resetTimeout();
}
/**
* 设置连接状态
*/
private setState(newState: ClientConnectionState): void {
const oldState = this._state;
if (oldState !== newState) {
this._state = newState;
this.emit('state-changed', oldState, newState);
}
}
/**
* 处理错误
*/
private handleError(error: Error): void {
this.setState(ClientConnectionState.ERROR);
this.emit('error', error);
}
/**
* 启动超时检测
*/
private startTimeout(): void {
this.resetTimeout();
}
/**
* 重置超时定时器
*/
private resetTimeout(): void {
this.stopTimeout();
if (this._connectionTimeout > 0) {
this._timeoutTimer = setTimeout(() => {
this.handleTimeout();
}, this._connectionTimeout);
}
}
/**
* 停止超时检测
*/
private stopTimeout(): void {
if (this._timeoutTimer) {
clearTimeout(this._timeoutTimer);
this._timeoutTimer = null;
}
}
/**
* 处理超时
*/
private handleTimeout(): void {
this.emit('timeout');
this.disconnect('timeout');
}
/**
* 销毁连接
*/
destroy(): void {
this.stopTimeout();
this.removeAllListeners();
this.setState(ClientConnectionState.DISCONNECTED);
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof ClientConnectionEvents>(event: K, listener: ClientConnectionEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof ClientConnectionEvents>(event: K, ...args: Parameters<ClientConnectionEvents[K]>): boolean {
return super.emit(event, ...args);
}
/**
* 序列化连接信息
*/
toJSON(): object {
return {
id: this.id,
remoteAddress: this.remoteAddress,
state: this._state,
connectedAt: this.connectedAt.toISOString(),
lastActivity: this._lastActivity.toISOString(),
currentRoomId: this._currentRoomId,
userData: this._userData,
permissions: this._permissions,
stats: this.stats
};
}
}

View File

@@ -1,602 +0,0 @@
/**
* HTTP 传输层实现
*
* 用于处理 REST API 请求和长轮询连接
*/
import { createServer, IncomingMessage, ServerResponse, Server as HttpServer } from 'http';
import { parse as parseUrl } from 'url';
import { v4 as uuidv4 } from 'uuid';
import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport';
/**
* HTTP 传输配置
*/
export interface HttpTransportConfig extends TransportConfig {
/** API 路径前缀 */
apiPrefix?: string;
/** 最大请求大小(字节) */
maxRequestSize?: number;
/** 长轮询超时(毫秒) */
longPollTimeout?: number;
/** 是否启用 CORS */
enableCors?: boolean;
/** 允许的域名 */
corsOrigins?: string[];
}
/**
* HTTP 请求上下文
*/
interface HttpRequestContext {
/** 请求ID */
id: string;
/** HTTP 请求 */
request: IncomingMessage;
/** HTTP 响应 */
response: ServerResponse;
/** 解析后的URL */
parsedUrl: any;
/** 请求体数据 */
body?: string;
/** 查询参数 */
query: Record<string, string>;
}
/**
* HTTP 客户端连接信息(用于长轮询)
*/
interface HttpConnectionInfo extends ClientConnectionInfo {
/** 长轮询响应对象 */
longPollResponse?: ServerResponse;
/** 消息队列 */
messageQueue: TransportMessage[];
/** 长轮询超时定时器 */
longPollTimer?: NodeJS.Timeout;
}
/**
* HTTP 传输层实现
*/
export class HttpTransport extends Transport {
private httpServer: HttpServer | null = null;
private httpConnections = new Map<string, HttpConnectionInfo>();
protected override config: HttpTransportConfig;
constructor(config: HttpTransportConfig) {
super(config);
this.config = {
apiPrefix: '/api',
maxRequestSize: 1024 * 1024, // 1MB
longPollTimeout: 30000, // 30秒
enableCors: true,
corsOrigins: ['*'],
heartbeatInterval: 60000,
connectionTimeout: 120000,
maxConnections: 1000,
...config
};
}
/**
* 启动 HTTP 服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('HTTP transport is already running');
}
try {
this.httpServer = createServer((req, res) => {
this.handleHttpRequest(req, res);
});
this.httpServer.on('error', (error: Error) => {
this.handleError(error);
});
await new Promise<void>((resolve, reject) => {
this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => {
if (error) {
reject(error);
} else {
this.isRunning = true;
resolve();
}
});
});
this.emit('server-started', this.config);
} catch (error) {
await this.cleanup();
throw error;
}
}
/**
* 停止 HTTP 服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有长轮询连接
for (const [connectionId] of this.httpConnections) {
this.disconnectClient(connectionId, 'server-shutdown');
}
await this.cleanup();
this.emit('server-stopped');
}
/**
* 发送消息给指定客户端
*/
async sendToClient(connectionId: string, message: TransportMessage): Promise<boolean> {
const connection = this.httpConnections.get(connectionId);
if (!connection) {
return false;
}
// 如果有长轮询连接,直接发送
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
this.sendLongPollResponse(connection, [message]);
return true;
}
// 否则加入消息队列
connection.messageQueue.push(message);
return true;
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
let sentCount = 0;
for (const [connectionId, connection] of this.httpConnections) {
if (excludeId && connectionId === excludeId) {
continue;
}
if (await this.sendToClient(connectionId, message)) {
sentCount++;
}
}
return sentCount;
}
/**
* 发送消息给指定客户端列表
*/
async sendToClients(connectionIds: string[], message: TransportMessage): Promise<number> {
let sentCount = 0;
for (const connectionId of connectionIds) {
if (await this.sendToClient(connectionId, message)) {
sentCount++;
}
}
return sentCount;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(connectionId: string, reason?: string): Promise<void> {
const connection = this.httpConnections.get(connectionId);
if (connection) {
this.cleanupConnection(connectionId);
this.removeConnection(connectionId, reason);
}
}
/**
* 处理 HTTP 请求
*/
private async handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
try {
// 设置 CORS 头
if (this.config.enableCors) {
this.setCorsHeaders(res);
}
// 处理 OPTIONS 请求
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
const parsedUrl = parseUrl(req.url || '', true);
const pathname = parsedUrl.pathname || '';
// 检查是否为 API 请求
if (!pathname.startsWith(this.config.apiPrefix!)) {
this.sendErrorResponse(res, 404, 'Not Found');
return;
}
const context: HttpRequestContext = {
id: uuidv4(),
request: req,
response: res,
parsedUrl,
query: parsedUrl.query as Record<string, string>,
};
// 读取请求体
if (req.method === 'POST' || req.method === 'PUT') {
context.body = await this.readRequestBody(req);
}
// 路由处理
const apiPath = pathname.substring(this.config.apiPrefix!.length);
await this.routeApiRequest(context, apiPath);
} catch (error) {
this.handleError(error as Error);
this.sendErrorResponse(res, 500, 'Internal Server Error');
}
}
/**
* API 路由处理
*/
private async routeApiRequest(context: HttpRequestContext, apiPath: string): Promise<void> {
const { request, response } = context;
switch (apiPath) {
case '/connect':
if (request.method === 'POST') {
await this.handleConnect(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/disconnect':
if (request.method === 'POST') {
await this.handleDisconnect(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/poll':
if (request.method === 'GET') {
await this.handleLongPoll(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/send':
if (request.method === 'POST') {
await this.handleSendMessage(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/status':
if (request.method === 'GET') {
await this.handleStatus(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
default:
this.sendErrorResponse(response, 404, 'API endpoint not found');
break;
}
}
/**
* 处理连接请求
*/
private async handleConnect(context: HttpRequestContext): Promise<void> {
const { request, response } = context;
try {
// 检查连接数限制
if (this.config.maxConnections && this.httpConnections.size >= this.config.maxConnections) {
this.sendErrorResponse(response, 429, 'Too many connections');
return;
}
const connectionId = uuidv4();
const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown';
const connectionInfo: HttpConnectionInfo = {
id: connectionId,
remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress,
connectedAt: new Date(),
lastActivity: new Date(),
userData: {},
messageQueue: []
};
this.httpConnections.set(connectionId, connectionInfo);
this.addConnection(connectionInfo);
this.sendJsonResponse(response, 200, {
success: true,
connectionId,
serverTime: Date.now()
});
} catch (error) {
this.handleError(error as Error);
this.sendErrorResponse(response, 500, 'Failed to create connection');
}
}
/**
* 处理断开连接请求
*/
private async handleDisconnect(context: HttpRequestContext): Promise<void> {
const { response, query } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
await this.disconnectClient(connectionId, 'client-disconnect');
this.sendJsonResponse(response, 200, {
success: true,
message: 'Disconnected successfully'
});
}
/**
* 处理长轮询请求
*/
private async handleLongPoll(context: HttpRequestContext): Promise<void> {
const { response, query } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
const connection = this.httpConnections.get(connectionId);
if (!connection) {
this.sendErrorResponse(response, 404, 'Connection not found');
return;
}
this.updateClientActivity(connectionId);
// 如果有排队的消息,立即返回
if (connection.messageQueue.length > 0) {
const messages = connection.messageQueue.splice(0);
this.sendLongPollResponse(connection, messages);
return;
}
// 设置长轮询
connection.longPollResponse = response;
// 设置超时
connection.longPollTimer = setTimeout(() => {
this.sendLongPollResponse(connection, []);
}, this.config.longPollTimeout);
}
/**
* 处理发送消息请求
*/
private async handleSendMessage(context: HttpRequestContext): Promise<void> {
const { response, query, body } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
const connection = this.httpConnections.get(connectionId);
if (!connection) {
this.sendErrorResponse(response, 404, 'Connection not found');
return;
}
if (!body) {
this.sendErrorResponse(response, 400, 'Missing message body');
return;
}
try {
const message = JSON.parse(body) as TransportMessage;
message.senderId = connectionId;
this.handleMessage(connectionId, message);
this.sendJsonResponse(response, 200, {
success: true,
message: 'Message sent successfully'
});
} catch (error) {
this.sendErrorResponse(response, 400, 'Invalid message format');
}
}
/**
* 处理状态请求
*/
private async handleStatus(context: HttpRequestContext): Promise<void> {
const { response } = context;
this.sendJsonResponse(response, 200, {
success: true,
status: 'running',
connections: this.httpConnections.size,
uptime: process.uptime(),
serverTime: Date.now()
});
}
/**
* 读取请求体
*/
private readRequestBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let body = '';
let totalSize = 0;
req.on('data', (chunk: Buffer) => {
totalSize += chunk.length;
if (totalSize > this.config.maxRequestSize!) {
reject(new Error('Request body too large'));
return;
}
body += chunk.toString();
});
req.on('end', () => {
resolve(body);
});
req.on('error', (error) => {
reject(error);
});
});
}
/**
* 发送长轮询响应
*/
private sendLongPollResponse(connection: HttpConnectionInfo, messages: TransportMessage[]): void {
if (!connection.longPollResponse || connection.longPollResponse.headersSent) {
return;
}
// 清理定时器
if (connection.longPollTimer) {
clearTimeout(connection.longPollTimer);
connection.longPollTimer = undefined;
}
this.sendJsonResponse(connection.longPollResponse, 200, {
success: true,
messages
});
connection.longPollResponse = undefined;
}
/**
* 设置 CORS 头
*/
private setCorsHeaders(res: ServerResponse): void {
const origins = this.config.corsOrigins!;
const origin = origins.includes('*') ? '*' : origins[0];
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
}
/**
* 发送 JSON 响应
*/
private sendJsonResponse(res: ServerResponse, statusCode: number, data: any): void {
if (res.headersSent) return;
res.setHeader('Content-Type', 'application/json');
res.writeHead(statusCode);
res.end(JSON.stringify(data));
}
/**
* 发送错误响应
*/
private sendErrorResponse(res: ServerResponse, statusCode: number, message: string): void {
if (res.headersSent) return;
this.sendJsonResponse(res, statusCode, {
success: false,
error: message,
code: statusCode
});
}
/**
* 清理连接资源
*/
private cleanupConnection(connectionId: string): void {
const connection = this.httpConnections.get(connectionId);
if (connection) {
if (connection.longPollTimer) {
clearTimeout(connection.longPollTimer);
}
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
this.sendJsonResponse(connection.longPollResponse, 200, {
success: true,
messages: [],
disconnected: true
});
}
this.httpConnections.delete(connectionId);
}
}
/**
* 清理所有资源
*/
private async cleanup(): Promise<void> {
// 清理所有连接
for (const connectionId of this.httpConnections.keys()) {
this.cleanupConnection(connectionId);
}
this.clearConnections();
// 关闭 HTTP 服务器
if (this.httpServer) {
await new Promise<void>((resolve) => {
this.httpServer!.close(() => resolve());
});
this.httpServer = null;
}
}
/**
* 获取 HTTP 连接统计信息
*/
getHttpStats(): {
totalConnections: number;
activeLongPolls: number;
queuedMessages: number;
} {
let activeLongPolls = 0;
let queuedMessages = 0;
for (const connection of this.httpConnections.values()) {
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
activeLongPolls++;
}
queuedMessages += connection.messageQueue.length;
}
return {
totalConnections: this.httpConnections.size,
activeLongPolls,
queuedMessages
};
}
}

View File

@@ -1,452 +0,0 @@
/**
* 网络服务器主类
*
* 整合 WebSocket 和 HTTP 传输,提供统一的网络服务接口
*/
import { EventEmitter } from 'events';
import { Transport, TransportConfig, TransportMessage } from './Transport';
import { WebSocketTransport, WebSocketTransportConfig } from './WebSocketTransport';
import { HttpTransport, HttpTransportConfig } from './HttpTransport';
import { ClientConnection, ClientConnectionState, ClientPermissions } from './ClientConnection';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 网络服务器配置
*/
export interface NetworkServerConfig {
/** 服务器名称 */
name?: string;
/** WebSocket 配置 */
websocket?: WebSocketTransportConfig;
/** HTTP 配置 */
http?: HttpTransportConfig;
/** 默认客户端权限 */
defaultPermissions?: ClientPermissions;
/** 最大客户端连接数 */
maxConnections?: number;
/** 客户端认证超时(毫秒) */
authenticationTimeout?: number;
/** 是否启用统计 */
enableStats?: boolean;
}
/**
* 服务器统计信息
*/
export interface ServerStats {
/** 总连接数 */
totalConnections: number;
/** 当前活跃连接数 */
activeConnections: number;
/** 已认证连接数 */
authenticatedConnections: number;
/** 消息总数 */
totalMessages: number;
/** 错误总数 */
totalErrors: number;
/** 服务器启动时间 */
startTime: Date;
/** 服务器运行时间(毫秒) */
uptime: number;
}
/**
* 网络服务器事件
*/
export interface NetworkServerEvents {
/** 服务器启动 */
'server-started': () => void;
/** 服务器停止 */
'server-stopped': () => void;
/** 客户端连接 */
'client-connected': (client: ClientConnection) => void;
/** 客户端断开连接 */
'client-disconnected': (clientId: string, reason?: string) => void;
/** 客户端认证成功 */
'client-authenticated': (client: ClientConnection) => void;
/** 收到消息 */
'message': (client: ClientConnection, message: TransportMessage) => void;
/** 服务器错误 */
'error': (error: Error, clientId?: string) => void;
}
/**
* 网络服务器主类
*/
export class NetworkServer extends EventEmitter {
private config: NetworkServerConfig;
private wsTransport: WebSocketTransport | null = null;
private httpTransport: HttpTransport | null = null;
private clients = new Map<string, ClientConnection>();
private isRunning = false;
private stats: ServerStats;
constructor(config: NetworkServerConfig) {
super();
this.config = {
name: 'NetworkServer',
maxConnections: 1000,
authenticationTimeout: 30000, // 30秒
enableStats: true,
defaultPermissions: {
canJoinRooms: true,
canCreateRooms: false,
canSendRpc: true,
canSyncVars: true
},
...config
};
this.stats = {
totalConnections: 0,
activeConnections: 0,
authenticatedConnections: 0,
totalMessages: 0,
totalErrors: 0,
startTime: new Date(),
uptime: 0
};
this.initialize();
}
/**
* 启动服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('Server is already running');
}
try {
const promises: Promise<void>[] = [];
// 启动 WebSocket 传输
if (this.config.websocket && this.wsTransport) {
promises.push(this.wsTransport.start());
}
// 启动 HTTP 传输
if (this.config.http && this.httpTransport) {
promises.push(this.httpTransport.start());
}
if (promises.length === 0) {
throw new Error('No transport configured. Please configure at least one transport (WebSocket or HTTP)');
}
await Promise.all(promises);
this.isRunning = true;
this.stats.startTime = new Date();
console.log(`Network Server "${this.config.name}" started successfully`);
if (this.config.websocket) {
console.log(`- WebSocket: ws://${this.config.websocket.host || 'localhost'}:${this.config.websocket.port}${this.config.websocket.path || '/ws'}`);
}
if (this.config.http) {
console.log(`- HTTP: http://${this.config.http.host || 'localhost'}:${this.config.http.port}${this.config.http.apiPrefix || '/api'}`);
}
this.emit('server-started');
} catch (error) {
await this.stop();
throw error;
}
}
/**
* 停止服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有客户端
const clients = Array.from(this.clients.values());
for (const client of clients) {
client.disconnect('server-shutdown');
}
// 停止传输层
const promises: Promise<void>[] = [];
if (this.wsTransport) {
promises.push(this.wsTransport.stop());
}
if (this.httpTransport) {
promises.push(this.httpTransport.stop());
}
await Promise.all(promises);
console.log(`Network Server "${this.config.name}" stopped`);
this.emit('server-stopped');
}
/**
* 获取服务器配置
*/
getConfig(): Readonly<NetworkServerConfig> {
return this.config;
}
/**
* 获取服务器统计信息
*/
getStats(): ServerStats {
this.stats.uptime = Date.now() - this.stats.startTime.getTime();
this.stats.activeConnections = this.clients.size;
this.stats.authenticatedConnections = Array.from(this.clients.values())
.filter(client => client.isAuthenticated).length;
return { ...this.stats };
}
/**
* 获取所有客户端连接
*/
getClients(): ClientConnection[] {
return Array.from(this.clients.values());
}
/**
* 获取指定客户端连接
*/
getClient(clientId: string): ClientConnection | undefined {
return this.clients.get(clientId);
}
/**
* 检查客户端是否存在
*/
hasClient(clientId: string): boolean {
return this.clients.has(clientId);
}
/**
* 获取客户端数量
*/
getClientCount(): number {
return this.clients.size;
}
/**
* 发送消息给指定客户端
*/
async sendToClient(clientId: string, message: TransportMessage): Promise<boolean> {
const client = this.clients.get(clientId);
if (!client) {
return false;
}
return await client.sendMessage(message);
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
const promises = Array.from(this.clients.entries())
.filter(([clientId]) => clientId !== excludeId)
.map(([, client]) => client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 发送消息给指定房间的所有客户端
*/
async broadcastToRoom(roomId: string, message: TransportMessage, excludeId?: string): Promise<number> {
const roomClients = Array.from(this.clients.values())
.filter(client => client.currentRoomId === roomId && client.id !== excludeId);
const promises = roomClients.map(client => client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(clientId: string, reason?: string): Promise<void> {
const client = this.clients.get(clientId);
if (client) {
client.disconnect(reason);
}
}
/**
* 获取在指定房间的客户端列表
*/
getClientsInRoom(roomId: string): ClientConnection[] {
return Array.from(this.clients.values())
.filter(client => client.currentRoomId === roomId);
}
/**
* 检查服务器是否正在运行
*/
isServerRunning(): boolean {
return this.isRunning;
}
/**
* 初始化服务器
*/
private initialize(): void {
// 初始化 WebSocket 传输
if (this.config.websocket) {
this.wsTransport = new WebSocketTransport(this.config.websocket);
this.setupTransportEvents(this.wsTransport);
}
// 初始化 HTTP 传输
if (this.config.http) {
this.httpTransport = new HttpTransport(this.config.http);
this.setupTransportEvents(this.httpTransport);
}
}
/**
* 设置传输层事件监听
*/
private setupTransportEvents(transport: Transport): void {
transport.on('client-connected', (connectionInfo) => {
this.handleClientConnected(connectionInfo.id, connectionInfo.remoteAddress || 'unknown', transport);
});
transport.on('client-disconnected', (connectionId, reason) => {
this.handleClientDisconnected(connectionId, reason);
});
transport.on('message', (connectionId, message) => {
this.handleTransportMessage(connectionId, message);
});
transport.on('error', (error, connectionId) => {
this.handleTransportError(error, connectionId);
});
}
/**
* 处理客户端连接
*/
private handleClientConnected(connectionId: string, remoteAddress: string, transport: Transport): void {
// 检查连接数限制
if (this.config.maxConnections && this.clients.size >= this.config.maxConnections) {
transport.disconnectClient(connectionId, 'Max connections reached');
return;
}
const client = new ClientConnection(
connectionId,
remoteAddress,
(message) => transport.sendToClient(connectionId, message),
{
connectionTimeout: this.config.authenticationTimeout,
permissions: this.config.defaultPermissions
}
);
// 设置客户端事件监听
this.setupClientEvents(client);
this.clients.set(connectionId, client);
this.stats.totalConnections++;
console.log(`Client connected: ${connectionId} from ${remoteAddress}`);
this.emit('client-connected', client);
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnected(connectionId: string, reason?: string): void {
const client = this.clients.get(connectionId);
if (client) {
client.destroy();
this.clients.delete(connectionId);
console.log(`Client disconnected: ${connectionId}, reason: ${reason || 'unknown'}`);
this.emit('client-disconnected', connectionId, reason);
}
}
/**
* 处理传输层消息
*/
private handleTransportMessage(connectionId: string, message: TransportMessage): void {
const client = this.clients.get(connectionId);
if (!client) {
return;
}
client.handleMessage(message);
this.stats.totalMessages++;
this.emit('message', client, message);
}
/**
* 处理传输层错误
*/
private handleTransportError(error: Error, connectionId?: string): void {
this.stats.totalErrors++;
console.error(`Transport error${connectionId ? ` (client: ${connectionId})` : ''}:`, error.message);
this.emit('error', error, connectionId);
// 如果是特定客户端的错误,断开该客户端
if (connectionId) {
this.disconnectClient(connectionId, 'transport-error');
}
}
/**
* 设置客户端事件监听
*/
private setupClientEvents(client: ClientConnection): void {
client.on('authenticated', (userData) => {
console.log(`Client authenticated: ${client.id}`, userData);
this.emit('client-authenticated', client);
});
client.on('error', (error) => {
console.error(`Client error (${client.id}):`, error.message);
this.emit('error', error, client.id);
});
client.on('timeout', () => {
console.log(`Client timeout: ${client.id}`);
this.disconnectClient(client.id, 'timeout');
});
client.on('state-changed', (oldState, newState) => {
console.log(`Client ${client.id} state changed: ${oldState} -> ${newState}`);
});
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof NetworkServerEvents>(event: K, listener: NetworkServerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof NetworkServerEvents>(event: K, ...args: Parameters<NetworkServerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,224 +0,0 @@
/**
* 网络传输层抽象接口
*/
import { EventEmitter } from 'events';
import { NetworkMessage, NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 传输层配置
*/
export interface TransportConfig {
/** 服务器端口 */
port: number;
/** 主机地址 */
host?: string;
/** 最大连接数 */
maxConnections?: number;
/** 心跳间隔(毫秒) */
heartbeatInterval?: number;
/** 连接超时(毫秒) */
connectionTimeout?: number;
}
/**
* 客户端连接信息
*/
export interface ClientConnectionInfo {
/** 连接ID */
id: string;
/** 客户端IP */
remoteAddress?: string;
/** 连接时间 */
connectedAt: Date;
/** 最后活跃时间 */
lastActivity: Date;
/** 用户数据 */
userData?: Record<string, NetworkValue>;
}
/**
* 网络消息包装
*/
export interface TransportMessage {
/** 消息类型 */
type: 'rpc' | 'syncvar' | 'system' | 'custom';
/** 消息数据 */
data: NetworkValue;
/** 发送者ID */
senderId?: string;
/** 目标客户端ID(可选,用于单播) */
targetId?: string;
/** 是否可靠传输 */
reliable?: boolean;
}
/**
* 网络传输层事件
*/
export interface TransportEvents {
/** 客户端连接 */
'client-connected': (connectionInfo: ClientConnectionInfo) => void;
/** 客户端断开连接 */
'client-disconnected': (connectionId: string, reason?: string) => void;
/** 收到消息 */
'message': (connectionId: string, message: TransportMessage) => void;
/** 传输错误 */
'error': (error: Error, connectionId?: string) => void;
/** 服务器启动 */
'server-started': (config: TransportConfig) => void;
/** 服务器关闭 */
'server-stopped': () => void;
}
/**
* 网络传输层抽象类
*/
export abstract class Transport extends EventEmitter {
protected config: TransportConfig;
protected isRunning = false;
protected connections = new Map<string, ClientConnectionInfo>();
constructor(config: TransportConfig) {
super();
this.config = config;
}
/**
* 启动传输层服务
*/
abstract start(): Promise<void>;
/**
* 停止传输层服务
*/
abstract stop(): Promise<void>;
/**
* 发送消息给指定客户端
*/
abstract sendToClient(connectionId: string, message: TransportMessage): Promise<boolean>;
/**
* 广播消息给所有客户端
*/
abstract broadcast(message: TransportMessage, excludeId?: string): Promise<number>;
/**
* 广播消息给指定客户端列表
*/
abstract sendToClients(connectionIds: string[], message: TransportMessage): Promise<number>;
/**
* 断开指定客户端连接
*/
abstract disconnectClient(connectionId: string, reason?: string): Promise<void>;
/**
* 获取在线客户端数量
*/
getConnectionCount(): number {
return this.connections.size;
}
/**
* 获取所有连接信息
*/
getConnections(): ClientConnectionInfo[] {
return Array.from(this.connections.values());
}
/**
* 获取指定连接信息
*/
getConnection(connectionId: string): ClientConnectionInfo | undefined {
return this.connections.get(connectionId);
}
/**
* 检查连接是否存在
*/
hasConnection(connectionId: string): boolean {
return this.connections.has(connectionId);
}
/**
* 服务器是否正在运行
*/
isServerRunning(): boolean {
return this.isRunning;
}
/**
* 获取传输层配置
*/
getConfig(): TransportConfig {
return { ...this.config };
}
/**
* 更新客户端最后活跃时间
*/
protected updateClientActivity(connectionId: string): void {
const connection = this.connections.get(connectionId);
if (connection) {
connection.lastActivity = new Date();
}
}
/**
* 添加客户端连接
*/
protected addConnection(connectionInfo: ClientConnectionInfo): void {
this.connections.set(connectionInfo.id, connectionInfo);
this.emit('client-connected', connectionInfo);
}
/**
* 移除客户端连接
*/
protected removeConnection(connectionId: string, reason?: string): void {
if (this.connections.delete(connectionId)) {
this.emit('client-disconnected', connectionId, reason);
}
}
/**
* 处理接收到的消息
*/
protected handleMessage(connectionId: string, message: TransportMessage): void {
this.updateClientActivity(connectionId);
this.emit('message', connectionId, message);
}
/**
* 处理传输错误
*/
protected handleError(error: Error, connectionId?: string): void {
this.emit('error', error, connectionId);
}
/**
* 清理所有连接
*/
protected clearConnections(): void {
const connectionIds = Array.from(this.connections.keys());
for (const id of connectionIds) {
this.removeConnection(id, 'server-shutdown');
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof TransportEvents>(event: K, listener: TransportEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof TransportEvents>(event: K, ...args: Parameters<TransportEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,406 +0,0 @@
/**
* WebSocket 传输层实现
*/
import { WebSocketServer, WebSocket } from 'ws';
import { createServer, Server as HttpServer } from 'http';
import { v4 as uuidv4 } from 'uuid';
import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport';
/**
* WebSocket 传输配置
*/
export interface WebSocketTransportConfig extends TransportConfig {
/** WebSocket 路径 */
path?: string;
/** 是否启用压缩 */
compression?: boolean;
/** 最大消息大小(字节) */
maxMessageSize?: number;
/** ping 间隔(毫秒) */
pingInterval?: number;
/** pong 超时(毫秒) */
pongTimeout?: number;
}
/**
* WebSocket 客户端连接扩展信息
*/
interface WebSocketConnectionInfo extends ClientConnectionInfo {
/** WebSocket 实例 */
socket: WebSocket;
/** ping 定时器 */
pingTimer?: NodeJS.Timeout;
/** pong 超时定时器 */
pongTimer?: NodeJS.Timeout;
}
/**
* WebSocket 传输层实现
*/
export class WebSocketTransport extends Transport {
private httpServer: HttpServer | null = null;
private wsServer: WebSocketServer | null = null;
private wsConnections = new Map<string, WebSocketConnectionInfo>();
protected override config: WebSocketTransportConfig;
constructor(config: WebSocketTransportConfig) {
super(config);
this.config = {
path: '/ws',
compression: true,
maxMessageSize: 1024 * 1024, // 1MB
pingInterval: 30000, // 30秒
pongTimeout: 5000, // 5秒
heartbeatInterval: 30000,
connectionTimeout: 60000,
maxConnections: 1000,
...config
};
}
/**
* 启动 WebSocket 服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('WebSocket transport is already running');
}
try {
// 创建 HTTP 服务器
this.httpServer = createServer();
// 创建 WebSocket 服务器
this.wsServer = new WebSocketServer({
server: this.httpServer,
path: this.config.path,
maxPayload: this.config.maxMessageSize,
perMessageDeflate: this.config.compression
});
// 设置事件监听
this.setupEventListeners();
// 启动服务器
await new Promise<void>((resolve, reject) => {
this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => {
if (error) {
reject(error);
} else {
this.isRunning = true;
resolve();
}
});
});
this.emit('server-started', this.config);
} catch (error) {
await this.cleanup();
throw error;
}
}
/**
* 停止 WebSocket 服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有客户端连接
for (const [connectionId, connection] of this.wsConnections) {
this.disconnectClient(connectionId, 'server-shutdown');
}
await this.cleanup();
this.emit('server-stopped');
}
/**
* 发送消息给指定客户端
*/
async sendToClient(connectionId: string, message: TransportMessage): Promise<boolean> {
const connection = this.wsConnections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
return false;
}
try {
const data = JSON.stringify(message);
connection.socket.send(data);
this.updateClientActivity(connectionId);
return true;
} catch (error) {
this.handleError(error as Error, connectionId);
return false;
}
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
const data = JSON.stringify(message);
let sentCount = 0;
for (const [connectionId, connection] of this.wsConnections) {
if (excludeId && connectionId === excludeId) {
continue;
}
if (connection.socket.readyState === WebSocket.OPEN) {
try {
connection.socket.send(data);
sentCount++;
} catch (error) {
this.handleError(error as Error, connectionId);
}
}
}
return sentCount;
}
/**
* 发送消息给指定客户端列表
*/
async sendToClients(connectionIds: string[], message: TransportMessage): Promise<number> {
const data = JSON.stringify(message);
let sentCount = 0;
for (const connectionId of connectionIds) {
const connection = this.wsConnections.get(connectionId);
if (connection && connection.socket.readyState === WebSocket.OPEN) {
try {
connection.socket.send(data);
sentCount++;
} catch (error) {
this.handleError(error as Error, connectionId);
}
}
}
return sentCount;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(connectionId: string, reason?: string): Promise<void> {
const connection = this.wsConnections.get(connectionId);
if (connection) {
this.cleanupConnection(connectionId);
connection.socket.close(1000, reason);
}
}
/**
* 设置事件监听器
*/
private setupEventListeners(): void {
if (!this.wsServer) return;
this.wsServer.on('connection', (socket: WebSocket, request) => {
this.handleNewConnection(socket, request);
});
this.wsServer.on('error', (error: Error) => {
this.handleError(error);
});
if (this.httpServer) {
this.httpServer.on('error', (error: Error) => {
this.handleError(error);
});
}
}
/**
* 处理新连接
*/
private handleNewConnection(socket: WebSocket, request: any): void {
// 检查连接数限制
if (this.config.maxConnections && this.wsConnections.size >= this.config.maxConnections) {
socket.close(1013, 'Too many connections');
return;
}
const connectionId = uuidv4();
const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown';
const connectionInfo: WebSocketConnectionInfo = {
id: connectionId,
socket,
remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress,
connectedAt: new Date(),
lastActivity: new Date(),
userData: {}
};
this.wsConnections.set(connectionId, connectionInfo);
this.addConnection(connectionInfo);
// 设置 socket 事件监听
socket.on('message', (data: Buffer) => {
this.handleClientMessage(connectionId, data);
});
socket.on('close', (code: number, reason: Buffer) => {
this.handleClientDisconnect(connectionId, code, reason.toString());
});
socket.on('error', (error: Error) => {
this.handleError(error, connectionId);
this.handleClientDisconnect(connectionId, 1006, 'Socket error');
});
socket.on('pong', () => {
this.handlePong(connectionId);
});
// 启动心跳检测
this.startHeartbeat(connectionId);
}
/**
* 处理客户端消息
*/
private handleClientMessage(connectionId: string, data: Buffer): void {
try {
const message = JSON.parse(data.toString()) as TransportMessage;
message.senderId = connectionId;
this.handleMessage(connectionId, message);
} catch (error) {
this.handleError(new Error(`Invalid message format from client ${connectionId}`), connectionId);
}
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnect(connectionId: string, code: number, reason: string): void {
this.cleanupConnection(connectionId);
this.removeConnection(connectionId, `${code}: ${reason}`);
}
/**
* 启动心跳检测
*/
private startHeartbeat(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (!connection) return;
if (this.config.pingInterval && this.config.pingInterval > 0) {
connection.pingTimer = setInterval(() => {
this.sendPing(connectionId);
}, this.config.pingInterval);
}
}
/**
* 发送 ping
*/
private sendPing(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
return;
}
connection.socket.ping();
// 设置 pong 超时
if (this.config.pongTimeout && this.config.pongTimeout > 0) {
if (connection.pongTimer) {
clearTimeout(connection.pongTimer);
}
connection.pongTimer = setTimeout(() => {
this.disconnectClient(connectionId, 'Pong timeout');
}, this.config.pongTimeout);
}
}
/**
* 处理 pong 响应
*/
private handlePong(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (connection && connection.pongTimer) {
clearTimeout(connection.pongTimer);
connection.pongTimer = undefined;
}
this.updateClientActivity(connectionId);
}
/**
* 清理连接资源
*/
private cleanupConnection(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (connection) {
if (connection.pingTimer) {
clearInterval(connection.pingTimer);
}
if (connection.pongTimer) {
clearTimeout(connection.pongTimer);
}
this.wsConnections.delete(connectionId);
}
}
/**
* 清理所有资源
*/
private async cleanup(): Promise<void> {
// 清理所有连接
for (const connectionId of this.wsConnections.keys()) {
this.cleanupConnection(connectionId);
}
this.clearConnections();
// 关闭 WebSocket 服务器
if (this.wsServer) {
this.wsServer.close();
this.wsServer = null;
}
// 关闭 HTTP 服务器
if (this.httpServer) {
await new Promise<void>((resolve) => {
this.httpServer!.close(() => resolve());
});
this.httpServer = null;
}
}
/**
* 获取 WebSocket 连接统计信息
*/
getWebSocketStats(): {
totalConnections: number;
activeConnections: number;
inactiveConnections: number;
} {
let activeConnections = 0;
let inactiveConnections = 0;
for (const connection of this.wsConnections.values()) {
if (connection.socket.readyState === WebSocket.OPEN) {
activeConnections++;
} else {
inactiveConnections++;
}
}
return {
totalConnections: this.wsConnections.size,
activeConnections,
inactiveConnections
};
}
}

View File

@@ -1,9 +0,0 @@
/**
* 核心模块导出
*/
export * from './Transport';
export * from './WebSocketTransport';
export * from './HttpTransport';
export * from './ClientConnection';
export * from './NetworkServer';

View File

@@ -1,79 +1,26 @@
/**
* ECS Framework Network Server
*
* 提供完整的网络服务端功能,包括:
* - WebSocket 和 HTTP 传输层
* - 客户端连接管理
* - 房间系统
* - 身份验证和权限管理
* - SyncVar 和 RPC 系统
* - 消息验证
* @esengine/network-server
* ECS Framework网络层 - 服务端实现
*/
// 核心模块
export * from './core';
// 核心服务器 (待实现)
// export * from './core/NetworkServer';
// export * from './core/ClientConnection';
// 房间系统
export * from './rooms';
// 传输层 (待实现)
// export * from './transport/WebSocketTransport';
// export * from './transport/HttpTransport';
// 认证系统
export * from './auth';
// 系统层 (待实现)
// export * from './systems/SyncVarSystem';
// export * from './systems/RpcSystem';
// 网络系统
export * from './systems';
// 房间管理 (待实现)
// export * from './rooms/Room';
// export * from './rooms/RoomManager';
// 验证系统
export * from './validation';
// 认证授权 (待实现)
// export * from './auth/AuthManager';
// 版本信息
export const VERSION = '1.0.0';
// 导出常用组合配置
export interface ServerConfigPreset {
/** 服务器名称 */
name: string;
/** WebSocket 端口 */
wsPort: number;
/** HTTP 端口(可选) */
httpPort?: number;
/** 最大连接数 */
maxConnections: number;
/** 是否启用认证 */
enableAuth: boolean;
/** 是否启用房间系统 */
enableRooms: boolean;
}
/**
* 预定义服务器配置
*/
export const ServerPresets = {
/** 开发环境配置 */
Development: {
name: 'Development Server',
wsPort: 8080,
httpPort: 3000,
maxConnections: 100,
enableAuth: false,
enableRooms: true
} as ServerConfigPreset,
/** 生产环境配置 */
Production: {
name: 'Production Server',
wsPort: 443,
httpPort: 80,
maxConnections: 10000,
enableAuth: true,
enableRooms: true
} as ServerConfigPreset,
/** 测试环境配置 */
Testing: {
name: 'Test Server',
wsPort: 9090,
maxConnections: 10,
enableAuth: false,
enableRooms: false
} as ServerConfigPreset
};
// 重新导出shared包的类型
export * from '@esengine/network-shared';

View File

@@ -1,637 +0,0 @@
/**
* 房间管理
*
* 类似于 Unity Mirror 的 Scene 概念,管理一组客户端和网络对象
*/
import { EventEmitter } from 'events';
import { Entity, Scene } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { TransportMessage } from '../core/Transport';
/**
* 房间状态
*/
export enum RoomState {
/** 创建中 */
CREATING = 'creating',
/** 活跃状态 */
ACTIVE = 'active',
/** 暂停状态 */
PAUSED = 'paused',
/** 关闭中 */
CLOSING = 'closing',
/** 已关闭 */
CLOSED = 'closed'
}
/**
* 房间配置
*/
export interface RoomConfig {
/** 房间ID */
id: string;
/** 房间名称 */
name: string;
/** 房间描述 */
description?: string;
/** 最大玩家数 */
maxPlayers: number;
/** 是否私有房间 */
isPrivate?: boolean;
/** 房间密码 */
password?: string;
/** 房间元数据 */
metadata?: Record<string, NetworkValue>;
/** 是否持久化 */
persistent?: boolean;
/** 房间过期时间(毫秒) */
expirationTime?: number;
}
/**
* 玩家数据
*/
export interface PlayerData {
/** 客户端连接 */
client: ClientConnection;
/** 加入时间 */
joinedAt: Date;
/** 是否为房主 */
isOwner: boolean;
/** 玩家自定义数据 */
customData: Record<string, NetworkValue>;
}
/**
* 房间统计信息
*/
export interface RoomStats {
/** 当前玩家数 */
currentPlayers: number;
/** 最大玩家数 */
maxPlayers: number;
/** 总加入过的玩家数 */
totalPlayersJoined: number;
/** 消息总数 */
totalMessages: number;
/** 创建时间 */
createdAt: Date;
/** 房间存活时间(毫秒) */
lifetime: number;
}
/**
* 房间事件
*/
export interface RoomEvents {
/** 玩家加入 */
'player-joined': (player: PlayerData) => void;
/** 玩家离开 */
'player-left': (clientId: string, reason?: string) => void;
/** 房主变更 */
'owner-changed': (newOwnerId: string, oldOwnerId?: string) => void;
/** 房间状态变化 */
'state-changed': (oldState: RoomState, newState: RoomState) => void;
/** 收到消息 */
'message': (clientId: string, message: TransportMessage) => void;
/** 房间更新 */
'room-updated': (updatedFields: Partial<RoomConfig>) => void;
/** 房间错误 */
'error': (error: Error, clientId?: string) => void;
/** 房间即将关闭 */
'closing': (reason: string) => void;
/** 房间已关闭 */
'closed': (reason: string) => void;
}
/**
* 房间类
*/
export class Room extends EventEmitter {
private config: RoomConfig;
private state: RoomState = RoomState.CREATING;
private players = new Map<string, PlayerData>();
private ownerId: string | null = null;
private ecsScene: Scene | null = null;
private stats: RoomStats;
private expirationTimer: NodeJS.Timeout | null = null;
constructor(config: RoomConfig) {
super();
this.config = { ...config };
this.stats = {
currentPlayers: 0,
maxPlayers: config.maxPlayers,
totalPlayersJoined: 0,
totalMessages: 0,
createdAt: new Date(),
lifetime: 0
};
this.initialize();
}
/**
* 获取房间ID
*/
get id(): string {
return this.config.id;
}
/**
* 获取房间名称
*/
get name(): string {
return this.config.name;
}
/**
* 获取房间状态
*/
get currentState(): RoomState {
return this.state;
}
/**
* 获取房间配置
*/
getConfig(): Readonly<RoomConfig> {
return this.config;
}
/**
* 获取房间统计信息
*/
getStats(): RoomStats {
this.stats.lifetime = Date.now() - this.stats.createdAt.getTime();
this.stats.currentPlayers = this.players.size;
return { ...this.stats };
}
/**
* 获取所有玩家
*/
getPlayers(): PlayerData[] {
return Array.from(this.players.values());
}
/**
* 获取指定玩家
*/
getPlayer(clientId: string): PlayerData | undefined {
return this.players.get(clientId);
}
/**
* 检查玩家是否在房间中
*/
hasPlayer(clientId: string): boolean {
return this.players.has(clientId);
}
/**
* 获取当前玩家数量
*/
getPlayerCount(): number {
return this.players.size;
}
/**
* 检查房间是否已满
*/
isFull(): boolean {
return this.players.size >= this.config.maxPlayers;
}
/**
* 检查房间是否为空
*/
isEmpty(): boolean {
return this.players.size === 0;
}
/**
* 获取房主
*/
getOwner(): PlayerData | undefined {
return this.ownerId ? this.players.get(this.ownerId) : undefined;
}
/**
* 获取 ECS 场景
*/
getEcsScene(): Scene | null {
return this.ecsScene;
}
/**
* 玩家加入房间
*/
async addPlayer(client: ClientConnection, customData: Record<string, NetworkValue> = {}): Promise<boolean> {
if (this.state !== RoomState.ACTIVE) {
throw new Error(`Cannot join room in state: ${this.state}`);
}
if (this.hasPlayer(client.id)) {
throw new Error(`Player ${client.id} is already in the room`);
}
if (this.isFull()) {
throw new Error('Room is full');
}
// 检查房间密码
if (this.config.isPrivate && this.config.password) {
const providedPassword = customData.password as string;
if (providedPassword !== this.config.password) {
throw new Error('Invalid room password');
}
}
const isFirstPlayer = this.isEmpty();
const playerData: PlayerData = {
client,
joinedAt: new Date(),
isOwner: isFirstPlayer,
customData: { ...customData }
};
this.players.set(client.id, playerData);
client.joinRoom(this.id);
// 设置房主
if (isFirstPlayer) {
this.ownerId = client.id;
}
this.stats.totalPlayersJoined++;
// 通知其他玩家
await this.broadcast({
type: 'system',
data: {
action: 'player-joined',
playerId: client.id,
playerData: {
id: client.id,
joinedAt: playerData.joinedAt.toISOString(),
isOwner: playerData.isOwner,
customData: playerData.customData
}
}
}, client.id);
console.log(`Player ${client.id} joined room ${this.id}`);
this.emit('player-joined', playerData);
return true;
}
/**
* 玩家离开房间
*/
async removePlayer(clientId: string, reason?: string): Promise<boolean> {
const player = this.players.get(clientId);
if (!player) {
return false;
}
this.players.delete(clientId);
player.client.leaveRoom();
// 如果离开的是房主,转移房主权限
if (this.ownerId === clientId) {
await this.transferOwnership();
}
// 通知其他玩家
await this.broadcast({
type: 'system',
data: {
action: 'player-left',
playerId: clientId,
reason: reason || 'unknown'
}
});
console.log(`Player ${clientId} left room ${this.id}, reason: ${reason || 'unknown'}`);
this.emit('player-left', clientId, reason);
// 如果房间为空,考虑关闭
if (this.isEmpty() && !this.config.persistent) {
await this.close('empty-room');
}
return true;
}
/**
* 转移房主权限
*/
async transferOwnership(newOwnerId?: string): Promise<boolean> {
const oldOwnerId = this.ownerId;
if (newOwnerId) {
const newOwner = this.players.get(newOwnerId);
if (!newOwner) {
return false;
}
this.ownerId = newOwnerId;
newOwner.isOwner = true;
} else {
// 自动选择下一个玩家作为房主
const players = Array.from(this.players.values());
if (players.length > 0) {
const newOwner = players[0];
this.ownerId = newOwner.client.id;
newOwner.isOwner = true;
} else {
this.ownerId = null;
}
}
// 更新旧房主状态
if (oldOwnerId) {
const oldOwner = this.players.get(oldOwnerId);
if (oldOwner) {
oldOwner.isOwner = false;
}
}
// 通知所有玩家房主变更
if (this.ownerId) {
await this.broadcast({
type: 'system',
data: {
action: 'owner-changed',
newOwnerId: this.ownerId,
oldOwnerId: oldOwnerId || ''
}
});
console.log(`Room ${this.id} ownership transferred from ${oldOwnerId || 'none'} to ${this.ownerId}`);
this.emit('owner-changed', this.ownerId, oldOwnerId || undefined);
}
return true;
}
/**
* 广播消息给房间内所有玩家
*/
async broadcast(message: TransportMessage, excludeClientId?: string): Promise<number> {
const players = Array.from(this.players.values())
.filter(player => player.client.id !== excludeClientId);
const promises = players.map(player => player.client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 发送消息给指定玩家
*/
async sendToPlayer(clientId: string, message: TransportMessage): Promise<boolean> {
const player = this.players.get(clientId);
if (!player) {
return false;
}
return await player.client.sendMessage(message);
}
/**
* 处理玩家消息
*/
async handleMessage(clientId: string, message: TransportMessage): Promise<void> {
if (!this.hasPlayer(clientId)) {
return;
}
this.stats.totalMessages++;
this.emit('message', clientId, message);
// 根据消息类型进行处理
switch (message.type) {
case 'rpc':
await this.handleRpcMessage(clientId, message);
break;
case 'syncvar':
await this.handleSyncVarMessage(clientId, message);
break;
case 'system':
await this.handleSystemMessage(clientId, message);
break;
default:
// 转发自定义消息
await this.broadcast(message, clientId);
break;
}
}
/**
* 更新房间配置
*/
async updateConfig(updates: Partial<RoomConfig>): Promise<void> {
// 验证更新
if (updates.maxPlayers !== undefined && updates.maxPlayers < this.players.size) {
throw new Error('Cannot reduce maxPlayers below current player count');
}
const oldConfig = { ...this.config };
Object.assign(this.config, updates);
// 通知所有玩家房间更新
await this.broadcast({
type: 'system',
data: {
action: 'room-updated',
updates
}
});
this.emit('room-updated', updates);
}
/**
* 暂停房间
*/
async pause(): Promise<void> {
if (this.state === RoomState.ACTIVE) {
this.setState(RoomState.PAUSED);
await this.broadcast({
type: 'system',
data: {
action: 'room-paused'
}
});
}
}
/**
* 恢复房间
*/
async resume(): Promise<void> {
if (this.state === RoomState.PAUSED) {
this.setState(RoomState.ACTIVE);
await this.broadcast({
type: 'system',
data: {
action: 'room-resumed'
}
});
}
}
/**
* 关闭房间
*/
async close(reason: string = 'server-shutdown'): Promise<void> {
if (this.state === RoomState.CLOSED || this.state === RoomState.CLOSING) {
return;
}
this.setState(RoomState.CLOSING);
this.emit('closing', reason);
// 通知所有玩家房间即将关闭
await this.broadcast({
type: 'system',
data: {
action: 'room-closing',
reason
}
});
// 移除所有玩家
const playerIds = Array.from(this.players.keys());
for (const clientId of playerIds) {
await this.removePlayer(clientId, 'room-closed');
}
this.cleanup();
this.setState(RoomState.CLOSED);
console.log(`Room ${this.id} closed, reason: ${reason}`);
this.emit('closed', reason);
}
/**
* 初始化房间
*/
private initialize(): void {
// 创建 ECS 场景
this.ecsScene = new Scene();
// 设置过期定时器
if (this.config.expirationTime && this.config.expirationTime > 0) {
this.expirationTimer = setTimeout(() => {
this.close('expired');
}, this.config.expirationTime);
}
this.setState(RoomState.ACTIVE);
}
/**
* 处理 RPC 消息
*/
private async handleRpcMessage(clientId: string, message: TransportMessage): Promise<void> {
// RPC 消息处理逻辑
// 这里可以添加权限检查、速率限制等
await this.broadcast(message, clientId);
}
/**
* 处理 SyncVar 消息
*/
private async handleSyncVarMessage(clientId: string, message: TransportMessage): Promise<void> {
// SyncVar 消息处理逻辑
// 这里可以添加权限检查、数据验证等
await this.broadcast(message, clientId);
}
/**
* 处理系统消息
*/
private async handleSystemMessage(clientId: string, message: TransportMessage): Promise<void> {
const data = message.data as any;
switch (data.action) {
case 'request-ownership':
// 处理房主权限转移请求
if (this.ownerId === clientId) {
await this.transferOwnership(data.newOwnerId);
}
break;
// 其他系统消息处理...
}
}
/**
* 设置房间状态
*/
private setState(newState: RoomState): void {
const oldState = this.state;
if (oldState !== newState) {
this.state = newState;
this.emit('state-changed', oldState, newState);
}
}
/**
* 清理资源
*/
private cleanup(): void {
if (this.expirationTimer) {
clearTimeout(this.expirationTimer);
this.expirationTimer = null;
}
this.removeAllListeners();
if (this.ecsScene) {
this.ecsScene = null;
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof RoomEvents>(event: K, listener: RoomEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof RoomEvents>(event: K, ...args: Parameters<RoomEvents[K]>): boolean {
return super.emit(event, ...args);
}
/**
* 序列化房间信息
*/
toJSON(): object {
return {
id: this.id,
name: this.name,
state: this.state,
config: this.config,
stats: this.getStats(),
players: this.getPlayers().map(player => ({
id: player.client.id,
joinedAt: player.joinedAt.toISOString(),
isOwner: player.isOwner,
customData: player.customData
})),
ownerId: this.ownerId
};
}
}

View File

@@ -1,499 +0,0 @@
/**
* 房间管理器
*
* 管理所有房间的创建、销毁、查找等操作
*/
import { EventEmitter } from 'events';
import { Room, RoomConfig, RoomState, PlayerData } from './Room';
import { ClientConnection } from '../core/ClientConnection';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 房间管理器配置
*/
export interface RoomManagerConfig {
/** 最大房间数量 */
maxRooms?: number;
/** 默认房间过期时间(毫秒) */
defaultExpirationTime?: number;
/** 是否启用房间统计 */
enableStats?: boolean;
/** 房间清理间隔(毫秒) */
cleanupInterval?: number;
}
/**
* 房间查询选项
*/
export interface RoomQueryOptions {
/** 房间名称模糊搜索 */
namePattern?: string;
/** 房间状态过滤 */
state?: RoomState;
/** 是否私有房间 */
isPrivate?: boolean;
/** 最小空位数 */
minAvailableSlots?: number;
/** 最大空位数 */
maxAvailableSlots?: number;
/** 元数据过滤 */
metadata?: Record<string, NetworkValue>;
/** 限制结果数量 */
limit?: number;
/** 跳过条数 */
offset?: number;
}
/**
* 房间管理器统计信息
*/
export interface RoomManagerStats {
/** 总房间数 */
totalRooms: number;
/** 活跃房间数 */
activeRooms: number;
/** 总玩家数 */
totalPlayers: number;
/** 私有房间数 */
privateRooms: number;
/** 持久化房间数 */
persistentRooms: number;
/** 创建的房间总数 */
roomsCreated: number;
/** 关闭的房间总数 */
roomsClosed: number;
}
/**
* 房间管理器事件
*/
export interface RoomManagerEvents {
/** 房间创建 */
'room-created': (room: Room) => void;
/** 房间关闭 */
'room-closed': (roomId: string, reason: string) => void;
/** 玩家加入房间 */
'player-joined-room': (roomId: string, player: PlayerData) => void;
/** 玩家离开房间 */
'player-left-room': (roomId: string, clientId: string, reason?: string) => void;
/** 房间管理器错误 */
'error': (error: Error, roomId?: string) => void;
}
/**
* 房间管理器
*/
export class RoomManager extends EventEmitter {
private config: RoomManagerConfig;
private rooms = new Map<string, Room>();
private stats: RoomManagerStats;
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: RoomManagerConfig = {}) {
super();
this.config = {
maxRooms: 1000,
defaultExpirationTime: 0, // 0 = 不过期
enableStats: true,
cleanupInterval: 60000, // 1分钟
...config
};
this.stats = {
totalRooms: 0,
activeRooms: 0,
totalPlayers: 0,
privateRooms: 0,
persistentRooms: 0,
roomsCreated: 0,
roomsClosed: 0
};
this.initialize();
}
/**
* 获取房间管理器配置
*/
getConfig(): Readonly<RoomManagerConfig> {
return this.config;
}
/**
* 获取房间管理器统计信息
*/
getStats(): RoomManagerStats {
this.updateStats();
return { ...this.stats };
}
/**
* 创建房间
*/
async createRoom(config: RoomConfig, creatorClient?: ClientConnection): Promise<Room> {
// 检查房间数量限制
if (this.config.maxRooms && this.rooms.size >= this.config.maxRooms) {
throw new Error('Maximum number of rooms reached');
}
// 检查房间ID是否已存在
if (this.rooms.has(config.id)) {
throw new Error(`Room with id "${config.id}" already exists`);
}
// 应用默认过期时间
const roomConfig: RoomConfig = {
expirationTime: this.config.defaultExpirationTime,
...config
};
const room = new Room(roomConfig);
// 设置房间事件监听
this.setupRoomEvents(room);
this.rooms.set(room.id, room);
this.stats.roomsCreated++;
console.log(`Room created: ${room.id} by ${creatorClient?.id || 'system'}`);
this.emit('room-created', room);
// 如果有创建者,自动加入房间
if (creatorClient) {
try {
await room.addPlayer(creatorClient);
} catch (error) {
console.error(`Failed to add creator to room ${room.id}:`, error);
}
}
return room;
}
/**
* 获取房间
*/
getRoom(roomId: string): Room | undefined {
return this.rooms.get(roomId);
}
/**
* 检查房间是否存在
*/
hasRoom(roomId: string): boolean {
return this.rooms.has(roomId);
}
/**
* 获取所有房间
*/
getAllRooms(): Room[] {
return Array.from(this.rooms.values());
}
/**
* 查询房间
*/
findRooms(options: RoomQueryOptions = {}): Room[] {
let rooms = Array.from(this.rooms.values());
// 状态过滤
if (options.state !== undefined) {
rooms = rooms.filter(room => room.currentState === options.state);
}
// 私有房间过滤
if (options.isPrivate !== undefined) {
rooms = rooms.filter(room => room.getConfig().isPrivate === options.isPrivate);
}
// 名称模糊搜索
if (options.namePattern) {
const pattern = options.namePattern.toLowerCase();
rooms = rooms.filter(room =>
room.getConfig().name.toLowerCase().includes(pattern)
);
}
// 空位数过滤
if (options.minAvailableSlots !== undefined) {
rooms = rooms.filter(room => {
const available = room.getConfig().maxPlayers - room.getPlayerCount();
return available >= options.minAvailableSlots!;
});
}
if (options.maxAvailableSlots !== undefined) {
rooms = rooms.filter(room => {
const available = room.getConfig().maxPlayers - room.getPlayerCount();
return available <= options.maxAvailableSlots!;
});
}
// 元数据过滤
if (options.metadata) {
rooms = rooms.filter(room => {
const roomMetadata = room.getConfig().metadata || {};
return Object.entries(options.metadata!).every(([key, value]) =>
roomMetadata[key] === value
);
});
}
// 排序(按创建时间,最新的在前)
rooms.sort((a, b) =>
b.getStats().createdAt.getTime() - a.getStats().createdAt.getTime()
);
// 分页
const offset = options.offset || 0;
const limit = options.limit || rooms.length;
return rooms.slice(offset, offset + limit);
}
/**
* 关闭房间
*/
async closeRoom(roomId: string, reason: string = 'manual'): Promise<boolean> {
const room = this.rooms.get(roomId);
if (!room) {
return false;
}
try {
await room.close(reason);
return true;
} catch (error) {
this.emit('error', error as Error, roomId);
return false;
}
}
/**
* 玩家加入房间
*/
async joinRoom(
roomId: string,
client: ClientConnection,
customData: Record<string, NetworkValue> = {}
): Promise<boolean> {
const room = this.rooms.get(roomId);
if (!room) {
throw new Error(`Room "${roomId}" not found`);
}
try {
return await room.addPlayer(client, customData);
} catch (error) {
this.emit('error', error as Error, roomId);
throw error;
}
}
/**
* 玩家离开房间
*/
async leaveRoom(roomId: string, clientId: string, reason?: string): Promise<boolean> {
const room = this.rooms.get(roomId);
if (!room) {
return false;
}
try {
return await room.removePlayer(clientId, reason);
} catch (error) {
this.emit('error', error as Error, roomId);
return false;
}
}
/**
* 玩家离开所有房间
*/
async leaveAllRooms(clientId: string, reason?: string): Promise<number> {
let leftCount = 0;
for (const room of this.rooms.values()) {
if (room.hasPlayer(clientId)) {
try {
await room.removePlayer(clientId, reason);
leftCount++;
} catch (error) {
console.error(`Error removing player ${clientId} from room ${room.id}:`, error);
}
}
}
return leftCount;
}
/**
* 获取玩家所在的房间
*/
getPlayerRooms(clientId: string): Room[] {
return Array.from(this.rooms.values())
.filter(room => room.hasPlayer(clientId));
}
/**
* 获取房间数量
*/
getRoomCount(): number {
return this.rooms.size;
}
/**
* 获取总玩家数量
*/
getTotalPlayerCount(): number {
return Array.from(this.rooms.values())
.reduce((total, room) => total + room.getPlayerCount(), 0);
}
/**
* 清理空闲房间
*/
async cleanupRooms(): Promise<number> {
let cleanedCount = 0;
const now = Date.now();
for (const room of this.rooms.values()) {
const config = room.getConfig();
const stats = room.getStats();
// 清理条件:
// 1. 非持久化的空房间
// 2. 已过期的房间
// 3. 已关闭的房间
let shouldClean = false;
let reason = '';
if (room.currentState === RoomState.CLOSED) {
shouldClean = true;
reason = 'room-closed';
} else if (!config.persistent && room.isEmpty()) {
shouldClean = true;
reason = 'empty-room';
} else if (config.expirationTime && config.expirationTime > 0) {
const expireTime = stats.createdAt.getTime() + config.expirationTime;
if (now >= expireTime) {
shouldClean = true;
reason = 'expired';
}
}
if (shouldClean) {
try {
if (room.currentState !== RoomState.CLOSED) {
await room.close(reason);
}
this.rooms.delete(room.id);
cleanedCount++;
console.log(`Cleaned up room: ${room.id}, reason: ${reason}`);
} catch (error) {
console.error(`Error cleaning up room ${room.id}:`, error);
}
}
}
return cleanedCount;
}
/**
* 关闭所有房间
*/
async closeAllRooms(reason: string = 'shutdown'): Promise<void> {
const rooms = Array.from(this.rooms.values());
const promises = rooms.map(room => room.close(reason));
await Promise.allSettled(promises);
this.rooms.clear();
console.log(`Closed ${rooms.length} rooms, reason: ${reason}`);
}
/**
* 销毁房间管理器
*/
async destroy(): Promise<void> {
// 停止清理定时器
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
// 关闭所有房间
await this.closeAllRooms('manager-destroyed');
// 移除所有事件监听器
this.removeAllListeners();
}
/**
* 初始化房间管理器
*/
private initialize(): void {
// 启动清理定时器
if (this.config.cleanupInterval && this.config.cleanupInterval > 0) {
this.cleanupTimer = setInterval(() => {
this.cleanupRooms().catch(error => {
console.error('Error during room cleanup:', error);
});
}, this.config.cleanupInterval);
}
}
/**
* 设置房间事件监听
*/
private setupRoomEvents(room: Room): void {
room.on('player-joined', (player) => {
this.emit('player-joined-room', room.id, player);
});
room.on('player-left', (clientId, reason) => {
this.emit('player-left-room', room.id, clientId, reason);
});
room.on('closed', (reason) => {
this.rooms.delete(room.id);
this.stats.roomsClosed++;
console.log(`Room ${room.id} removed from manager, reason: ${reason}`);
this.emit('room-closed', room.id, reason);
});
room.on('error', (error) => {
this.emit('error', error, room.id);
});
}
/**
* 更新统计信息
*/
private updateStats(): void {
this.stats.totalRooms = this.rooms.size;
this.stats.activeRooms = Array.from(this.rooms.values())
.filter(room => room.currentState === RoomState.ACTIVE).length;
this.stats.totalPlayers = this.getTotalPlayerCount();
this.stats.privateRooms = Array.from(this.rooms.values())
.filter(room => room.getConfig().isPrivate).length;
this.stats.persistentRooms = Array.from(this.rooms.values())
.filter(room => room.getConfig().persistent).length;
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof RoomManagerEvents>(event: K, listener: RoomManagerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof RoomManagerEvents>(event: K, ...args: Parameters<RoomManagerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,6 +0,0 @@
/**
* 房间系统导出
*/
export * from './Room';
export * from './RoomManager';

View File

@@ -1,762 +0,0 @@
/**
* RPC 系统
*
* 处理服务端的 RPC 调用、权限验证、参数验证等
*/
import { EventEmitter } from 'events';
import { v4 as uuidv4 } from 'uuid';
import {
NetworkValue,
RpcMetadata
} from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { Room } from '../rooms/Room';
import { TransportMessage } from '../core/Transport';
/**
* RPC 调用记录
*/
export interface RpcCall {
/** 调用ID */
id: string;
/** 网络对象ID */
networkId: number;
/** 组件类型 */
componentType: string;
/** 方法名 */
methodName: string;
/** 参数 */
parameters: NetworkValue[];
/** 元数据 */
metadata: RpcMetadata;
/** 发送者客户端ID */
senderId: string;
/** 目标客户端IDs用于 ClientRpc */
targetClientIds?: string[];
/** 是否需要响应 */
requiresResponse: boolean;
/** 时间戳 */
timestamp: Date;
/** 过期时间 */
expiresAt?: Date;
}
/**
* RPC 响应
*/
export interface RpcResponse {
/** 调用ID */
callId: string;
/** 是否成功 */
success: boolean;
/** 返回值 */
result?: NetworkValue;
/** 错误信息 */
error?: string;
/** 错误代码 */
errorCode?: string;
/** 时间戳 */
timestamp: Date;
}
/**
* RPC 系统配置
*/
export interface RpcSystemConfig {
/** RPC 调用超时时间(毫秒) */
callTimeout?: number;
/** 最大并发 RPC 调用数 */
maxConcurrentCalls?: number;
/** 是否启用权限检查 */
enablePermissionCheck?: boolean;
/** 是否启用参数验证 */
enableParameterValidation?: boolean;
/** 是否启用频率限制 */
enableRateLimit?: boolean;
/** 最大 RPC 频率(调用/秒) */
maxRpcRate?: number;
/** 单个参数最大大小(字节) */
maxParameterSize?: number;
}
/**
* RPC 系统事件
*/
export interface RpcSystemEvents {
/** ClientRpc 调用 */
'client-rpc-called': (call: RpcCall) => void;
/** ServerRpc 调用 */
'server-rpc-called': (call: RpcCall) => void;
/** RPC 调用完成 */
'rpc-completed': (call: RpcCall, response?: RpcResponse) => void;
/** RPC 调用超时 */
'rpc-timeout': (callId: string) => void;
/** 权限验证失败 */
'permission-denied': (clientId: string, call: RpcCall) => void;
/** 参数验证失败 */
'parameter-validation-failed': (clientId: string, call: RpcCall, reason: string) => void;
/** 频率限制触发 */
'rate-limit-exceeded': (clientId: string) => void;
/** RPC 错误 */
'rpc-error': (error: Error, callId?: string, clientId?: string) => void;
}
/**
* 客户端 RPC 状态
*/
interface ClientRpcState {
/** 客户端ID */
clientId: string;
/** 活跃的调用 */
activeCalls: Map<string, RpcCall>;
/** RPC 调用计数 */
rpcCount: number;
/** 频率重置时间 */
rateResetTime: Date;
}
/**
* 待处理的 RPC 响应
*/
interface PendingRpcResponse {
/** 调用信息 */
call: RpcCall;
/** 超时定时器 */
timeoutTimer: NodeJS.Timeout;
/** 响应回调 */
responseCallback: (response: RpcResponse) => void;
}
/**
* RPC 系统
*/
export class RpcSystem extends EventEmitter {
private config: RpcSystemConfig;
private clientStates = new Map<string, ClientRpcState>();
private pendingCalls = new Map<string, PendingRpcResponse>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: RpcSystemConfig = {}) {
super();
this.config = {
callTimeout: 30000, // 30秒
maxConcurrentCalls: 10,
enablePermissionCheck: true,
enableParameterValidation: true,
enableRateLimit: true,
maxRpcRate: 30, // 30次/秒
maxParameterSize: 65536, // 64KB
...config
};
this.initialize();
}
/**
* 处理 ClientRpc 调用
*/
async handleClientRpcCall(
client: ClientConnection,
message: TransportMessage,
room: Room
): Promise<void> {
try {
const data = message.data as any;
const {
networkId,
componentType,
methodName,
parameters = [],
metadata,
targetFilter = 'all'
} = data;
// 创建 RPC 调用记录
const rpcCall: RpcCall = {
id: uuidv4(),
networkId,
componentType,
methodName,
parameters,
metadata,
senderId: client.id,
requiresResponse: metadata?.requiresResponse || false,
timestamp: new Date()
};
// 权限检查
if (this.config.enablePermissionCheck) {
if (!this.checkRpcPermission(client, rpcCall, 'client-rpc')) {
this.emit('permission-denied', client.id, rpcCall);
return;
}
}
// 频率限制检查
if (this.config.enableRateLimit && !this.checkRpcRate(client.id)) {
this.emit('rate-limit-exceeded', client.id);
return;
}
// 参数验证
if (this.config.enableParameterValidation) {
const validationResult = this.validateRpcParameters(rpcCall);
if (!validationResult.valid) {
this.emit('parameter-validation-failed', client.id, rpcCall, validationResult.reason!);
return;
}
}
// 确定目标客户端
const targetClientIds = this.getClientRpcTargets(room, client.id, targetFilter);
rpcCall.targetClientIds = targetClientIds;
// 记录活跃调用
this.recordActiveCall(client.id, rpcCall);
// 触发事件
this.emit('client-rpc-called', rpcCall);
// 发送到目标客户端
await this.sendClientRpc(room, rpcCall, targetClientIds);
// 如果不需要响应,立即标记完成
if (!rpcCall.requiresResponse) {
this.completeRpcCall(rpcCall);
}
} catch (error) {
this.emit('rpc-error', error as Error, undefined, client.id);
}
}
/**
* 处理 ServerRpc 调用
*/
async handleServerRpcCall(
client: ClientConnection,
message: TransportMessage,
room: Room
): Promise<void> {
try {
const data = message.data as any;
const {
networkId,
componentType,
methodName,
parameters = [],
metadata
} = data;
// 创建 RPC 调用记录
const rpcCall: RpcCall = {
id: uuidv4(),
networkId,
componentType,
methodName,
parameters,
metadata,
senderId: client.id,
requiresResponse: metadata?.requiresResponse || false,
timestamp: new Date()
};
// 权限检查
if (this.config.enablePermissionCheck) {
if (!this.checkRpcPermission(client, rpcCall, 'server-rpc')) {
this.emit('permission-denied', client.id, rpcCall);
return;
}
}
// 频率限制检查
if (this.config.enableRateLimit && !this.checkRpcRate(client.id)) {
this.emit('rate-limit-exceeded', client.id);
return;
}
// 参数验证
if (this.config.enableParameterValidation) {
const validationResult = this.validateRpcParameters(rpcCall);
if (!validationResult.valid) {
this.emit('parameter-validation-failed', client.id, rpcCall, validationResult.reason!);
return;
}
}
// 记录活跃调用
this.recordActiveCall(client.id, rpcCall);
// 触发事件
this.emit('server-rpc-called', rpcCall);
// ServerRpc 在服务端执行,这里需要实际的执行逻辑
// 在实际使用中,应该通过事件或回调来执行具体的方法
const response = await this.executeServerRpc(rpcCall);
// 发送响应(如果需要)
if (rpcCall.requiresResponse && response) {
await this.sendRpcResponse(client, response);
}
this.completeRpcCall(rpcCall, response || undefined);
} catch (error) {
this.emit('rpc-error', error as Error, undefined, client.id);
// 发送错误响应
if (message.data && (message.data as any).requiresResponse) {
const errorResponse: RpcResponse = {
callId: (message.data as any).callId || uuidv4(),
success: false,
error: (error as Error).message,
errorCode: 'EXECUTION_ERROR',
timestamp: new Date()
};
await this.sendRpcResponse(client, errorResponse);
}
}
}
/**
* 处理 RPC 响应
*/
async handleRpcResponse(
client: ClientConnection,
message: TransportMessage
): Promise<void> {
try {
const response = message.data as any as RpcResponse;
const pendingCall = this.pendingCalls.get(response.callId);
if (pendingCall) {
// 清除超时定时器
clearTimeout(pendingCall.timeoutTimer);
this.pendingCalls.delete(response.callId);
// 调用响应回调
pendingCall.responseCallback(response);
// 完成调用
this.completeRpcCall(pendingCall.call, response);
}
} catch (error) {
this.emit('rpc-error', error as Error, undefined, client.id);
}
}
/**
* 调用 ClientRpc从服务端向客户端发送
*/
async callClientRpc(
room: Room,
networkId: number,
componentType: string,
methodName: string,
parameters: NetworkValue[] = [],
options: {
targetFilter?: 'all' | 'others' | 'owner' | string[];
requiresResponse?: boolean;
timeout?: number;
} = {}
): Promise<RpcResponse[]> {
const rpcCall: RpcCall = {
id: uuidv4(),
networkId,
componentType,
methodName,
parameters,
metadata: {
methodName,
rpcType: 'client-rpc',
requiresAuth: false,
reliable: true,
requiresResponse: options.requiresResponse || false
},
senderId: 'server',
requiresResponse: options.requiresResponse || false,
timestamp: new Date()
};
// 确定目标客户端
const targetClientIds = typeof options.targetFilter === 'string'
? this.getClientRpcTargets(room, 'server', options.targetFilter)
: options.targetFilter || [];
rpcCall.targetClientIds = targetClientIds;
// 发送到目标客户端
await this.sendClientRpc(room, rpcCall, targetClientIds);
// 如果需要响应,等待响应
if (options.requiresResponse) {
return await this.waitForRpcResponses(rpcCall, targetClientIds, options.timeout);
}
this.completeRpcCall(rpcCall);
return [];
}
/**
* 获取客户端统计信息
*/
getClientRpcStats(clientId: string): {
activeCalls: number;
totalCalls: number;
} {
const state = this.clientStates.get(clientId);
return {
activeCalls: state?.activeCalls.size || 0,
totalCalls: state?.rpcCount || 0
};
}
/**
* 取消所有客户端的 RPC 调用
*/
cancelClientRpcs(clientId: string): number {
const state = this.clientStates.get(clientId);
if (!state) {
return 0;
}
const cancelledCount = state.activeCalls.size;
// 取消所有活跃调用
for (const call of state.activeCalls.values()) {
this.completeRpcCall(call);
}
state.activeCalls.clear();
return cancelledCount;
}
/**
* 销毁 RPC 系统
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
// 清除所有待处理的调用
for (const pending of this.pendingCalls.values()) {
clearTimeout(pending.timeoutTimer);
}
this.clientStates.clear();
this.pendingCalls.clear();
this.removeAllListeners();
}
/**
* 初始化系统
*/
private initialize(): void {
// 启动清理定时器(每分钟清理一次)
this.cleanupTimer = setInterval(() => {
this.cleanup();
}, 60000);
}
/**
* 检查 RPC 权限
*/
private checkRpcPermission(
client: ClientConnection,
call: RpcCall,
rpcType: 'client-rpc' | 'server-rpc'
): boolean {
// 基本权限检查
if (!client.hasPermission('canSendRpc')) {
return false;
}
// ServerRpc 额外权限检查
if (rpcType === 'server-rpc' && call.metadata.requiresAuth) {
if (!client.isAuthenticated) {
return false;
}
}
// 可以添加更多特定的权限检查逻辑
return true;
}
/**
* 检查 RPC 频率
*/
private checkRpcRate(clientId: string): boolean {
if (!this.config.maxRpcRate || this.config.maxRpcRate <= 0) {
return true;
}
const now = new Date();
let state = this.clientStates.get(clientId);
if (!state) {
state = {
clientId,
activeCalls: new Map(),
rpcCount: 1,
rateResetTime: new Date(now.getTime() + 1000)
};
this.clientStates.set(clientId, state);
return true;
}
// 检查是否需要重置计数
if (now >= state.rateResetTime) {
state.rpcCount = 1;
state.rateResetTime = new Date(now.getTime() + 1000);
return true;
}
// 检查频率限制
if (state.rpcCount >= this.config.maxRpcRate) {
return false;
}
state.rpcCount++;
return true;
}
/**
* 验证 RPC 参数
*/
private validateRpcParameters(call: RpcCall): { valid: boolean; reason?: string } {
// 检查参数数量
if (call.parameters.length > 10) {
return { valid: false, reason: 'Too many parameters' };
}
// 检查每个参数的大小
for (let i = 0; i < call.parameters.length; i++) {
const param = call.parameters[i];
try {
const serialized = JSON.stringify(param);
if (serialized.length > this.config.maxParameterSize!) {
return { valid: false, reason: `Parameter ${i} is too large` };
}
} catch (error) {
return { valid: false, reason: `Parameter ${i} is not serializable` };
}
}
return { valid: true };
}
/**
* 获取 ClientRpc 目标客户端
*/
private getClientRpcTargets(
room: Room,
senderId: string,
targetFilter: string
): string[] {
const players = room.getPlayers();
switch (targetFilter) {
case 'all':
return players.map(p => p.client.id);
case 'others':
return players
.filter(p => p.client.id !== senderId)
.map(p => p.client.id);
case 'owner':
const owner = room.getOwner();
return owner ? [owner.client.id] : [];
default:
return [];
}
}
/**
* 发送 ClientRpc
*/
private async sendClientRpc(
room: Room,
call: RpcCall,
targetClientIds: string[]
): Promise<void> {
const message: TransportMessage = {
type: 'rpc',
data: {
action: 'client-rpc',
callId: call.id,
networkId: call.networkId,
componentType: call.componentType,
methodName: call.methodName,
parameters: call.parameters,
metadata: call.metadata as any,
requiresResponse: call.requiresResponse,
timestamp: call.timestamp.getTime()
} as any
};
// 发送给目标客户端
const promises = targetClientIds.map(clientId =>
room.sendToPlayer(clientId, message)
);
await Promise.allSettled(promises);
}
/**
* 执行 ServerRpc
*/
private async executeServerRpc(call: RpcCall): Promise<RpcResponse | null> {
// 这里应该是实际的服务端方法执行逻辑
// 在实际实现中,可能需要通过事件或回调来执行具体的方法
// 示例响应
const response: RpcResponse = {
callId: call.id,
success: true,
result: undefined, // 实际执行结果
timestamp: new Date()
};
return response;
}
/**
* 发送 RPC 响应
*/
private async sendRpcResponse(
client: ClientConnection,
response: RpcResponse
): Promise<void> {
const message: TransportMessage = {
type: 'rpc',
data: {
action: 'rpc-response',
...response
} as any
};
await client.sendMessage(message);
}
/**
* 等待 RPC 响应
*/
private async waitForRpcResponses(
call: RpcCall,
targetClientIds: string[],
timeout?: number
): Promise<RpcResponse[]> {
return new Promise((resolve) => {
const responses: RpcResponse[] = [];
const responseTimeout = timeout || this.config.callTimeout!;
let responseCount = 0;
const responseCallback = (response: RpcResponse) => {
responses.push(response);
responseCount++;
// 如果收到所有响应立即resolve
if (responseCount >= targetClientIds.length) {
resolve(responses);
}
};
// 设置超时
const timeoutTimer = setTimeout(() => {
resolve(responses); // 返回已收到的响应
this.emit('rpc-timeout', call.id);
}, responseTimeout);
// 注册待处理的响应
this.pendingCalls.set(call.id, {
call,
timeoutTimer,
responseCallback
});
});
}
/**
* 记录活跃调用
*/
private recordActiveCall(clientId: string, call: RpcCall): void {
let state = this.clientStates.get(clientId);
if (!state) {
state = {
clientId,
activeCalls: new Map(),
rpcCount: 0,
rateResetTime: new Date()
};
this.clientStates.set(clientId, state);
}
state.activeCalls.set(call.id, call);
}
/**
* 完成 RPC 调用
*/
private completeRpcCall(call: RpcCall, response?: RpcResponse): void {
// 从活跃调用中移除
const state = this.clientStates.get(call.senderId);
if (state) {
state.activeCalls.delete(call.id);
}
// 触发完成事件
this.emit('rpc-completed', call, response);
}
/**
* 清理过期的调用和状态
*/
private cleanup(): void {
const now = new Date();
let cleanedCalls = 0;
let cleanedStates = 0;
// 清理过期的待处理调用
for (const [callId, pending] of this.pendingCalls.entries()) {
if (pending.call.expiresAt && pending.call.expiresAt < now) {
clearTimeout(pending.timeoutTimer);
this.pendingCalls.delete(callId);
cleanedCalls++;
}
}
// 清理空的客户端状态
for (const [clientId, state] of this.clientStates.entries()) {
if (state.activeCalls.size === 0 &&
now.getTime() - state.rateResetTime.getTime() > 60000) {
this.clientStates.delete(clientId);
cleanedStates++;
}
}
if (cleanedCalls > 0 || cleanedStates > 0) {
console.log(`RPC cleanup: ${cleanedCalls} calls, ${cleanedStates} states`);
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof RpcSystemEvents>(event: K, listener: RpcSystemEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof RpcSystemEvents>(event: K, ...args: Parameters<RpcSystemEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,587 +0,0 @@
/**
* SyncVar 同步系统
*
* 处理服务端的 SyncVar 同步逻辑、权限验证、数据传播等
*/
import { EventEmitter } from 'events';
import {
NetworkValue,
SyncVarMetadata,
NetworkSerializer
} from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { Room } from '../rooms/Room';
import { TransportMessage } from '../core/Transport';
/**
* SyncVar 更改记录
*/
export interface SyncVarChange {
/** 网络对象ID */
networkId: number;
/** 组件类型 */
componentType: string;
/** 属性名 */
propertyName: string;
/** 旧值 */
oldValue: NetworkValue;
/** 新值 */
newValue: NetworkValue;
/** 元数据 */
metadata: SyncVarMetadata;
/** 发送者客户端ID */
senderId: string;
/** 时间戳 */
timestamp: Date;
}
/**
* SyncVar 同步配置
*/
export interface SyncVarSystemConfig {
/** 批量同步间隔(毫秒) */
batchInterval?: number;
/** 单次批量最大数量 */
maxBatchSize?: number;
/** 是否启用增量同步 */
enableDeltaSync?: boolean;
/** 是否启用权限检查 */
enablePermissionCheck?: boolean;
/** 是否启用数据验证 */
enableDataValidation?: boolean;
/** 最大同步频率(次/秒) */
maxSyncRate?: number;
}
/**
* 网络对象状态
*/
export interface NetworkObjectState {
/** 网络对象ID */
networkId: number;
/** 拥有者客户端ID */
ownerId: string;
/** 组件状态 */
components: Map<string, Map<string, NetworkValue>>;
/** 最后更新时间 */
lastUpdateTime: Date;
/** 权威状态 */
hasAuthority: boolean;
}
/**
* SyncVar 系统事件
*/
export interface SyncVarSystemEvents {
/** SyncVar 值变化 */
'syncvar-changed': (change: SyncVarChange) => void;
/** 同步批次完成 */
'batch-synced': (changes: SyncVarChange[], targetClients: string[]) => void;
/** 权限验证失败 */
'permission-denied': (clientId: string, change: SyncVarChange) => void;
/** 数据验证失败 */
'validation-failed': (clientId: string, change: SyncVarChange, reason: string) => void;
/** 同步错误 */
'sync-error': (error: Error, clientId?: string) => void;
}
/**
* 客户端同步状态
*/
interface ClientSyncState {
/** 客户端ID */
clientId: string;
/** 待同步的变化列表 */
pendingChanges: SyncVarChange[];
/** 最后同步时间 */
lastSyncTime: Date;
/** 同步频率限制 */
syncCount: number;
/** 频率重置时间 */
rateResetTime: Date;
}
/**
* SyncVar 同步系统
*/
export class SyncVarSystem extends EventEmitter {
private config: SyncVarSystemConfig;
private networkObjects = new Map<number, NetworkObjectState>();
private clientSyncStates = new Map<string, ClientSyncState>();
private serializer: NetworkSerializer;
private batchTimer: NodeJS.Timeout | null = null;
constructor(config: SyncVarSystemConfig = {}) {
super();
this.config = {
batchInterval: 50, // 50ms批量间隔
maxBatchSize: 100,
enableDeltaSync: true,
enablePermissionCheck: true,
enableDataValidation: true,
maxSyncRate: 60, // 60次/秒
...config
};
this.serializer = new NetworkSerializer();
this.initialize();
}
/**
* 注册网络对象
*/
registerNetworkObject(
networkId: number,
ownerId: string,
hasAuthority: boolean = true
): void {
if (this.networkObjects.has(networkId)) {
console.warn(`Network object ${networkId} is already registered`);
return;
}
const networkObject: NetworkObjectState = {
networkId,
ownerId,
components: new Map(),
lastUpdateTime: new Date(),
hasAuthority
};
this.networkObjects.set(networkId, networkObject);
console.log(`Network object registered: ${networkId} owned by ${ownerId}`);
}
/**
* 注销网络对象
*/
unregisterNetworkObject(networkId: number): boolean {
const removed = this.networkObjects.delete(networkId);
if (removed) {
console.log(`Network object unregistered: ${networkId}`);
}
return removed;
}
/**
* 获取网络对象
*/
getNetworkObject(networkId: number): NetworkObjectState | undefined {
return this.networkObjects.get(networkId);
}
/**
* 处理 SyncVar 变化消息
*/
async handleSyncVarChange(
client: ClientConnection,
message: TransportMessage,
room?: Room
): Promise<void> {
try {
const data = message.data as any;
const {
networkId,
componentType,
propertyName,
oldValue,
newValue,
metadata
} = data;
// 创建变化记录
const change: SyncVarChange = {
networkId,
componentType,
propertyName,
oldValue,
newValue,
metadata,
senderId: client.id,
timestamp: new Date()
};
// 权限检查
if (this.config.enablePermissionCheck) {
if (!this.checkSyncVarPermission(client, change)) {
this.emit('permission-denied', client.id, change);
return;
}
}
// 频率限制检查
if (!this.checkSyncRate(client.id)) {
console.warn(`SyncVar rate limit exceeded for client ${client.id}`);
return;
}
// 数据验证
if (this.config.enableDataValidation) {
const validationResult = this.validateSyncVarData(change);
if (!validationResult.valid) {
this.emit('validation-failed', client.id, change, validationResult.reason!);
return;
}
}
// 更新网络对象状态
this.updateNetworkObjectState(change);
// 触发变化事件
this.emit('syncvar-changed', change);
// 添加到待同步列表
if (room) {
this.addToBatchSync(change, room);
}
} catch (error) {
this.emit('sync-error', error as Error, client.id);
}
}
/**
* 获取网络对象的完整状态
*/
getNetworkObjectSnapshot(networkId: number): Record<string, any> | null {
const networkObject = this.networkObjects.get(networkId);
if (!networkObject) {
return null;
}
const snapshot: Record<string, any> = {};
for (const [componentType, componentData] of networkObject.components) {
snapshot[componentType] = {};
for (const [propertyName, value] of componentData) {
snapshot[componentType][propertyName] = value;
}
}
return snapshot;
}
/**
* 向客户端发送网络对象快照
*/
async sendNetworkObjectSnapshot(
client: ClientConnection,
networkId: number
): Promise<boolean> {
const snapshot = this.getNetworkObjectSnapshot(networkId);
if (!snapshot) {
return false;
}
const message: TransportMessage = {
type: 'syncvar',
data: {
action: 'snapshot',
networkId,
snapshot
}
};
return await client.sendMessage(message);
}
/**
* 同步所有网络对象给新客户端
*/
async syncAllNetworkObjects(client: ClientConnection, room: Room): Promise<number> {
let syncedCount = 0;
for (const networkObject of this.networkObjects.values()) {
// 检查客户端是否有权限看到这个网络对象
if (this.canClientSeeNetworkObject(client.id, networkObject)) {
const success = await this.sendNetworkObjectSnapshot(client, networkObject.networkId);
if (success) {
syncedCount++;
}
}
}
console.log(`Synced ${syncedCount} network objects to client ${client.id}`);
return syncedCount;
}
/**
* 设置网络对象拥有者
*/
setNetworkObjectOwner(networkId: number, newOwnerId: string): boolean {
const networkObject = this.networkObjects.get(networkId);
if (!networkObject) {
return false;
}
const oldOwnerId = networkObject.ownerId;
networkObject.ownerId = newOwnerId;
networkObject.lastUpdateTime = new Date();
console.log(`Network object ${networkId} ownership changed: ${oldOwnerId} -> ${newOwnerId}`);
return true;
}
/**
* 获取网络对象拥有者
*/
getNetworkObjectOwner(networkId: number): string | undefined {
const networkObject = this.networkObjects.get(networkId);
return networkObject?.ownerId;
}
/**
* 销毁 SyncVar 系统
*/
destroy(): void {
if (this.batchTimer) {
clearInterval(this.batchTimer);
this.batchTimer = null;
}
this.networkObjects.clear();
this.clientSyncStates.clear();
this.removeAllListeners();
}
/**
* 初始化系统
*/
private initialize(): void {
// 启动批量同步定时器
if (this.config.batchInterval && this.config.batchInterval > 0) {
this.batchTimer = setInterval(() => {
this.processBatchSync();
}, this.config.batchInterval);
}
}
/**
* 检查 SyncVar 权限
*/
private checkSyncVarPermission(client: ClientConnection, change: SyncVarChange): boolean {
// 检查客户端是否有网络同步权限
if (!client.hasPermission('canSyncVars')) {
return false;
}
// 获取网络对象
const networkObject = this.networkObjects.get(change.networkId);
if (!networkObject) {
return false;
}
// 检查权威权限
if (change.metadata.authorityOnly) {
// 只有网络对象拥有者或有权威权限的客户端可以修改
return networkObject.ownerId === client.id || networkObject.hasAuthority;
}
return true;
}
/**
* 检查同步频率
*/
private checkSyncRate(clientId: string): boolean {
if (!this.config.maxSyncRate || this.config.maxSyncRate <= 0) {
return true;
}
const now = new Date();
let syncState = this.clientSyncStates.get(clientId);
if (!syncState) {
syncState = {
clientId,
pendingChanges: [],
lastSyncTime: now,
syncCount: 1,
rateResetTime: new Date(now.getTime() + 1000) // 1秒后重置
};
this.clientSyncStates.set(clientId, syncState);
return true;
}
// 检查是否需要重置计数
if (now >= syncState.rateResetTime) {
syncState.syncCount = 1;
syncState.rateResetTime = new Date(now.getTime() + 1000);
return true;
}
// 检查频率限制
if (syncState.syncCount >= this.config.maxSyncRate) {
return false;
}
syncState.syncCount++;
return true;
}
/**
* 验证 SyncVar 数据
*/
private validateSyncVarData(change: SyncVarChange): { valid: boolean; reason?: string } {
// 基本类型检查
if (change.newValue === null || change.newValue === undefined) {
return { valid: false, reason: 'Value cannot be null or undefined' };
}
// 检查数据大小(防止过大的数据)
try {
const serialized = JSON.stringify(change.newValue);
if (serialized.length > 65536) { // 64KB限制
return { valid: false, reason: 'Data too large' };
}
} catch (error) {
return { valid: false, reason: 'Data is not serializable' };
}
// 可以添加更多特定的验证逻辑
return { valid: true };
}
/**
* 更新网络对象状态
*/
private updateNetworkObjectState(change: SyncVarChange): void {
let networkObject = this.networkObjects.get(change.networkId);
if (!networkObject) {
// 如果网络对象不存在,创建一个新的(可能是客户端创建的)
networkObject = {
networkId: change.networkId,
ownerId: change.senderId,
components: new Map(),
lastUpdateTime: new Date(),
hasAuthority: true
};
this.networkObjects.set(change.networkId, networkObject);
}
// 获取或创建组件数据
let componentData = networkObject.components.get(change.componentType);
if (!componentData) {
componentData = new Map();
networkObject.components.set(change.componentType, componentData);
}
// 更新属性值
componentData.set(change.propertyName, change.newValue);
networkObject.lastUpdateTime = change.timestamp;
}
/**
* 添加到批量同步
*/
private addToBatchSync(change: SyncVarChange, room: Room): void {
// 获取房间内需要同步的客户端
const roomPlayers = room.getPlayers();
const targetClientIds = roomPlayers
.filter(player => player.client.id !== change.senderId) // 不发送给发送者
.map(player => player.client.id);
// 为每个目标客户端添加变化记录
for (const clientId of targetClientIds) {
let syncState = this.clientSyncStates.get(clientId);
if (!syncState) {
syncState = {
clientId,
pendingChanges: [],
lastSyncTime: new Date(),
syncCount: 0,
rateResetTime: new Date()
};
this.clientSyncStates.set(clientId, syncState);
}
syncState.pendingChanges.push(change);
}
}
/**
* 处理批量同步
*/
private async processBatchSync(): Promise<void> {
const syncPromises: Promise<void>[] = [];
for (const [clientId, syncState] of this.clientSyncStates.entries()) {
if (syncState.pendingChanges.length === 0) {
continue;
}
// 获取要同步的变化(限制批量大小)
const changesToSync = syncState.pendingChanges.splice(
0,
this.config.maxBatchSize
);
if (changesToSync.length > 0) {
syncPromises.push(this.sendBatchChanges(clientId, changesToSync));
}
}
if (syncPromises.length > 0) {
await Promise.allSettled(syncPromises);
}
}
/**
* 发送批量变化
*/
private async sendBatchChanges(clientId: string, changes: SyncVarChange[]): Promise<void> {
try {
// 这里需要获取客户端连接,实际实现中可能需要从外部传入
// 为了简化,这里假设有一个方法可以获取客户端连接
// 实际使用时,可能需要通过回调或事件来发送消息
const message: TransportMessage = {
type: 'syncvar',
data: {
action: 'batch-update',
changes: changes.map(change => ({
networkId: change.networkId,
componentType: change.componentType,
propertyName: change.propertyName,
newValue: change.newValue,
metadata: change.metadata as any,
timestamp: change.timestamp.getTime()
}))
} as any
};
// 这里需要实际的发送逻辑
// 在实际使用中,应该通过事件或回调来发送消息
this.emit('batch-synced', changes, [clientId]);
} catch (error) {
this.emit('sync-error', error as Error, clientId);
}
}
/**
* 检查客户端是否可以看到网络对象
*/
private canClientSeeNetworkObject(clientId: string, networkObject: NetworkObjectState): boolean {
// 基本实现:客户端可以看到自己拥有的对象和公共对象
// 实际实现中可能需要更复杂的可见性逻辑
return true;
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof SyncVarSystemEvents>(event: K, listener: SyncVarSystemEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof SyncVarSystemEvents>(event: K, ...args: Parameters<SyncVarSystemEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,6 +0,0 @@
/**
* 系统模块导出
*/
export * from './SyncVarSystem';
export * from './RpcSystem';

View File

@@ -1,572 +0,0 @@
/**
* 消息验证器
*
* 验证网络消息的格式、大小、内容等
*/
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { TransportMessage } from '../core/Transport';
/**
* 验证结果
*/
export interface ValidationResult {
/** 是否有效 */
valid: boolean;
/** 错误信息 */
error?: string;
/** 错误代码 */
errorCode?: string;
/** 详细信息 */
details?: Record<string, any>;
}
/**
* 验证配置
*/
export interface ValidationConfig {
/** 最大消息大小(字节) */
maxMessageSize?: number;
/** 最大数组长度 */
maxArrayLength?: number;
/** 最大对象深度 */
maxObjectDepth?: number;
/** 最大字符串长度 */
maxStringLength?: number;
/** 允许的消息类型 */
allowedMessageTypes?: string[];
/** 是否允许null值 */
allowNullValues?: boolean;
/** 是否允许undefined值 */
allowUndefinedValues?: boolean;
}
/**
* 验证规则
*/
export interface ValidationRule {
/** 规则名称 */
name: string;
/** 验证函数 */
validate: (value: any, context: ValidationContext) => ValidationResult;
/** 是否必需 */
required?: boolean;
}
/**
* 验证上下文
*/
export interface ValidationContext {
/** 当前路径 */
path: string[];
/** 当前深度 */
depth: number;
/** 配置 */
config: ValidationConfig;
/** 消息类型 */
messageType?: string;
}
/**
* 消息验证器
*/
export class MessageValidator {
private config: ValidationConfig;
private customRules = new Map<string, ValidationRule>();
constructor(config: ValidationConfig = {}) {
this.config = {
maxMessageSize: 1024 * 1024, // 1MB
maxArrayLength: 1000,
maxObjectDepth: 10,
maxStringLength: 10000,
allowedMessageTypes: ['rpc', 'syncvar', 'system', 'custom'],
allowNullValues: true,
allowUndefinedValues: false,
...config
};
}
/**
* 验证传输消息
*/
validateMessage(message: TransportMessage): ValidationResult {
try {
// 基本结构验证
const structureResult = this.validateMessageStructure(message);
if (!structureResult.valid) {
return structureResult;
}
// 消息大小验证
const sizeResult = this.validateMessageSize(message);
if (!sizeResult.valid) {
return sizeResult;
}
// 消息类型验证
const typeResult = this.validateMessageType(message);
if (!typeResult.valid) {
return typeResult;
}
// 数据内容验证
const dataResult = this.validateMessageData(message);
if (!dataResult.valid) {
return dataResult;
}
// 自定义规则验证
const customResult = this.validateCustomRules(message);
if (!customResult.valid) {
return customResult;
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: (error as Error).message,
errorCode: 'VALIDATION_ERROR'
};
}
}
/**
* 验证网络值
*/
validateNetworkValue(value: NetworkValue, context?: Partial<ValidationContext>): ValidationResult {
const fullContext: ValidationContext = {
path: [],
depth: 0,
config: this.config,
...context
};
return this.validateValue(value, fullContext);
}
/**
* 添加自定义验证规则
*/
addValidationRule(rule: ValidationRule): void {
this.customRules.set(rule.name, rule);
}
/**
* 移除自定义验证规则
*/
removeValidationRule(ruleName: string): boolean {
return this.customRules.delete(ruleName);
}
/**
* 获取所有自定义规则
*/
getCustomRules(): ValidationRule[] {
return Array.from(this.customRules.values());
}
/**
* 验证消息结构
*/
private validateMessageStructure(message: TransportMessage): ValidationResult {
// 检查必需字段
if (!message.type) {
return {
valid: false,
error: 'Message type is required',
errorCode: 'MISSING_TYPE'
};
}
if (message.data === undefined) {
return {
valid: false,
error: 'Message data is required',
errorCode: 'MISSING_DATA'
};
}
// 检查字段类型
if (typeof message.type !== 'string') {
return {
valid: false,
error: 'Message type must be a string',
errorCode: 'INVALID_TYPE_FORMAT'
};
}
// 检查可选字段
if (message.senderId && typeof message.senderId !== 'string') {
return {
valid: false,
error: 'Sender ID must be a string',
errorCode: 'INVALID_SENDER_ID'
};
}
if (message.targetId && typeof message.targetId !== 'string') {
return {
valid: false,
error: 'Target ID must be a string',
errorCode: 'INVALID_TARGET_ID'
};
}
if (message.reliable !== undefined && typeof message.reliable !== 'boolean') {
return {
valid: false,
error: 'Reliable flag must be a boolean',
errorCode: 'INVALID_RELIABLE_FLAG'
};
}
return { valid: true };
}
/**
* 验证消息大小
*/
private validateMessageSize(message: TransportMessage): ValidationResult {
try {
const serialized = JSON.stringify(message);
const size = new TextEncoder().encode(serialized).length;
if (size > this.config.maxMessageSize!) {
return {
valid: false,
error: `Message size (${size} bytes) exceeds maximum (${this.config.maxMessageSize} bytes)`,
errorCode: 'MESSAGE_TOO_LARGE',
details: { actualSize: size, maxSize: this.config.maxMessageSize }
};
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: 'Failed to serialize message for size validation',
errorCode: 'SERIALIZATION_ERROR'
};
}
}
/**
* 验证消息类型
*/
private validateMessageType(message: TransportMessage): ValidationResult {
if (!this.config.allowedMessageTypes!.includes(message.type)) {
return {
valid: false,
error: `Message type '${message.type}' is not allowed`,
errorCode: 'INVALID_MESSAGE_TYPE',
details: {
messageType: message.type,
allowedTypes: this.config.allowedMessageTypes
}
};
}
return { valid: true };
}
/**
* 验证消息数据
*/
private validateMessageData(message: TransportMessage): ValidationResult {
const context: ValidationContext = {
path: ['data'],
depth: 0,
config: this.config,
messageType: message.type
};
return this.validateValue(message.data, context);
}
/**
* 验证值
*/
private validateValue(value: any, context: ValidationContext): ValidationResult {
// 深度检查
if (context.depth > this.config.maxObjectDepth!) {
return {
valid: false,
error: `Object depth (${context.depth}) exceeds maximum (${this.config.maxObjectDepth})`,
errorCode: 'OBJECT_TOO_DEEP',
details: { path: context.path.join('.'), depth: context.depth }
};
}
// null/undefined 检查
if (value === null) {
if (!this.config.allowNullValues) {
return {
valid: false,
error: 'Null values are not allowed',
errorCode: 'NULL_NOT_ALLOWED',
details: { path: context.path.join('.') }
};
}
return { valid: true };
}
if (value === undefined) {
if (!this.config.allowUndefinedValues) {
return {
valid: false,
error: 'Undefined values are not allowed',
errorCode: 'UNDEFINED_NOT_ALLOWED',
details: { path: context.path.join('.') }
};
}
return { valid: true };
}
// 根据类型验证
switch (typeof value) {
case 'string':
return this.validateString(value, context);
case 'number':
return this.validateNumber(value, context);
case 'boolean':
return { valid: true };
case 'object':
if (Array.isArray(value)) {
return this.validateArray(value, context);
} else {
return this.validateObject(value, context);
}
default:
return {
valid: false,
error: `Unsupported value type: ${typeof value}`,
errorCode: 'UNSUPPORTED_TYPE',
details: { path: context.path.join('.'), type: typeof value }
};
}
}
/**
* 验证字符串
*/
private validateString(value: string, context: ValidationContext): ValidationResult {
if (value.length > this.config.maxStringLength!) {
return {
valid: false,
error: `String length (${value.length}) exceeds maximum (${this.config.maxStringLength})`,
errorCode: 'STRING_TOO_LONG',
details: {
path: context.path.join('.'),
actualLength: value.length,
maxLength: this.config.maxStringLength
}
};
}
return { valid: true };
}
/**
* 验证数字
*/
private validateNumber(value: number, context: ValidationContext): ValidationResult {
// 检查是否为有效数字
if (!Number.isFinite(value)) {
return {
valid: false,
error: 'Number must be finite',
errorCode: 'INVALID_NUMBER',
details: { path: context.path.join('.'), value }
};
}
return { valid: true };
}
/**
* 验证数组
*/
private validateArray(value: any[], context: ValidationContext): ValidationResult {
// 长度检查
if (value.length > this.config.maxArrayLength!) {
return {
valid: false,
error: `Array length (${value.length}) exceeds maximum (${this.config.maxArrayLength})`,
errorCode: 'ARRAY_TOO_LONG',
details: {
path: context.path.join('.'),
actualLength: value.length,
maxLength: this.config.maxArrayLength
}
};
}
// 验证每个元素
for (let i = 0; i < value.length; i++) {
const elementContext: ValidationContext = {
...context,
path: [...context.path, `[${i}]`],
depth: context.depth + 1
};
const result = this.validateValue(value[i], elementContext);
if (!result.valid) {
return result;
}
}
return { valid: true };
}
/**
* 验证对象
*/
private validateObject(value: Record<string, any>, context: ValidationContext): ValidationResult {
// 验证每个属性
for (const [key, propertyValue] of Object.entries(value)) {
const propertyContext: ValidationContext = {
...context,
path: [...context.path, key],
depth: context.depth + 1
};
const result = this.validateValue(propertyValue, propertyContext);
if (!result.valid) {
return result;
}
}
return { valid: true };
}
/**
* 验证自定义规则
*/
private validateCustomRules(message: TransportMessage): ValidationResult {
for (const rule of this.customRules.values()) {
const context: ValidationContext = {
path: [],
depth: 0,
config: this.config,
messageType: message.type
};
const result = rule.validate(message, context);
if (!result.valid) {
return {
...result,
details: {
...result.details,
rule: rule.name
}
};
}
}
return { valid: true };
}
}
/**
* 预定义验证规则
*/
export const DefaultValidationRules = {
/**
* RPC 消息验证规则
*/
RpcMessage: {
name: 'RpcMessage',
validate: (message: TransportMessage, context: ValidationContext): ValidationResult => {
if (message.type !== 'rpc') {
return { valid: true }; // 不是 RPC 消息,跳过验证
}
const data = message.data as any;
// 检查必需字段
if (!data.networkId || typeof data.networkId !== 'number') {
return {
valid: false,
error: 'RPC message must have a valid networkId',
errorCode: 'RPC_INVALID_NETWORK_ID'
};
}
if (!data.componentType || typeof data.componentType !== 'string') {
return {
valid: false,
error: 'RPC message must have a valid componentType',
errorCode: 'RPC_INVALID_COMPONENT_TYPE'
};
}
if (!data.methodName || typeof data.methodName !== 'string') {
return {
valid: false,
error: 'RPC message must have a valid methodName',
errorCode: 'RPC_INVALID_METHOD_NAME'
};
}
// 检查参数数组
if (data.parameters && !Array.isArray(data.parameters)) {
return {
valid: false,
error: 'RPC parameters must be an array',
errorCode: 'RPC_INVALID_PARAMETERS'
};
}
return { valid: true };
}
} as ValidationRule,
/**
* SyncVar 消息验证规则
*/
SyncVarMessage: {
name: 'SyncVarMessage',
validate: (message: TransportMessage, context: ValidationContext): ValidationResult => {
if (message.type !== 'syncvar') {
return { valid: true }; // 不是 SyncVar 消息,跳过验证
}
const data = message.data as any;
// 检查必需字段
if (!data.networkId || typeof data.networkId !== 'number') {
return {
valid: false,
error: 'SyncVar message must have a valid networkId',
errorCode: 'SYNCVAR_INVALID_NETWORK_ID'
};
}
if (!data.componentType || typeof data.componentType !== 'string') {
return {
valid: false,
error: 'SyncVar message must have a valid componentType',
errorCode: 'SYNCVAR_INVALID_COMPONENT_TYPE'
};
}
if (!data.propertyName || typeof data.propertyName !== 'string') {
return {
valid: false,
error: 'SyncVar message must have a valid propertyName',
errorCode: 'SYNCVAR_INVALID_PROPERTY_NAME'
};
}
return { valid: true };
}
} as ValidationRule
};

View File

@@ -1,776 +0,0 @@
/**
* RPC 验证器
*
* 专门用于验证 RPC 调用的参数、权限、频率等
*/
import { NetworkValue, RpcMetadata } from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { ValidationResult } from './MessageValidator';
/**
* RPC 验证配置
*/
export interface RpcValidationConfig {
/** 最大参数数量 */
maxParameterCount?: number;
/** 单个参数最大大小(字节) */
maxParameterSize?: number;
/** 允许的参数类型 */
allowedParameterTypes?: string[];
/** 方法名黑名单 */
blacklistedMethods?: string[];
/** 方法名白名单 */
whitelistedMethods?: string[];
/** 是否启用参数类型检查 */
enableTypeCheck?: boolean;
/** 是否启用参数内容过滤 */
enableContentFilter?: boolean;
}
/**
* RPC 调用上下文
*/
export interface RpcCallContext {
/** 客户端连接 */
client: ClientConnection;
/** 网络对象ID */
networkId: number;
/** 组件类型 */
componentType: string;
/** 方法名 */
methodName: string;
/** 参数列表 */
parameters: NetworkValue[];
/** RPC 元数据 */
metadata: RpcMetadata;
/** RPC 类型 */
rpcType: 'client-rpc' | 'server-rpc';
}
/**
* 参数类型定义
*/
export interface ParameterTypeDefinition {
/** 参数名 */
name: string;
/** 参数类型 */
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any';
/** 是否必需 */
required?: boolean;
/** 最小值/长度 */
min?: number;
/** 最大值/长度 */
max?: number;
/** 允许的值列表 */
allowedValues?: NetworkValue[];
/** 正则表达式(仅用于字符串) */
pattern?: RegExp;
/** 自定义验证函数 */
customValidator?: (value: NetworkValue) => ValidationResult;
}
/**
* 方法签名定义
*/
export interface MethodSignature {
/** 方法名 */
methodName: string;
/** 组件类型 */
componentType: string;
/** 参数定义 */
parameters: ParameterTypeDefinition[];
/** 返回值类型 */
returnType?: string;
/** 是否需要权限验证 */
requiresAuth?: boolean;
/** 所需权限 */
requiredPermissions?: string[];
/** 频率限制(调用/分钟) */
rateLimit?: number;
/** 自定义验证函数 */
customValidator?: (context: RpcCallContext) => ValidationResult;
}
/**
* RPC 频率跟踪
*/
interface RpcRateTracker {
/** 客户端ID */
clientId: string;
/** 方法调用计数 */
methodCalls: Map<string, { count: number; resetTime: Date }>;
/** 最后更新时间 */
lastUpdate: Date;
}
/**
* RPC 验证器
*/
export class RpcValidator {
private config: RpcValidationConfig;
private methodSignatures = new Map<string, MethodSignature>();
private rateTrackers = new Map<string, RpcRateTracker>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: RpcValidationConfig = {}) {
this.config = {
maxParameterCount: 10,
maxParameterSize: 65536, // 64KB
allowedParameterTypes: ['string', 'number', 'boolean', 'object', 'array'],
blacklistedMethods: [],
whitelistedMethods: [],
enableTypeCheck: true,
enableContentFilter: true,
...config
};
this.initialize();
}
/**
* 验证 RPC 调用
*/
validateRpcCall(context: RpcCallContext): ValidationResult {
try {
// 基本验证
const basicResult = this.validateBasicRpcCall(context);
if (!basicResult.valid) {
return basicResult;
}
// 方法名验证
const methodResult = this.validateMethodName(context);
if (!methodResult.valid) {
return methodResult;
}
// 权限验证
const permissionResult = this.validateRpcPermissions(context);
if (!permissionResult.valid) {
return permissionResult;
}
// 参数验证
const parameterResult = this.validateParameters(context);
if (!parameterResult.valid) {
return parameterResult;
}
// 频率限制验证
const rateResult = this.validateRateLimit(context);
if (!rateResult.valid) {
return rateResult;
}
// 签名验证(如果有定义)
const signatureResult = this.validateMethodSignature(context);
if (!signatureResult.valid) {
return signatureResult;
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: (error as Error).message,
errorCode: 'RPC_VALIDATION_ERROR'
};
}
}
/**
* 注册方法签名
*/
registerMethodSignature(signature: MethodSignature): void {
const key = `${signature.componentType}.${signature.methodName}`;
this.methodSignatures.set(key, signature);
}
/**
* 移除方法签名
*/
removeMethodSignature(componentType: string, methodName: string): boolean {
const key = `${componentType}.${methodName}`;
return this.methodSignatures.delete(key);
}
/**
* 获取方法签名
*/
getMethodSignature(componentType: string, methodName: string): MethodSignature | undefined {
const key = `${componentType}.${methodName}`;
return this.methodSignatures.get(key);
}
/**
* 添加方法到黑名单
*/
addToBlacklist(methodName: string): void {
if (!this.config.blacklistedMethods!.includes(methodName)) {
this.config.blacklistedMethods!.push(methodName);
}
}
/**
* 从黑名单移除方法
*/
removeFromBlacklist(methodName: string): boolean {
const index = this.config.blacklistedMethods!.indexOf(methodName);
if (index !== -1) {
this.config.blacklistedMethods!.splice(index, 1);
return true;
}
return false;
}
/**
* 添加方法到白名单
*/
addToWhitelist(methodName: string): void {
if (!this.config.whitelistedMethods!.includes(methodName)) {
this.config.whitelistedMethods!.push(methodName);
}
}
/**
* 获取客户端的 RPC 统计
*/
getClientRpcStats(clientId: string): {
totalCalls: number;
methodStats: Record<string, number>;
} {
const tracker = this.rateTrackers.get(clientId);
if (!tracker) {
return { totalCalls: 0, methodStats: {} };
}
let totalCalls = 0;
const methodStats: Record<string, number> = {};
for (const [method, data] of tracker.methodCalls) {
totalCalls += data.count;
methodStats[method] = data.count;
}
return { totalCalls, methodStats };
}
/**
* 重置客户端的频率限制
*/
resetClientRateLimit(clientId: string): boolean {
const tracker = this.rateTrackers.get(clientId);
if (!tracker) {
return false;
}
tracker.methodCalls.clear();
tracker.lastUpdate = new Date();
return true;
}
/**
* 销毁验证器
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.methodSignatures.clear();
this.rateTrackers.clear();
}
/**
* 初始化验证器
*/
private initialize(): void {
// 启动清理定时器每5分钟清理一次
this.cleanupTimer = setInterval(() => {
this.cleanupRateTrackers();
}, 5 * 60 * 1000);
}
/**
* 基本 RPC 调用验证
*/
private validateBasicRpcCall(context: RpcCallContext): ValidationResult {
// 网络对象ID验证
if (!Number.isInteger(context.networkId) || context.networkId <= 0) {
return {
valid: false,
error: 'Invalid network object ID',
errorCode: 'INVALID_NETWORK_ID'
};
}
// 组件类型验证
if (!context.componentType || typeof context.componentType !== 'string') {
return {
valid: false,
error: 'Invalid component type',
errorCode: 'INVALID_COMPONENT_TYPE'
};
}
// 方法名验证
if (!context.methodName || typeof context.methodName !== 'string') {
return {
valid: false,
error: 'Invalid method name',
errorCode: 'INVALID_METHOD_NAME'
};
}
// 参数数组验证
if (!Array.isArray(context.parameters)) {
return {
valid: false,
error: 'Parameters must be an array',
errorCode: 'INVALID_PARAMETERS_FORMAT'
};
}
// 参数数量检查
if (context.parameters.length > this.config.maxParameterCount!) {
return {
valid: false,
error: `Too many parameters: ${context.parameters.length} (max: ${this.config.maxParameterCount})`,
errorCode: 'TOO_MANY_PARAMETERS'
};
}
return { valid: true };
}
/**
* 方法名验证
*/
private validateMethodName(context: RpcCallContext): ValidationResult {
const methodName = context.methodName;
// 黑名单检查
if (this.config.blacklistedMethods!.length > 0) {
if (this.config.blacklistedMethods!.includes(methodName)) {
return {
valid: false,
error: `Method '${methodName}' is blacklisted`,
errorCode: 'METHOD_BLACKLISTED'
};
}
}
// 白名单检查
if (this.config.whitelistedMethods!.length > 0) {
if (!this.config.whitelistedMethods!.includes(methodName)) {
return {
valid: false,
error: `Method '${methodName}' is not whitelisted`,
errorCode: 'METHOD_NOT_WHITELISTED'
};
}
}
// 危险方法名检查
const dangerousPatterns = [
/^__/, // 私有方法
/constructor/i,
/prototype/i,
/eval/i,
/function/i
];
for (const pattern of dangerousPatterns) {
if (pattern.test(methodName)) {
return {
valid: false,
error: `Potentially dangerous method name: '${methodName}'`,
errorCode: 'DANGEROUS_METHOD_NAME'
};
}
}
return { valid: true };
}
/**
* RPC 权限验证
*/
private validateRpcPermissions(context: RpcCallContext): ValidationResult {
// 基本 RPC 权限检查
if (!context.client.hasPermission('canSendRpc')) {
return {
valid: false,
error: 'Client does not have RPC permission',
errorCode: 'RPC_PERMISSION_DENIED'
};
}
// ServerRpc 特殊权限检查
if (context.rpcType === 'server-rpc') {
if (context.metadata.requiresAuth && !context.client.isAuthenticated) {
return {
valid: false,
error: 'Authentication required for this RPC',
errorCode: 'AUTHENTICATION_REQUIRED'
};
}
}
// 检查方法签名中的权限要求
const signature = this.getMethodSignature(context.componentType, context.methodName);
if (signature && signature.requiredPermissions) {
for (const permission of signature.requiredPermissions) {
if (!context.client.hasCustomPermission(permission)) {
return {
valid: false,
error: `Required permission '${permission}' not found`,
errorCode: 'INSUFFICIENT_PERMISSIONS'
};
}
}
}
return { valid: true };
}
/**
* 参数验证
*/
private validateParameters(context: RpcCallContext): ValidationResult {
// 参数大小检查
for (let i = 0; i < context.parameters.length; i++) {
const param = context.parameters[i];
try {
const serialized = JSON.stringify(param);
const size = new TextEncoder().encode(serialized).length;
if (size > this.config.maxParameterSize!) {
return {
valid: false,
error: `Parameter ${i} is too large: ${size} bytes (max: ${this.config.maxParameterSize})`,
errorCode: 'PARAMETER_TOO_LARGE'
};
}
} catch (error) {
return {
valid: false,
error: `Parameter ${i} is not serializable`,
errorCode: 'PARAMETER_NOT_SERIALIZABLE'
};
}
}
// 参数类型检查
if (this.config.enableTypeCheck) {
const typeResult = this.validateParameterTypes(context);
if (!typeResult.valid) {
return typeResult;
}
}
// 参数内容过滤
if (this.config.enableContentFilter) {
const contentResult = this.validateParameterContent(context);
if (!contentResult.valid) {
return contentResult;
}
}
return { valid: true };
}
/**
* 参数类型验证
*/
private validateParameterTypes(context: RpcCallContext): ValidationResult {
for (let i = 0; i < context.parameters.length; i++) {
const param = context.parameters[i];
const paramType = this.getParameterType(param);
if (!this.config.allowedParameterTypes!.includes(paramType)) {
return {
valid: false,
error: `Parameter ${i} type '${paramType}' is not allowed`,
errorCode: 'INVALID_PARAMETER_TYPE'
};
}
}
return { valid: true };
}
/**
* 参数内容验证
*/
private validateParameterContent(context: RpcCallContext): ValidationResult {
for (let i = 0; i < context.parameters.length; i++) {
const param = context.parameters[i];
// 检查危险内容
if (typeof param === 'string') {
const dangerousPatterns = [
/<script/i,
/javascript:/i,
/eval\(/i,
/function\(/i,
/__proto__/i
];
for (const pattern of dangerousPatterns) {
if (pattern.test(param)) {
return {
valid: false,
error: `Parameter ${i} contains potentially dangerous content`,
errorCode: 'DANGEROUS_PARAMETER_CONTENT'
};
}
}
}
}
return { valid: true };
}
/**
* 频率限制验证
*/
private validateRateLimit(context: RpcCallContext): ValidationResult {
const signature = this.getMethodSignature(context.componentType, context.methodName);
if (!signature || !signature.rateLimit) {
return { valid: true }; // 没有频率限制
}
const clientId = context.client.id;
const methodKey = `${context.componentType}.${context.methodName}`;
let tracker = this.rateTrackers.get(clientId);
if (!tracker) {
tracker = {
clientId,
methodCalls: new Map(),
lastUpdate: new Date()
};
this.rateTrackers.set(clientId, tracker);
}
const now = new Date();
let methodData = tracker.methodCalls.get(methodKey);
if (!methodData) {
methodData = {
count: 1,
resetTime: new Date(now.getTime() + 60000) // 1分钟后重置
};
tracker.methodCalls.set(methodKey, methodData);
return { valid: true };
}
// 检查是否需要重置
if (now >= methodData.resetTime) {
methodData.count = 1;
methodData.resetTime = new Date(now.getTime() + 60000);
return { valid: true };
}
// 检查频率限制
if (methodData.count >= signature.rateLimit) {
return {
valid: false,
error: `Rate limit exceeded for method '${methodKey}': ${methodData.count}/${signature.rateLimit} per minute`,
errorCode: 'RATE_LIMIT_EXCEEDED'
};
}
methodData.count++;
tracker.lastUpdate = now;
return { valid: true };
}
/**
* 方法签名验证
*/
private validateMethodSignature(context: RpcCallContext): ValidationResult {
const signature = this.getMethodSignature(context.componentType, context.methodName);
if (!signature) {
return { valid: true }; // 没有定义签名,跳过验证
}
// 参数数量检查
const requiredParams = signature.parameters.filter(p => p.required !== false);
if (context.parameters.length < requiredParams.length) {
return {
valid: false,
error: `Not enough parameters: expected at least ${requiredParams.length}, got ${context.parameters.length}`,
errorCode: 'INSUFFICIENT_PARAMETERS'
};
}
if (context.parameters.length > signature.parameters.length) {
return {
valid: false,
error: `Too many parameters: expected at most ${signature.parameters.length}, got ${context.parameters.length}`,
errorCode: 'EXCESS_PARAMETERS'
};
}
// 参数类型和值验证
for (let i = 0; i < Math.min(context.parameters.length, signature.parameters.length); i++) {
const param = context.parameters[i];
const paramDef = signature.parameters[i];
const paramResult = this.validateParameterDefinition(param, paramDef, i);
if (!paramResult.valid) {
return paramResult;
}
}
// 自定义验证
if (signature.customValidator) {
const customResult = signature.customValidator(context);
if (!customResult.valid) {
return customResult;
}
}
return { valid: true };
}
/**
* 验证参数定义
*/
private validateParameterDefinition(
value: NetworkValue,
definition: ParameterTypeDefinition,
index: number
): ValidationResult {
// 类型检查
const actualType = this.getParameterType(value);
if (definition.type !== 'any' && actualType !== definition.type) {
return {
valid: false,
error: `Parameter ${index} type mismatch: expected '${definition.type}', got '${actualType}'`,
errorCode: 'PARAMETER_TYPE_MISMATCH'
};
}
// 值范围检查
if (typeof value === 'number' && (definition.min !== undefined || definition.max !== undefined)) {
if (definition.min !== undefined && value < definition.min) {
return {
valid: false,
error: `Parameter ${index} value ${value} is less than minimum ${definition.min}`,
errorCode: 'PARAMETER_BELOW_MINIMUM'
};
}
if (definition.max !== undefined && value > definition.max) {
return {
valid: false,
error: `Parameter ${index} value ${value} is greater than maximum ${definition.max}`,
errorCode: 'PARAMETER_ABOVE_MAXIMUM'
};
}
}
// 字符串长度检查
if (typeof value === 'string' && (definition.min !== undefined || definition.max !== undefined)) {
if (definition.min !== undefined && value.length < definition.min) {
return {
valid: false,
error: `Parameter ${index} string length ${value.length} is less than minimum ${definition.min}`,
errorCode: 'STRING_TOO_SHORT'
};
}
if (definition.max !== undefined && value.length > definition.max) {
return {
valid: false,
error: `Parameter ${index} string length ${value.length} is greater than maximum ${definition.max}`,
errorCode: 'STRING_TOO_LONG'
};
}
}
// 允许值检查
if (definition.allowedValues && definition.allowedValues.length > 0) {
if (!definition.allowedValues.includes(value)) {
return {
valid: false,
error: `Parameter ${index} value '${value}' is not in allowed values: ${definition.allowedValues.join(', ')}`,
errorCode: 'VALUE_NOT_ALLOWED'
};
}
}
// 正则表达式检查(字符串)
if (typeof value === 'string' && definition.pattern) {
if (!definition.pattern.test(value)) {
return {
valid: false,
error: `Parameter ${index} string '${value}' does not match required pattern`,
errorCode: 'PATTERN_MISMATCH'
};
}
}
// 自定义验证
if (definition.customValidator) {
const customResult = definition.customValidator(value);
if (!customResult.valid) {
return {
...customResult,
error: `Parameter ${index} validation failed: ${customResult.error}`
};
}
}
return { valid: true };
}
/**
* 获取参数类型
*/
private getParameterType(value: any): string {
if (value === null || value === undefined) {
return 'null';
}
if (Array.isArray(value)) {
return 'array';
}
return typeof value;
}
/**
* 清理过期的频率跟踪器
*/
private cleanupRateTrackers(): void {
const now = new Date();
const expireTime = 10 * 60 * 1000; // 10分钟
let cleanedCount = 0;
for (const [clientId, tracker] of this.rateTrackers.entries()) {
if (now.getTime() - tracker.lastUpdate.getTime() > expireTime) {
this.rateTrackers.delete(clientId);
cleanedCount++;
} else {
// 清理过期的方法调用记录
for (const [methodKey, methodData] of tracker.methodCalls.entries()) {
if (now >= methodData.resetTime) {
tracker.methodCalls.delete(methodKey);
}
}
}
}
if (cleanedCount > 0) {
console.log(`RPC validator cleanup: ${cleanedCount} rate trackers removed`);
}
}
}

View File

@@ -1,6 +0,0 @@
/**
* 验证系统导出
*/
export * from './MessageValidator';
export * from './RpcValidator';

View File

@@ -1,9 +1,26 @@
/**
* Jest测试环境设置 - 服务端
*/
// 导入reflect-metadata以支持装饰器
import 'reflect-metadata';
global.beforeEach(() => {
jest.clearAllMocks();
// 全局测试配置
beforeAll(() => {
// 设置测试环境
process.env.NODE_ENV = 'test';
process.env.NETWORK_ENV = 'server';
});
global.afterEach(() => {
jest.restoreAllMocks();
afterAll(() => {
// 清理测试环境
});
beforeEach(() => {
// 每个测试前的准备工作
});
afterEach(() => {
// 每个测试后的清理工作
// 清理可能的网络连接、定时器等
});

View File

@@ -8,6 +8,7 @@
"outDir": "./bin",
"rootDir": "./src",
"strict": true,
"composite": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
@@ -41,5 +42,13 @@
"bin",
"**/*.test.ts",
"**/*.spec.ts"
],
"references": [
{
"path": "../core"
},
{
"path": "../network-shared"
}
]
}

View File

@@ -1,60 +0,0 @@
# @esengine/ecs-framework-network-shared
ECS Framework 网络库 - 共享组件和类型定义
## 概述
这是 ECS Framework 网络库的共享包,包含了客户端和服务端通用的:
- 装饰器定义 (`@SyncVar`, `@ClientRpc`, `@ServerRpc` 等)
- 类型定义和接口
- 序列化/反序列化工具
- Protobuf 自动生成机制
- 网络消息基类
## 特性
- **装饰器驱动**: 基于装饰器自动生成网络协议
- **类型安全**: 完整的 TypeScript 支持
- **自动序列化**: 基于 Protobuf 的高性能序列化
- **零配置**: 无需手写 .proto 文件
- **ECS 集成**: 深度集成 ECS 框架的特性
## 安装
```bash
npm install @esengine/ecs-framework-network-shared
```
## 基本用法
```typescript
import { NetworkComponent, SyncVar, ClientRpc, ServerRpc } from '@esengine/ecs-framework-network-shared';
@NetworkComponent()
class PlayerController extends Component {
@SyncVar({ onChanged: 'onHealthChanged' })
public health: number = 100;
@SyncVar()
public playerName: string = '';
@ClientRpc()
public showDamage(damage: number): void {
// 客户端显示伤害效果
}
@ServerRpc()
public movePlayer(direction: Vector3): void {
// 服务端处理玩家移动
}
private onHealthChanged(oldValue: number, newValue: number): void {
console.log(`生命值从 ${oldValue} 变为 ${newValue}`);
}
}
```
## License
MIT

View File

@@ -2,27 +2,32 @@ const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
console.log('🚀 使用 Rollup 构建 network-shared 包...');
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('rollup -c rollup.config.cjs', { stdio: 'inherit' });
execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' });
// 生成package.json
console.log('📋 生成 package.json...');
generatePackageJson();
// 复制其他文件
console.log('📁 复制必要文件...');
copyFiles();
// 输出构建结果
showBuildResults();
console.log('✅ network-shared 构建完成!');
console.log('✅ @esengine/network-shared 构建完成!');
console.log('\n🚀 发布命令:');
console.log('cd dist && npm publish');
@@ -63,19 +68,20 @@ function generatePackageJson() {
],
keywords: [
'ecs',
'networking',
'network',
'multiplayer',
'game',
'shared',
'decorators',
'protobuf',
'serialization',
'game-engine',
'typescript'
'typescript',
'cocos-creator',
'laya'
],
author: sourcePackage.author,
license: sourcePackage.license,
repository: sourcePackage.repository,
dependencies: sourcePackage.dependencies,
peerDependencies: sourcePackage.peerDependencies,
dependencies: sourcePackage.dependencies,
publishConfig: sourcePackage.publishConfig,
engines: {
node: '>=16.0.0'
},
@@ -88,7 +94,7 @@ function generatePackageJson() {
function copyFiles() {
const filesToCopy = [
{ src: './README.md', dest: './dist/README.md' },
{ src: '../../LICENSE', dest: './dist/LICENSE' }
{ src: './LICENSE', dest: './dist/LICENSE' }
];
filesToCopy.forEach(({ src, dest }) => {

View File

@@ -18,16 +18,10 @@ module.exports = {
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 60,
branches: 70,
functions: 70,
lines: 70,
statements: 70
},
'./src/decorators/': {
branches: 70,
functions: 80,
lines: 80,
statements: 80
}
},
verbose: true,
@@ -39,6 +33,7 @@ module.exports = {
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@esengine/ecs-framework$': '<rootDir>/../core/src/index.ts',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
@@ -49,5 +44,8 @@ module.exports = {
'<rootDir>/bin/',
'<rootDir>/dist/',
'<rootDir>/node_modules/'
],
transformIgnorePatterns: [
'node_modules/(?!(.*\\.mjs$|@esengine))'
]
};

View File

@@ -1,8 +1,7 @@
{
"name": "@esengine/ecs-framework-network-shared",
"version": "1.0.15",
"description": "ECS Framework 网络 - 共享组件和类型定义",
"type": "module",
"name": "@esengine/network-shared",
"version": "1.0.2",
"description": "ECS Framework网络 - 共享组件和协议",
"main": "bin/index.js",
"types": "bin/index.d.ts",
"exports": {
@@ -22,17 +21,15 @@
],
"keywords": [
"ecs",
"networking",
"shared",
"decorators",
"protobuf",
"serialization",
"game-engine",
"typescript"
"network",
"multiplayer",
"game",
"typescript",
"shared"
],
"scripts": {
"clean": "rimraf bin dist",
"build:ts": "tsc",
"clean": "rimraf bin dist tsconfig.tsbuildinfo",
"build:ts": "tsc --build",
"prebuild": "npm run clean",
"build": "npm run build:ts",
"build:watch": "tsc --watch",
@@ -46,30 +43,19 @@
"test": "jest --config jest.config.cjs",
"test:watch": "jest --watch --config jest.config.cjs",
"test:coverage": "jest --coverage --config jest.config.cjs",
"test:ci": "jest --ci --coverage --config jest.config.cjs",
"test:clear": "jest --clearCache"
"test:ci": "jest --ci --coverage --config jest.config.cjs"
},
"author": "yhh",
"license": "MIT",
"dependencies": {
"reflect-metadata": "^0.2.2",
"protobufjs": "^7.5.3"
},
"peerDependencies": {
"@esengine/ecs-framework": ">=2.1.29"
"@esengine/ecs-framework": "file:../core",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@esengine/ecs-framework": "*",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/node": "^20.19.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"rimraf": "^5.0.0",
"rollup": "^4.42.0",
"rollup-plugin-dts": "^6.2.1",
"ts-jest": "^29.4.0",
"typescript": "^5.8.3"
},

View File

@@ -7,14 +7,15 @@ const { readFileSync } = require('fs');
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
const banner = `/**
* @esengine/ecs-framework-network-shared v${pkg.version}
* ECS Framework 网络库 - 共享组件和类型定义
* @esengine/network-shared v${pkg.version}
* ECS网络层共享组件和协议
*
* @author ${pkg.author}
* @license ${pkg.license}
*/`;
const external = ['reflect-metadata', 'protobufjs', 'uuid', '@esengine/ecs-framework'];
// 外部依赖不打包进bundle
const external = ['@esengine/ecs-framework', 'reflect-metadata'];
const commonPlugins = [
resolve({
@@ -77,7 +78,7 @@ module.exports = [
}
},
// UMD构建
// UMD构建 - 包含所有依赖,用于浏览器直接使用
{
input: 'bin/index.js',
output: {
@@ -88,10 +89,8 @@ module.exports = [
sourcemap: true,
exports: 'named',
globals: {
'reflect-metadata': 'ReflectMetadata',
'protobufjs': 'protobuf',
'uuid': 'uuid',
'@esengine/ecs-framework': 'ECS'
'@esengine/ecs-framework': 'ECS',
'reflect-metadata': 'Reflect'
}
},
plugins: [
@@ -115,7 +114,7 @@ module.exports = [
file: 'dist/index.d.ts',
format: 'es',
banner: `/**
* @esengine/ecs-framework-network-shared v${pkg.version}
* @esengine/network-shared v${pkg.version}
* TypeScript definitions
*/`
},

View File

@@ -0,0 +1,317 @@
/**
* 网络身份组件
*/
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();
}
}

View File

@@ -1,261 +0,0 @@
/**
* NetworkBehaviour 基类
*
* 所有网络组件的基类,提供网络功能的基础实现
*/
import { Component } from '@esengine/ecs-framework';
import { INetworkComponent, INetworkObject, SyncVarMetadata, RpcMetadata, Constructor } from '../types/NetworkTypes';
import { getSyncVarMetadata, getDirtySyncVars, clearAllDirtySyncVars } from '../decorators/SyncVar';
import { getClientRpcMetadata } from '../decorators/ClientRpc';
import { getServerRpcMetadata } from '../decorators/ServerRpc';
/**
* NetworkBehaviour 基类
*
* 提供网络组件的基础功能:
* - SyncVar 支持
* - RPC 调用支持
* - 网络身份管理
* - 权限控制
*/
export abstract class NetworkBehaviour extends Component implements INetworkComponent {
/** 索引签名以支持动态属性访问 */
[key: string]: unknown;
/** 网络对象引用 */
public networkObject: INetworkObject | null = null;
/** 网络ID */
public get networkId(): number {
return this.networkObject?.networkId || 0;
}
/** 是否拥有权威 */
public get hasAuthority(): boolean {
return this.networkObject?.hasAuthority || false;
}
/** 组件类型名 */
public get componentType(): string {
return this.constructor.name;
}
/** 是否为服务端 */
public get isServer(): boolean {
// 这个方法会被具体的客户端/服务端库重写
return false;
}
/** 是否为客户端 */
public get isClient(): boolean {
// 这个方法会被具体的客户端/服务端库重写
return false;
}
/** 是否为本地对象 */
public get isLocal(): boolean {
return this.networkObject?.isLocal || false;
}
/** 所有者客户端ID */
public get ownerId(): number {
return this.networkObject?.ownerId || 0;
}
constructor() {
super();
this.setupSyncVarNotification();
}
/**
* 设置 SyncVar 变化通知
*/
private setupSyncVarNotification(): void {
// 添加 SyncVar 变化通知方法
(this as any).notifySyncVarChanged = (
propertyName: string,
oldValue: any,
newValue: any,
metadata: SyncVarMetadata
) => {
this.onSyncVarChanged(propertyName, oldValue, newValue, metadata);
};
}
/**
* SyncVar 变化处理
*/
protected onSyncVarChanged(
propertyName: string,
oldValue: any,
newValue: any,
metadata: SyncVarMetadata
): void {
// 权限检查
if (metadata.authorityOnly && !this.hasAuthority) {
console.warn(`Authority required for SyncVar: ${this.componentType}.${propertyName}`);
return;
}
// 通知网络管理器
this.notifyNetworkManager('syncvar-changed', {
networkId: this.networkId,
componentType: this.componentType,
propertyName,
oldValue,
newValue,
metadata
});
}
/**
* 发送客户端 RPC
*/
protected sendClientRpc(methodName: string, args: any[], options?: any, metadata?: RpcMetadata): any {
if (!this.hasAuthority && !this.isServer) {
console.warn(`Authority required for ClientRpc: ${this.componentType}.${methodName}`);
return;
}
return this.notifyNetworkManager('client-rpc', {
networkId: this.networkId,
componentType: this.componentType,
methodName,
args,
options,
metadata
});
}
/**
* 发送服务端 RPC
*/
protected sendServerRpc(methodName: string, args: any[], options?: any, metadata?: RpcMetadata): any {
if (!this.isClient) {
console.warn(`ServerRpc can only be called from client: ${this.componentType}.${methodName}`);
return;
}
return this.notifyNetworkManager('server-rpc', {
networkId: this.networkId,
componentType: this.componentType,
methodName,
args,
options,
metadata
});
}
/**
* 通知网络管理器
*/
private notifyNetworkManager(eventType: string, data: any): any {
// 这个方法会被具体的客户端/服务端库重写
// 用于与网络管理器通信
if (typeof (globalThis as any).NetworkManager !== 'undefined') {
return (globalThis as any).NetworkManager.handleNetworkEvent?.(eventType, data);
}
console.warn(`NetworkManager not found for event: ${eventType}`);
return null;
}
/**
* 获取所有 SyncVar 元数据
*/
public getSyncVars(): SyncVarMetadata[] {
return getSyncVarMetadata(this.constructor as Constructor);
}
/**
* 获取所有客户端 RPC 元数据
*/
public getClientRpcs(): RpcMetadata[] {
return getClientRpcMetadata(this.constructor as Constructor);
}
/**
* 获取所有服务端 RPC 元数据
*/
public getServerRpcs(): RpcMetadata[] {
return getServerRpcMetadata(this.constructor as Constructor);
}
/**
* 获取所有脏的 SyncVar
*/
public getDirtySyncVars() {
return getDirtySyncVars(this);
}
/**
* 清除所有脏标记
*/
public clearDirtySyncVars(): void {
clearAllDirtySyncVars(this);
}
/**
* 序列化组件状态
*/
public serializeState(): any {
const syncVars = this.getSyncVars();
const state: any = {};
for (const syncVar of syncVars) {
const value = (this as any)[`_${syncVar.propertyName}`];
if (value !== undefined) {
state[syncVar.propertyName] = value;
}
}
return state;
}
/**
* 反序列化组件状态
*/
public deserializeState(state: any): void {
const syncVars = this.getSyncVars();
for (const syncVar of syncVars) {
if (state.hasOwnProperty(syncVar.propertyName)) {
// 直接设置内部值,跳过权限检查
(this as any)[`_${syncVar.propertyName}`] = state[syncVar.propertyName];
// 调用变化回调
if (syncVar.onChanged && typeof (this as any)[syncVar.onChanged] === 'function') {
(this as any)[syncVar.onChanged](undefined, state[syncVar.propertyName]);
}
}
}
}
/**
* 检查是否有权限执行操作
*/
protected checkAuthority(requiresOwnership = false): boolean {
if (requiresOwnership && this.ownerId !== this.getLocalClientId()) {
return false;
}
return this.hasAuthority;
}
/**
* 获取本地客户端ID
* 这个方法会被具体实现重写
*/
protected getLocalClientId(): number {
return 0;
}
/**
* 组件销毁时的清理
*/
public onDestroy(): void {
this.networkObject = null;
// 清理网络资源(基类销毁由框架处理)
}
}

View File

@@ -1,324 +0,0 @@
/**
* NetworkIdentity 类
*
* 标识网络对象的唯一身份,管理网络组件和权威性
*/
import { Component } from '@esengine/ecs-framework';
import { INetworkObject, INetworkComponent } from '../types/NetworkTypes';
import { NetworkBehaviour } from './NetworkBehaviour';
/**
* NetworkIdentity 组件
*
* 所有需要网络同步的实体都必须拥有此组件
*/
export class NetworkIdentity extends Component implements INetworkObject {
/** 网络对象的唯一标识符 */
public networkId: number = 0;
/** 所有者客户端ID0 表示服务端拥有 */
public ownerId: number = 0;
/** 是否拥有权威,权威端可以修改 SyncVar 和发送 RPC */
public hasAuthority: boolean = false;
/** 是否为本地对象 */
public isLocal: boolean = false;
/** 是否为本地玩家对象 */
public isLocalPlayer: boolean = false;
/** 预制体名称(用于网络生成) */
public prefabName: string = '';
/** 场景对象ID用于场景中已存在的对象 */
public sceneId: number = 0;
/** 挂载的网络组件列表 */
public networkComponents: INetworkComponent[] = [];
/** 是否已在网络中生成 */
public isSpawned: boolean = false;
/** 可见性距离用于网络LOD */
public visibilityDistance: number = 100;
/** 网络更新频率覆盖0 = 使用全局设置) */
public updateRate: number = 0;
/** 是否总是相关(不受距离限制) */
public alwaysRelevant: boolean = false;
constructor() {
super();
}
/**
* 组件启动时初始化
*/
public override onEnabled(): void {
super.onEnabled();
this.gatherNetworkComponents();
this.registerToNetworkManager();
}
/**
* 收集实体上的所有网络组件
*/
private gatherNetworkComponents(): void {
if (!this.entity) {
return;
}
// 清空现有列表
this.networkComponents = [];
// 获取实体上的所有组件
// 获取实体上的所有组件,简化类型处理
const components = (this.entity as any).getComponents();
for (const component of components) {
if (component instanceof NetworkBehaviour) {
this.addNetworkComponent(component);
}
}
}
/**
* 添加网络组件
*/
public addNetworkComponent(component: INetworkComponent): void {
if (this.networkComponents.includes(component)) {
return;
}
this.networkComponents.push(component);
component.networkObject = this;
// 如果已经注册到网络,通知网络管理器
if (this.isSpawned) {
this.notifyNetworkManager('component-added', {
networkId: this.networkId,
componentType: component.componentType,
component
});
}
}
/**
* 移除网络组件
*/
public removeNetworkComponent(component: INetworkComponent): void {
const index = this.networkComponents.indexOf(component);
if (index === -1) {
return;
}
this.networkComponents.splice(index, 1);
component.networkObject = null;
// 如果已经注册到网络,通知网络管理器
if (this.isSpawned) {
this.notifyNetworkManager('component-removed', {
networkId: this.networkId,
componentType: component.componentType,
component
});
}
}
/**
* 设置权威性
*/
public setAuthority(hasAuthority: boolean, ownerId: number = 0): void {
const oldAuthority = this.hasAuthority;
const oldOwner = this.ownerId;
this.hasAuthority = hasAuthority;
this.ownerId = ownerId;
this.isLocal = this.checkIsLocal();
// 如果权威性发生变化,通知相关系统
if (oldAuthority !== hasAuthority || oldOwner !== ownerId) {
this.onAuthorityChanged(oldAuthority, hasAuthority, oldOwner, ownerId);
}
}
/**
* 设置为本地玩家
*/
public setAsLocalPlayer(): void {
this.isLocalPlayer = true;
this.hasAuthority = true;
this.isLocal = true;
}
/**
* 检查是否为本地对象
*/
private checkIsLocal(): boolean {
const localClientId = this.getLocalClientId();
return this.ownerId === localClientId;
}
/**
* 获取本地客户端ID
*/
private getLocalClientId(): number {
// 这个方法会被具体实现重写
if (typeof (globalThis as any).NetworkManager !== 'undefined') {
return (globalThis as any).NetworkManager.getLocalClientId?.() || 0;
}
return 0;
}
/**
* 权威性变化处理
*/
private onAuthorityChanged(
oldAuthority: boolean,
newAuthority: boolean,
oldOwner: number,
newOwner: number
): void {
// 通知网络管理器
this.notifyNetworkManager('authority-changed', {
networkId: this.networkId,
oldAuthority,
newAuthority,
oldOwner,
newOwner
});
// 通知所有网络组件
for (const component of this.networkComponents) {
if ('onAuthorityChanged' in component && typeof component.onAuthorityChanged === 'function') {
component.onAuthorityChanged(newAuthority);
}
}
}
/**
* 获取指定类型的网络组件
*/
public getNetworkComponent<T extends INetworkComponent>(type: new (...args: any[]) => T): T | null {
return this.networkComponents.find(c => c instanceof type) as T || null;
}
/**
* 获取所有指定类型的网络组件
*/
public getNetworkComponents<T extends INetworkComponent>(type: new (...args: any[]) => T): T[] {
return this.networkComponents.filter(c => c instanceof type) as T[];
}
/**
* 序列化网络身份
*/
public serialize(): any {
return {
networkId: this.networkId,
ownerId: this.ownerId,
hasAuthority: this.hasAuthority,
isLocal: this.isLocal,
isLocalPlayer: this.isLocalPlayer,
prefabName: this.prefabName,
sceneId: this.sceneId,
visibilityDistance: this.visibilityDistance,
updateRate: this.updateRate,
alwaysRelevant: this.alwaysRelevant
};
}
/**
* 反序列化网络身份
*/
public deserialize(data: any): void {
this.networkId = data.networkId || 0;
this.ownerId = data.ownerId || 0;
this.hasAuthority = data.hasAuthority || false;
this.isLocal = data.isLocal || false;
this.isLocalPlayer = data.isLocalPlayer || false;
this.prefabName = data.prefabName || '';
this.sceneId = data.sceneId || 0;
this.visibilityDistance = data.visibilityDistance || 100;
this.updateRate = data.updateRate || 0;
this.alwaysRelevant = data.alwaysRelevant || false;
}
/**
* 注册到网络管理器
*/
private registerToNetworkManager(): void {
this.notifyNetworkManager('register-network-object', {
networkIdentity: this,
networkId: this.networkId,
components: this.networkComponents
});
this.isSpawned = true;
}
/**
* 从网络管理器注销
*/
private unregisterFromNetworkManager(): void {
this.notifyNetworkManager('unregister-network-object', {
networkIdentity: this,
networkId: this.networkId
});
this.isSpawned = false;
}
/**
* 通知网络管理器
*/
private notifyNetworkManager(eventType: string, data: any): void {
if (typeof (globalThis as any).NetworkManager !== 'undefined') {
(globalThis as any).NetworkManager.handleNetworkEvent?.(eventType, data);
}
}
/**
* 检查对象是否对指定客户端可见
*/
public isVisibleTo(clientId: number, clientPosition?: { x: number; y: number; z?: number }): boolean {
// 如果总是相关,则对所有客户端可见
if (this.alwaysRelevant) {
return true;
}
// 如果没有提供客户端位置,默认可见
// 简单的可见性检查暂时不依赖Transform组件
if (!clientPosition) {
return true;
}
// 基于距离的可见性检查(需要自定义位置获取逻辑)
const position = { x: 0, y: 0, z: 0 }; // 占位符
const dx = position.x - clientPosition.x;
const dy = position.y - clientPosition.y;
const dz = (position.z || 0) - (clientPosition.z || 0);
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
return distance <= this.visibilityDistance;
}
/**
* 组件销毁时的清理
*/
public destroy(): void {
// 从网络管理器注销
if (this.isSpawned) {
this.unregisterFromNetworkManager();
}
// 清理所有网络组件的引用
for (const component of this.networkComponents) {
component.networkObject = null;
}
this.networkComponents = [];
// 清理网络资源(基类销毁由框架处理)
}
}

View File

@@ -1,6 +0,0 @@
/**
* 核心类导出
*/
export * from './NetworkBehaviour';
export * from './NetworkIdentity';

View File

@@ -1,177 +0,0 @@
/**
* ClientRpc 装饰器
*
* 用于标记可以在服务端调用,在客户端执行的方法
*/
import 'reflect-metadata';
import { RpcMetadata, DecoratorTarget, Constructor, RpcParameterType, RpcReturnType } from '../types/NetworkTypes';
import { getNetworkComponentMetadata } from './NetworkComponent';
/**
* ClientRpc 装饰器选项
*/
export interface ClientRpcOptions {
/** 是否需要权限验证 */
requiresAuth?: boolean;
/** 是否可靠传输,默认为 true */
reliable?: boolean;
/** 是否需要响应 */
requiresResponse?: boolean;
/** 目标客户端筛选器 */
targetFilter?: 'all' | 'others' | 'owner' | 'specific';
}
/**
* 存储 ClientRpc 元数据的 Symbol
*/
export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc_metadata');
/**
* ClientRpc 装饰器
*
* @param options 装饰器选项
* @returns 方法装饰器函数
*
* @example
* ```typescript
* @NetworkComponent()
* class PlayerController extends Component {
* @ClientRpc({ targetFilter: 'all' })
* public showDamageEffect(damage: number, position: Vector3): void {
* // 在所有客户端显示伤害效果
* console.log(`Showing damage: ${damage} at ${position}`);
* }
*
* @ClientRpc({ targetFilter: 'owner', reliable: false })
* public updateUI(data: UIData): void {
* // 只在拥有者客户端更新UI使用不可靠传输
* }
*
* @ClientRpc({ requiresResponse: true })
* public requestClientData(): ClientData {
* // 请求客户端数据并等待响应
* return this.getClientData();
* }
* }
* ```
*/
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
return function (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
if (typeof propertyKey !== 'string') {
throw new Error('ClientRpc can only be applied to string method names');
}
// 获取或创建元数据数组
let metadata: RpcMetadata[] = Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, (target as { constructor: Constructor }).constructor);
if (!metadata) {
metadata = [];
Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, metadata, (target as { constructor: Constructor }).constructor);
}
// 创建 RPC 元数据
const rpcMetadata: RpcMetadata = {
methodName: propertyKey,
rpcType: 'client-rpc',
requiresAuth: options.requiresAuth || false,
reliable: options.reliable !== false,
requiresResponse: options.requiresResponse || false
};
metadata.push(rpcMetadata);
// 更新 NetworkComponent 元数据
const componentMetadata = getNetworkComponentMetadata((target as { constructor: Constructor }).constructor);
if (componentMetadata) {
const existingIndex = componentMetadata.rpcs.findIndex(rpc => rpc.methodName === propertyKey);
if (existingIndex >= 0) {
componentMetadata.rpcs[existingIndex] = rpcMetadata;
} else {
componentMetadata.rpcs.push(rpcMetadata);
}
}
// 保存原方法
const originalMethod = descriptor.value;
if (typeof originalMethod !== 'function') {
throw new Error(`ClientRpc can only be applied to methods, got ${typeof originalMethod}`);
}
// 包装方法以添加网络调用逻辑
descriptor.value = function (this: Record<string, unknown> & {
isServer?: () => boolean;
sendClientRpc?: (methodName: string, args: RpcParameterType[], options: ClientRpcOptions, metadata: RpcMetadata) => RpcReturnType;
}, ...args: RpcParameterType[]): RpcReturnType {
// 如果在服务端调用,发送到客户端
const isServer = this.isServer?.() || (typeof window === 'undefined' && typeof process !== 'undefined');
if (isServer) {
return this.sendClientRpc?.(propertyKey, args, options, rpcMetadata) as RpcReturnType;
}
// 如果在客户端,直接执行本地方法
return (originalMethod as (...args: RpcParameterType[]) => RpcReturnType).apply(this, args);
};
// 保存原方法的引用,供直接调用
const decoratedFunction = descriptor.value as typeof descriptor.value & {
__originalMethod: typeof originalMethod;
__rpcMetadata: RpcMetadata;
__rpcOptions: ClientRpcOptions;
};
decoratedFunction.__originalMethod = originalMethod;
decoratedFunction.__rpcMetadata = rpcMetadata;
decoratedFunction.__rpcOptions = options;
return descriptor;
};
}
/**
* 获取类的 ClientRpc 元数据
*/
export function getClientRpcMetadata(target: Constructor): RpcMetadata[] {
return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || [];
}
/**
* 检查方法是否为 ClientRpc
*/
export function isClientRpc(target: Constructor, methodName: string): boolean {
const metadata = getClientRpcMetadata(target);
return metadata.some(m => m.methodName === methodName);
}
/**
* 获取特定方法的 ClientRpc 元数据
*/
export function getClientRpcMethodMetadata(target: Constructor, methodName: string): RpcMetadata | null {
const metadata = getClientRpcMetadata(target);
return metadata.find(m => m.methodName === methodName) || null;
}
/**
* 直接调用原方法(跳过网络逻辑)
*/
export function invokeClientRpcLocally(instance: Record<string, unknown>, methodName: string, args: RpcParameterType[]): RpcReturnType {
const method = instance[methodName] as { __originalMethod?: (...args: RpcParameterType[]) => RpcReturnType } | undefined;
if (method && typeof method.__originalMethod === 'function') {
return method.__originalMethod.apply(instance, args);
}
throw new Error(`Method ${methodName} is not a valid ClientRpc or original method not found`);
}
/**
* 检查 ClientRpc 是否需要响应
*/
export function clientRpcRequiresResponse(instance: Record<string, unknown>, methodName: string): boolean {
const method = instance[methodName] as { __rpcMetadata?: RpcMetadata } | undefined;
return method?.__rpcMetadata?.requiresResponse || false;
}
/**
* 获取 ClientRpc 的选项
*/
export function getClientRpcOptions(instance: Record<string, unknown>, methodName: string): ClientRpcOptions | null {
const method = instance[methodName] as { __rpcOptions?: ClientRpcOptions } | undefined;
return method?.__rpcOptions || null;
}

View File

@@ -1,138 +0,0 @@
/**
* NetworkComponent 装饰器
*
* 用于标记网络组件,自动注册到网络系统
*/
import 'reflect-metadata';
import { NetworkComponentMetadata } from '../types/NetworkTypes';
/**
* NetworkComponent 装饰器选项
*/
export interface NetworkComponentOptions {
/** 是否自动生成 protobuf 协议 */
autoGenerateProtocol?: boolean;
/** 自定义组件类型名 */
typeName?: string;
/** 是否仅服务端存在 */
serverOnly?: boolean;
/** 是否仅客户端存在 */
clientOnly?: boolean;
}
/**
* 存储 NetworkComponent 元数据的 Symbol
*/
export const NETWORK_COMPONENT_METADATA_KEY = Symbol('network_component_metadata');
/**
* NetworkComponent 装饰器
*
* @param options 装饰器选项
* @returns 类装饰器函数
*
* @example
* ```typescript
* @NetworkComponent({ autoGenerateProtocol: true })
* class PlayerController extends Component implements INetworkComponent {
* networkObject: INetworkObject | null = null;
* networkId: number = 0;
* hasAuthority: boolean = false;
* componentType: string = 'PlayerController';
*
* @SyncVar()
* public health: number = 100;
*
* @ClientRpc()
* public showDamage(damage: number): void {
* // 显示伤害效果
* }
* }
* ```
*/
export function NetworkComponent(options: NetworkComponentOptions = {}): ClassDecorator {
return function <T extends Function>(target: T) {
const metadata: NetworkComponentMetadata = {
componentType: options.typeName || target.name,
syncVars: [],
rpcs: [],
autoGenerateProtocol: options.autoGenerateProtocol !== false,
};
// 存储元数据
Reflect.defineMetadata(NETWORK_COMPONENT_METADATA_KEY, metadata, target);
// 注册到全局组件注册表
NetworkComponentRegistry.register(target as any, metadata);
return target;
};
}
/**
* 获取类的 NetworkComponent 元数据
*/
export function getNetworkComponentMetadata(target: any): NetworkComponentMetadata | null {
return Reflect.getMetadata(NETWORK_COMPONENT_METADATA_KEY, target) || null;
}
/**
* 检查类是否为 NetworkComponent
*/
export function isNetworkComponent(target: any): boolean {
return Reflect.hasMetadata(NETWORK_COMPONENT_METADATA_KEY, target);
}
/**
* 网络组件注册表
*/
class NetworkComponentRegistry {
private static components = new Map<string, {
constructor: any;
metadata: NetworkComponentMetadata;
}>();
/**
* 注册网络组件
*/
static register(constructor: any, metadata: NetworkComponentMetadata): void {
this.components.set(metadata.componentType, {
constructor,
metadata
});
}
/**
* 获取组件信息
*/
static getComponent(typeName: string) {
return this.components.get(typeName);
}
/**
* 获取所有组件
*/
static getAllComponents() {
return Array.from(this.components.entries()).map(([typeName, info]) => ({
typeName,
...info
}));
}
/**
* 检查组件是否已注册
*/
static hasComponent(typeName: string): boolean {
return this.components.has(typeName);
}
/**
* 清空注册表 (主要用于测试)
*/
static clear(): void {
this.components.clear();
}
}
export { NetworkComponentRegistry };

View File

@@ -1,178 +0,0 @@
/**
* ServerRpc 装饰器
*
* 用于标记可以在客户端调用,在服务端执行的方法
*/
import 'reflect-metadata';
import { RpcMetadata, DecoratorTarget, Constructor, RpcParameterType, RpcReturnType } from '../types/NetworkTypes';
import { getNetworkComponentMetadata } from './NetworkComponent';
/**
* ServerRpc 装饰器选项
*/
export interface ServerRpcOptions {
/** 是否需要权限验证 */
requiresAuth?: boolean;
/** 是否可靠传输,默认为 true */
reliable?: boolean;
/** 是否需要响应 */
requiresResponse?: boolean;
/** 是否需要拥有者权限 */
requiresOwnership?: boolean;
/** 调用频率限制 (调用/秒) */
rateLimit?: number;
}
/**
* 存储 ServerRpc 元数据的 Symbol
*/
export const SERVER_RPC_METADATA_KEY = Symbol('server_rpc_metadata');
/**
* ServerRpc 装饰器
*
* @param options 装饰器选项
* @returns 方法装饰器函数
*
* @example
* ```typescript
* @NetworkComponent()
* class PlayerController extends Component {
* @ServerRpc({ requiresOwnership: true, rateLimit: 10 })
* public movePlayer(direction: Vector3): void {
* // 在服务端处理玩家移动需要拥有者权限限制每秒10次调用
* this.transform.position.add(direction);
* }
*
* @ServerRpc({ requiresAuth: true })
* public purchaseItem(itemId: string): boolean {
* // 购买物品,需要认证
* return this.inventory.tryPurchase(itemId);
* }
*
* @ServerRpc({ requiresResponse: true })
* public getPlayerStats(): PlayerStats {
* // 获取玩家统计数据并返回给客户端
* return this.stats.toObject();
* }
* }
* ```
*/
export function ServerRpc(options: ServerRpcOptions = {}): MethodDecorator {
return function (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
if (typeof propertyKey !== 'string') {
throw new Error('ServerRpc can only be applied to string method names');
}
// 获取或创建元数据数组
const targetConstructor = (target as { constructor: Constructor }).constructor;
let metadata: RpcMetadata[] = Reflect.getMetadata(SERVER_RPC_METADATA_KEY, targetConstructor);
if (!metadata) {
metadata = [];
Reflect.defineMetadata(SERVER_RPC_METADATA_KEY, metadata, targetConstructor);
}
// 创建 RPC 元数据
const rpcMetadata: RpcMetadata = {
methodName: propertyKey,
rpcType: 'server-rpc',
requiresAuth: options.requiresAuth || false,
reliable: options.reliable !== false,
requiresResponse: options.requiresResponse || false
};
metadata.push(rpcMetadata);
// 更新 NetworkComponent 元数据
const componentMetadata = getNetworkComponentMetadata(targetConstructor);
if (componentMetadata) {
const existingIndex = componentMetadata.rpcs.findIndex(rpc => rpc.methodName === propertyKey);
if (existingIndex >= 0) {
componentMetadata.rpcs[existingIndex] = rpcMetadata;
} else {
componentMetadata.rpcs.push(rpcMetadata);
}
}
// 保存原方法
const originalMethod = descriptor.value;
if (typeof originalMethod !== 'function') {
throw new Error(`ServerRpc can only be applied to methods, got ${typeof originalMethod}`);
}
// 包装方法以添加网络调用逻辑
descriptor.value = function (this: any, ...args: any[]) {
// 如果在客户端调用,发送到服务端
const isClient = this.isClient?.() || (typeof window !== 'undefined');
if (isClient) {
return this.sendServerRpc?.(propertyKey, args, options, rpcMetadata);
}
// 如果在服务端,直接执行本地方法
return originalMethod.apply(this, args);
};
// 保存原方法的引用,供直接调用
(descriptor.value as any).__originalMethod = originalMethod;
(descriptor.value as any).__rpcMetadata = rpcMetadata;
(descriptor.value as any).__rpcOptions = options;
return descriptor;
};
}
/**
* Command 装饰器 (ServerRpc 的别名,用于兼容性)
*/
export const Command = ServerRpc;
/**
* 获取类的 ServerRpc 元数据
*/
export function getServerRpcMetadata(target: Constructor): RpcMetadata[] {
return Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target) || [];
}
/**
* 检查方法是否为 ServerRpc
*/
export function isServerRpc(target: Constructor, methodName: string): boolean {
const metadata = getServerRpcMetadata(target);
return metadata.some(m => m.methodName === methodName);
}
/**
* 获取特定方法的 ServerRpc 元数据
*/
export function getServerRpcMethodMetadata(target: Constructor, methodName: string): RpcMetadata | null {
const metadata = getServerRpcMetadata(target);
return metadata.find(m => m.methodName === methodName) || null;
}
/**
* 直接调用原方法(跳过网络逻辑)
*/
export function invokeServerRpcLocally(instance: any, methodName: string, args: any[]): any {
const method = instance[methodName];
if (method && typeof method.__originalMethod === 'function') {
return method.__originalMethod.apply(instance, args);
}
throw new Error(`Method ${methodName} is not a valid ServerRpc or original method not found`);
}
/**
* 检查 ServerRpc 是否需要响应
*/
export function serverRpcRequiresResponse(instance: any, methodName: string): boolean {
const method = instance[methodName];
return method?.__rpcMetadata?.requiresResponse || false;
}
/**
* 获取 ServerRpc 的选项
*/
export function getServerRpcOptions(instance: any, methodName: string): ServerRpcOptions | null {
const method = instance[methodName];
return method?.__rpcOptions || null;
}

View File

@@ -1,249 +0,0 @@
/**
* SyncVar 装饰器
*
* 用于标记需要在网络间自动同步的属性
*/
import 'reflect-metadata';
import { SyncVarMetadata, NetworkValue, DecoratorTarget, Constructor } from '../types/NetworkTypes';
import { getNetworkComponentMetadata } from './NetworkComponent';
/**
* SyncVar 装饰器选项
*/
export interface SyncVarOptions {
/** 是否仅权威端可修改,默认为 true */
authorityOnly?: boolean;
/** 变化回调函数名 */
onChanged?: string;
/** 序列化类型提示 */
serializeType?: string;
/** 是否使用增量同步 */
deltaSync?: boolean;
/** 同步优先级,数值越大优先级越高 */
priority?: number;
}
/**
* 存储 SyncVar 元数据的 Symbol
*/
export const SYNCVAR_METADATA_KEY = Symbol('syncvar_metadata');
/**
* SyncVar 装饰器
*
* @param options 装饰器选项
* @returns 属性装饰器函数
*
* @example
* ```typescript
* @NetworkComponent()
* class PlayerController extends Component {
* @SyncVar({ onChanged: 'onHealthChanged', priority: 10 })
* public health: number = 100;
*
* @SyncVar({ authorityOnly: false })
* public playerName: string = '';
*
* @SyncVar({ deltaSync: true })
* public inventory: Item[] = [];
*
* private onHealthChanged(oldValue: number, newValue: number): void {
* console.log(`Health changed from ${oldValue} to ${newValue}`);
* }
* }
* ```
*/
export function SyncVar<T extends NetworkValue = NetworkValue>(options: SyncVarOptions = {}): PropertyDecorator {
return function (target: unknown, propertyKey: string | symbol) {
if (typeof propertyKey !== 'string') {
throw new Error('SyncVar can only be applied to string property keys');
}
// 获取或创建元数据数组
const targetConstructor = (target as { constructor: Constructor }).constructor;
let metadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, targetConstructor);
if (!metadata) {
metadata = [];
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, metadata, targetConstructor);
}
// 创建 SyncVar 元数据
const syncVarMetadata: SyncVarMetadata = {
propertyName: propertyKey,
authorityOnly: options.authorityOnly !== false,
onChanged: options.onChanged,
serializeType: options.serializeType,
deltaSync: options.deltaSync || false,
priority: options.priority || 0
};
metadata.push(syncVarMetadata);
// 更新 NetworkComponent 元数据
const componentMetadata = getNetworkComponentMetadata(targetConstructor);
if (componentMetadata) {
const existingIndex = componentMetadata.syncVars.findIndex(sv => sv.propertyName === propertyKey);
if (existingIndex >= 0) {
componentMetadata.syncVars[existingIndex] = syncVarMetadata;
} else {
componentMetadata.syncVars.push(syncVarMetadata);
}
}
// 创建属性的内部存储和变化跟踪
const internalKey = `_${propertyKey}`;
const dirtyKey = `_${propertyKey}_dirty`;
const previousKey = `_${propertyKey}_previous`;
// 重新定义属性的 getter 和 setter
Object.defineProperty(target, propertyKey, {
get: function (this: Record<string, unknown>): T {
return this[internalKey] as T;
},
set: function (this: Record<string, unknown>, newValue: T) {
const oldValue = this[internalKey] as T;
// 检查值是否真的发生了变化
if (oldValue === newValue) {
return;
}
// 对于复杂对象,进行深度比较
if (typeof newValue === 'object' && newValue !== null &&
typeof oldValue === 'object' && oldValue !== null) {
if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
return;
}
}
// 保存旧值用于回调
this[previousKey] = oldValue;
this[internalKey] = newValue;
this[dirtyKey] = true;
// 调用变化回调
if (options.onChanged && typeof (this[options.onChanged] as unknown) === 'function') {
(this[options.onChanged] as (oldValue: T, newValue: T) => void)(oldValue, newValue);
}
// 通知网络同步系统
(this as { notifySyncVarChanged?: (key: string, oldValue: T, newValue: T, metadata: SyncVarMetadata) => void }).notifySyncVarChanged?.(propertyKey, oldValue, newValue, syncVarMetadata);
},
enumerable: true,
configurable: true
});
// 初始化内部属性
const targetRecord = target as Record<string, unknown>;
if (targetRecord[internalKey] === undefined) {
targetRecord[internalKey] = targetRecord[propertyKey];
}
targetRecord[dirtyKey] = false;
};
}
/**
* 获取类的 SyncVar 元数据
*/
export function getSyncVarMetadata(target: Constructor): SyncVarMetadata[] {
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
}
/**
* 检查属性是否为 SyncVar
*/
export function isSyncVar(target: Constructor, propertyName: string): boolean {
const metadata = getSyncVarMetadata(target);
return metadata.some(m => m.propertyName === propertyName);
}
/**
* 获取 SyncVar 的脏标记
*/
export function isSyncVarDirty(instance: Record<string, unknown>, propertyName: string): boolean {
return (instance[`_${propertyName}_dirty`] as boolean) || false;
}
/**
* 清除 SyncVar 的脏标记
*/
export function clearSyncVarDirty(instance: Record<string, unknown>, propertyName: string): void {
instance[`_${propertyName}_dirty`] = false;
}
/**
* 获取 SyncVar 的前一个值
*/
export function getSyncVarPreviousValue<T extends NetworkValue = NetworkValue>(instance: Record<string, unknown>, propertyName: string): T | undefined {
return instance[`_${propertyName}_previous`] as T | undefined;
}
/**
* 强制设置 SyncVar 值(跳过权限检查和变化检测)
*/
export function setSyncVarValue<T extends NetworkValue = NetworkValue>(instance: Record<string, unknown>, propertyName: string, value: T, skipCallback = false): void {
const internalKey = `_${propertyName}`;
const dirtyKey = `_${propertyName}_dirty`;
const previousKey = `_${propertyName}_previous`;
const oldValue = instance[internalKey] as T;
instance[previousKey] = oldValue;
instance[internalKey] = value;
instance[dirtyKey] = false; // 网络接收的值不标记为脏
// 可选择性调用回调
if (!skipCallback) {
const metadata = getSyncVarMetadata((instance as { constructor: Constructor }).constructor);
const syncVarMeta = metadata.find(m => m.propertyName === propertyName);
if (syncVarMeta?.onChanged && typeof (instance[syncVarMeta.onChanged] as unknown) === 'function') {
(instance[syncVarMeta.onChanged] as (oldValue: T, newValue: T) => void)(oldValue, value);
}
}
}
/**
* 批量获取所有脏的 SyncVar
*/
export function getDirtySyncVars(instance: Record<string, unknown>): Array<{
propertyName: string;
oldValue: NetworkValue;
newValue: NetworkValue;
metadata: SyncVarMetadata;
}> {
const metadata = getSyncVarMetadata((instance as { constructor: Constructor }).constructor);
const dirtyVars: Array<{
propertyName: string;
oldValue: NetworkValue;
newValue: NetworkValue;
metadata: SyncVarMetadata;
}> = [];
for (const syncVar of metadata) {
if (isSyncVarDirty(instance, syncVar.propertyName)) {
const oldValue = getSyncVarPreviousValue(instance, syncVar.propertyName);
const newValue = instance[`_${syncVar.propertyName}`] as NetworkValue;
dirtyVars.push({
propertyName: syncVar.propertyName,
oldValue: oldValue ?? newValue, // 使用空合并运算符处理undefined
newValue: newValue,
metadata: syncVar
});
}
}
// 按优先级排序,优先级高的先处理
return dirtyVars.sort((a, b) => (b.metadata.priority || 0) - (a.metadata.priority || 0));
}
/**
* 批量清除所有脏标记
*/
export function clearAllDirtySyncVars(instance: Record<string, unknown>): void {
const metadata = getSyncVarMetadata((instance as { constructor: Constructor }).constructor);
for (const syncVar of metadata) {
clearSyncVarDirty(instance, syncVar.propertyName);
}
}

View File

@@ -1,8 +0,0 @@
/**
* 装饰器导出
*/
export * from './NetworkComponent';
export * from './SyncVar';
export * from './ClientRpc';
export * from './ServerRpc';

View File

@@ -0,0 +1,287 @@
/**
* 网络事件类型枚举
* 定义网络层中的所有事件类型
*/
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
};
}
}

View File

@@ -0,0 +1 @@
export * from './NetworkEvents';

View File

@@ -1,43 +1,26 @@
/**
* ECS Framework Network Shared
*
* 共享的网络组件、装饰器和类型定义
* @esengine/network-shared
* ECS Framework网络层 - 共享组件和协议
*/
// 确保 reflect-metadata 被导入
import 'reflect-metadata';
// 类型定义
export * from './types';
export * from './types/NetworkTypes';
export * from './types/TransportTypes';
// 装饰器
export * from './decorators';
// 协议消息
export * from './protocols/MessageTypes';
// 核心
export * from './core';
// 核心组件
export * from './components/NetworkIdentity';
// 序列化工具
export * from './serialization';
// 装饰器系统 (待实现)
// export * from './decorators/SyncVar';
// export * from './decorators/ServerRpc';
// export * from './decorators/ClientRpc';
// export * from './decorators/NetworkComponent';
// 协议编译器
export * from './protocol';
// 事件系统
export * from './events/NetworkEvents';
// 工具函数
export * from './utils';
// 版本信息
export const VERSION = '1.0.0';
// 默认配置
export const DEFAULT_NETWORK_CONFIG = {
port: 7777,
host: 'localhost',
maxConnections: 100,
syncRate: 20,
snapshotRate: 5,
compression: true,
encryption: false,
timeout: 30000,
maxReconnectAttempts: 3,
reconnectInterval: 5000
};
// 序列化系统 (待实现)
// export * from './serialization/NetworkSerializer';

View File

@@ -1,663 +0,0 @@
/**
* TypeScript 协议分析器
*
* 负责解析 TypeScript 代码中的网络组件装饰器,
* 提取类型信息并构建协议定义
*/
// TypeScript编译器API - 开发时依赖
declare const require: any;
let ts: any;
let path: any;
let fs: any;
try {
ts = require('typescript');
path = require('path');
fs = require('fs');
} catch (e) {
// 在运行时如果没有这些依赖,使用占位符
ts = {
ScriptTarget: { ES2020: 99 },
ModuleKind: { ES2020: 99 },
createProgram: () => ({ getSourceFiles: () => [] }),
isClassDeclaration: () => false,
isDecorator: () => false,
isIdentifier: () => false,
isCallExpression: () => false,
forEachChild: () => {}
};
path = { join: (...args: string[]) => args.join('/') };
fs = { existsSync: () => false, readFileSync: () => '{}' };
}
import {
ComponentProtocol,
ProtocolField,
ProtocolRpc,
RpcParameter,
SerializeType,
ProtocolAnalysisResult,
ProtocolError,
ProtocolWarning,
ProtocolCompilerConfig
} from '../types/ProtocolTypes';
/**
* TypeScript 协议分析器
*/
export class TypeScriptAnalyzer {
private program: ts.Program;
private typeChecker: ts.TypeChecker;
private config: ProtocolCompilerConfig;
private components: ComponentProtocol[] = [];
private errors: ProtocolError[] = [];
private warnings: ProtocolWarning[] = [];
private dependencies: Map<string, string[]> = new Map();
constructor(config: ProtocolCompilerConfig) {
this.config = config;
this.initializeTypeScript();
}
/**
* 初始化 TypeScript 编译器
*/
private initializeTypeScript(): void {
if (!ts || !path || !fs) {
throw new Error('TypeScript analyzer requires typescript, path, and fs modules');
}
const configPath = this.config.tsconfigPath || path.join(this.config.inputDir, 'tsconfig.json');
let compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2020,
module: ts.ModuleKind.ES2020,
lib: ['ES2020'],
experimentalDecorators: true,
emitDecoratorMetadata: true,
strict: true
};
// 加载 tsconfig.json
if (fs.existsSync(configPath)) {
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
if (configFile.error) {
this.addError('syntax', `Failed to read tsconfig.json: ${configFile.error.messageText}`);
} else {
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configPath)
);
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
}
}
// 收集所有 TypeScript 文件
const files = this.collectTypeScriptFiles(this.config.inputDir);
this.program = ts.createProgram(files, compilerOptions);
this.typeChecker = this.program.getTypeChecker();
}
/**
* 收集 TypeScript 文件
*/
private collectTypeScriptFiles(dir: string): string[] {
const files: string[] = [];
const excludePatterns = this.config.excludePatterns || ['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**'];
function collectFiles(currentDir: string): void {
const items = fs.readdirSync(currentDir);
for (const item of items) {
const fullPath = path.join(currentDir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
// 检查是否应该排除此目录
const shouldExclude = excludePatterns.some(pattern =>
fullPath.includes(pattern.replace('**/', '').replace('/**', ''))
);
if (!shouldExclude) {
collectFiles(fullPath);
}
} else if (item.endsWith('.ts') || item.endsWith('.tsx')) {
// 检查是否应该排除此文件
const shouldExclude = excludePatterns.some(pattern => {
if (pattern.includes('**')) {
const regex = new RegExp(pattern.replace('**/', '.*').replace('*', '.*'));
return regex.test(fullPath);
}
return fullPath.endsWith(pattern.replace('*', ''));
});
if (!shouldExclude) {
files.push(fullPath);
}
}
}
}
collectFiles(dir);
return files;
}
/**
* 分析网络协议
*/
public analyze(): ProtocolAnalysisResult {
this.components = [];
this.errors = [];
this.warnings = [];
this.dependencies.clear();
const sourceFiles = this.program.getSourceFiles().filter(sf =>
!sf.isDeclarationFile && sf.fileName.includes(this.config.inputDir)
);
// 分析每个源文件
for (const sourceFile of sourceFiles) {
this.analyzeSourceFile(sourceFile);
}
// 检查依赖关系
this.validateDependencies();
return {
files: sourceFiles.map(sf => sf.fileName),
components: this.components,
dependencies: this.dependencies,
errors: this.errors,
warnings: this.warnings
};
}
/**
* 分析单个源文件
*/
private analyzeSourceFile(sourceFile: any): void {
const visit = (node: any): void => {
if (ts.isClassDeclaration(node) && this.isNetworkComponent(node)) {
this.analyzeNetworkComponent(node, sourceFile);
}
ts.forEachChild(node, visit);
};
visit(sourceFile);
}
/**
* 检查是否为网络组件
*/
private isNetworkComponent(node: any): boolean {
if (!node.modifiers) return false;
return node.modifiers.some((modifier: any) => {
if (ts.isDecorator(modifier)) {
const expression = modifier.expression;
if (ts.isCallExpression(expression) || ts.isIdentifier(expression)) {
const decoratorName = this.getDecoratorName(expression);
return decoratorName === 'NetworkComponent';
}
}
return false;
});
}
/**
* 获取装饰器名称
*/
private getDecoratorName(expression: ts.Expression): string | null {
if (ts.isIdentifier(expression)) {
return expression.text;
}
if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
return expression.expression.text;
}
return null;
}
/**
* 分析网络组件
*/
private analyzeNetworkComponent(node: any, sourceFile: any): void {
const className = node.name?.text;
if (!className) {
this.addError('syntax', 'NetworkComponent class must have a name', sourceFile, node);
return;
}
const componentProtocol: ComponentProtocol = {
typeName: className,
version: 1,
syncVars: [],
rpcs: [],
batchEnabled: false,
deltaEnabled: false
};
// 分析类成员
for (const member of node.members) {
if (ts.isPropertyDeclaration(member)) {
const syncVar = this.analyzeSyncVar(member, sourceFile);
if (syncVar) {
componentProtocol.syncVars.push(syncVar);
}
} else if (ts.isMethodDeclaration(member)) {
const rpc = this.analyzeRpc(member, sourceFile);
if (rpc) {
componentProtocol.rpcs.push(rpc);
}
}
}
// 分析装饰器选项
this.analyzeComponentDecorator(node, componentProtocol, sourceFile);
this.components.push(componentProtocol);
}
/**
* 分析 SyncVar 属性
*/
private analyzeSyncVar(node: ts.PropertyDeclaration, sourceFile: ts.SourceFile): ProtocolField | null {
if (!this.hasSyncVarDecorator(node)) {
return null;
}
const propertyName = this.getPropertyName(node);
if (!propertyName) {
this.addError('syntax', 'SyncVar property must have a name', sourceFile, node);
return null;
}
const type = this.typeChecker.getTypeAtLocation(node);
const serializeType = this.inferSerializeType(type, node, sourceFile);
if (!serializeType) {
this.addError('type', `Cannot infer serialize type for property: ${propertyName}`, sourceFile, node);
return null;
}
const field: ProtocolField = {
name: propertyName,
type: serializeType,
id: this.generateFieldId(propertyName),
optional: this.isOptionalProperty(node),
repeated: this.isArrayType(type)
};
// 分析装饰器选项
this.analyzeSyncVarDecorator(node, field, sourceFile);
return field;
}
/**
* 分析 RPC 方法
*/
private analyzeRpc(node: ts.MethodDeclaration, sourceFile: ts.SourceFile): ProtocolRpc | null {
const rpcType = this.getRpcType(node);
if (!rpcType) {
return null;
}
const methodName = this.getMethodName(node);
if (!methodName) {
this.addError('syntax', 'RPC method must have a name', sourceFile, node);
return null;
}
const parameters: RpcParameter[] = [];
// 分析参数
if (node.parameters) {
for (const param of node.parameters) {
const paramName = param.name.getText();
const paramType = this.typeChecker.getTypeAtLocation(param);
const serializeType = this.inferSerializeType(paramType, param, sourceFile);
if (serializeType === null) {
this.addError('type', `Cannot infer type for parameter: ${paramName}`, sourceFile, param);
continue;
}
parameters.push({
name: paramName,
type: serializeType,
optional: param.questionToken !== undefined,
isArray: this.isArrayType(paramType)
});
}
}
// 分析返回类型
let returnType: SerializeType | undefined;
if (node.type && !this.isVoidType(node.type)) {
const returnTypeNode = this.typeChecker.getTypeAtLocation(node.type);
returnType = this.inferSerializeType(returnTypeNode, node.type, sourceFile);
}
const rpc: ProtocolRpc = {
name: methodName,
id: this.generateRpcId(methodName),
type: rpcType,
parameters,
returnType
};
// 分析装饰器选项
this.analyzeRpcDecorator(node, rpc, sourceFile);
return rpc;
}
/**
* 检查是否有 SyncVar 装饰器
*/
private hasSyncVarDecorator(node: ts.PropertyDeclaration): boolean {
return this.hasDecorator(node, 'SyncVar');
}
/**
* 获取 RPC 类型
*/
private getRpcType(node: ts.MethodDeclaration): 'client-rpc' | 'server-rpc' | null {
if (this.hasDecorator(node, 'ClientRpc')) {
return 'client-rpc';
}
if (this.hasDecorator(node, 'ServerRpc') || this.hasDecorator(node, 'Command')) {
return 'server-rpc';
}
return null;
}
/**
* 检查是否有特定装饰器
*/
private hasDecorator(node: ts.Node, decoratorName: string): boolean {
if (!ts.canHaveModifiers(node) || !ts.getModifiers(node)) return false;
const modifiers = ts.getModifiers(node)!;
return modifiers.some(modifier => {
if (ts.isDecorator(modifier)) {
const name = this.getDecoratorName(modifier.expression);
return name === decoratorName;
}
return false;
});
}
/**
* 推导序列化类型
*/
private inferSerializeType(type: ts.Type, node: ts.Node, sourceFile: ts.SourceFile): SerializeType | null {
const typeString = this.typeChecker.typeToString(type);
// 自定义类型映射
if (this.config.typeMapping?.has(typeString)) {
return this.config.typeMapping.get(typeString)!;
}
// 基础类型推导
if (type.flags & ts.TypeFlags.Boolean) return SerializeType.BOOLEAN;
if (type.flags & ts.TypeFlags.Number) return SerializeType.FLOAT64;
if (type.flags & ts.TypeFlags.String) return SerializeType.STRING;
// 对象类型推导
if (type.flags & ts.TypeFlags.Object) {
// 检查是否为数组
if (this.typeChecker.isArrayType(type)) {
return SerializeType.ARRAY;
}
// 检查常见游戏类型
if (typeString.includes('Vector2')) return SerializeType.VECTOR2;
if (typeString.includes('Vector3')) return SerializeType.VECTOR3;
if (typeString.includes('Quaternion')) return SerializeType.QUATERNION;
if (typeString.includes('Color')) return SerializeType.COLOR;
// 默认为对象类型
return SerializeType.OBJECT;
}
this.addWarning('performance', `Unknown type: ${typeString}, falling back to JSON`, sourceFile, node);
return SerializeType.JSON;
}
/**
* 获取属性名
*/
private getPropertyName(node: ts.PropertyDeclaration): string | null {
if (ts.isIdentifier(node.name)) {
return node.name.text;
}
return null;
}
/**
* 获取方法名
*/
private getMethodName(node: ts.MethodDeclaration): string | null {
if (ts.isIdentifier(node.name)) {
return node.name.text;
}
return null;
}
/**
* 检查是否为可选属性
*/
private isOptionalProperty(node: ts.PropertyDeclaration): boolean {
return node.questionToken !== undefined;
}
/**
* 检查是否为数组类型
*/
private isArrayType(type: ts.Type): boolean {
return this.typeChecker.isArrayType(type);
}
/**
* 检查是否为 void 类型
*/
private isVoidType(node: ts.TypeNode): boolean {
return ts.isTypeReferenceNode(node) && node.typeName.getText() === 'void';
}
/**
* 生成字段 ID
*/
private generateFieldId(fieldName: string): number {
// 简单的哈希函数生成字段 ID
let hash = 0;
for (let i = 0; i < fieldName.length; i++) {
const char = fieldName.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为 32位整数
}
return Math.abs(hash) % 10000 + 1; // 确保 ID 为正数且在合理范围内
}
/**
* 生成 RPC ID
*/
private generateRpcId(rpcName: string): number {
return this.generateFieldId(rpcName) + 10000; // RPC ID 从 10000 开始
}
/**
* 分析组件装饰器选项
*/
private analyzeComponentDecorator(
node: ts.ClassDeclaration,
protocol: ComponentProtocol,
sourceFile: ts.SourceFile
): void {
const decorator = this.findDecorator(node, 'NetworkComponent');
if (decorator && ts.isCallExpression(decorator.expression)) {
const args = decorator.expression.arguments;
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
const options = args[0];
for (const prop of options.properties) {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
const propName = prop.name.text;
if (propName === 'batchEnabled' && this.isBooleanLiteral(prop.initializer)) {
protocol.batchEnabled = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
}
if (propName === 'deltaEnabled' && this.isBooleanLiteral(prop.initializer)) {
protocol.deltaEnabled = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
}
}
}
}
}
}
/**
* 分析 SyncVar 装饰器选项
*/
private analyzeSyncVarDecorator(
node: ts.PropertyDeclaration,
field: ProtocolField,
sourceFile: ts.SourceFile
): void {
const decorator = this.findDecorator(node, 'SyncVar');
if (decorator && ts.isCallExpression(decorator.expression)) {
const args = decorator.expression.arguments;
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
const options = args[0];
for (const prop of options.properties) {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
const propName = prop.name.text;
if (propName === 'serialize' && ts.isStringLiteral(prop.initializer)) {
const serializeType = prop.initializer.text as SerializeType;
if (Object.values(SerializeType).includes(serializeType)) {
field.type = serializeType;
}
}
}
}
}
}
}
/**
* 分析 RPC 装饰器选项
*/
private analyzeRpcDecorator(
node: ts.MethodDeclaration,
rpc: ProtocolRpc,
sourceFile: ts.SourceFile
): void {
const decoratorName = rpc.type === 'client-rpc' ? 'ClientRpc' : 'ServerRpc';
const decorator = this.findDecorator(node, decoratorName) || this.findDecorator(node, 'Command');
if (decorator && ts.isCallExpression(decorator.expression)) {
const args = decorator.expression.arguments;
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
const options = args[0];
for (const prop of options.properties) {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
const propName = prop.name.text;
if (propName === 'requiresAuth' && this.isBooleanLiteral(prop.initializer)) {
rpc.requiresAuth = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
}
if (propName === 'reliable' && this.isBooleanLiteral(prop.initializer)) {
rpc.reliable = (prop.initializer as ts.BooleanLiteral).token === ts.SyntaxKind.TrueKeyword;
}
if (propName === 'rateLimit' && ts.isNumericLiteral(prop.initializer)) {
rpc.rateLimit = parseInt(prop.initializer.text);
}
}
}
}
}
}
/**
* 查找装饰器
*/
private findDecorator(node: ts.Node, decoratorName: string): ts.Decorator | null {
if (!ts.canHaveModifiers(node) || !ts.getModifiers(node)) return null;
const modifiers = ts.getModifiers(node)!;
for (const modifier of modifiers) {
if (ts.isDecorator(modifier)) {
const name = this.getDecoratorName(modifier.expression);
if (name === decoratorName) {
return modifier;
}
}
}
return null;
}
/**
* 检查是否为布尔字面量
*/
private isBooleanLiteral(node: ts.Node): node is ts.BooleanLiteral {
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword;
}
/**
* 验证依赖关系
*/
private validateDependencies(): void {
// 检查循环依赖等问题
// 这里可以添加更复杂的依赖分析逻辑
}
/**
* 添加错误
*/
private addError(
type: ProtocolError['type'],
message: string,
sourceFile?: ts.SourceFile,
node?: ts.Node
): void {
const error: ProtocolError = {
type,
message,
file: sourceFile?.fileName,
line: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).line + 1 : undefined,
column: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).character + 1 : undefined
};
this.errors.push(error);
}
/**
* 添加警告
*/
private addWarning(
type: ProtocolWarning['type'],
message: string,
sourceFile?: ts.SourceFile,
node?: ts.Node
): void {
const warning: ProtocolWarning = {
type,
message,
file: sourceFile?.fileName,
line: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).line + 1 : undefined,
column: node ? ts.getLineAndCharacterOfPosition(sourceFile!, node.getStart()).character + 1 : undefined
};
this.warnings.push(warning);
}
}

View File

@@ -1,5 +0,0 @@
/**
* 协议分析器导出
*/
export * from './TypeScriptAnalyzer';

View File

@@ -1,576 +0,0 @@
/**
* 协议推导引擎
*
* 负责从分析结果推导出最优的序列化协议,
* 包括类型优化、字段重排序、兼容性检查等
*/
import {
ComponentProtocol,
ProtocolField,
ProtocolRpc,
SerializeType,
ProtocolSchema,
ProtocolError,
ProtocolWarning
} from '../types/ProtocolTypes';
/**
* 优化选项
*/
export interface InferenceOptions {
/** 是否启用字段重排序优化 */
enableFieldReordering?: boolean;
/** 是否启用类型提升优化 */
enableTypePromotion?: boolean;
/** 是否启用批量处理优化 */
enableBatchOptimization?: boolean;
/** 是否启用向后兼容检查 */
enableCompatibilityCheck?: boolean;
/** 最大字段数量限制 */
maxFieldCount?: number;
/** 最大 RPC 数量限制 */
maxRpcCount?: number;
}
/**
* 协议推导引擎
*/
export class ProtocolInferenceEngine {
private options: Required<InferenceOptions>;
private errors: ProtocolError[] = [];
private warnings: ProtocolWarning[] = [];
constructor(options: InferenceOptions = {}) {
this.options = {
enableFieldReordering: true,
enableTypePromotion: true,
enableBatchOptimization: true,
enableCompatibilityCheck: true,
maxFieldCount: 100,
maxRpcCount: 50,
...options
};
}
/**
* 推导协议模式
*/
public inferSchema(components: ComponentProtocol[], version: string = '1.0.0'): ProtocolSchema {
this.errors = [];
this.warnings = [];
const optimizedComponents = new Map<string, ComponentProtocol>();
const globalTypes = new Map<string, ProtocolField[]>();
// 第一遍:基础优化和验证
for (const component of components) {
const optimized = this.optimizeComponent(component);
if (optimized) {
optimizedComponents.set(component.typeName, optimized);
}
}
// 第二遍:跨组件优化
this.performCrossComponentOptimizations(optimizedComponents);
// 提取全局类型
this.extractGlobalTypes(optimizedComponents, globalTypes);
const schema: ProtocolSchema = {
version,
components: optimizedComponents,
types: globalTypes,
compatibility: {
minVersion: version,
maxVersion: version
}
};
// 最终验证
this.validateSchema(schema);
return schema;
}
/**
* 优化单个组件
*/
private optimizeComponent(component: ComponentProtocol): ComponentProtocol | null {
// 验证组件
if (!this.validateComponent(component)) {
return null;
}
const optimized: ComponentProtocol = {
...component,
syncVars: [...component.syncVars],
rpcs: [...component.rpcs]
};
// 优化 SyncVar 字段
if (this.options.enableFieldReordering) {
optimized.syncVars = this.optimizeFieldOrdering(optimized.syncVars);
}
if (this.options.enableTypePromotion) {
optimized.syncVars = this.optimizeFieldTypes(optimized.syncVars);
}
// 优化 RPC 方法
optimized.rpcs = this.optimizeRpcs(optimized.rpcs);
// 启用批量处理优化
if (this.options.enableBatchOptimization) {
this.inferBatchOptimization(optimized);
}
return optimized;
}
/**
* 验证组件
*/
private validateComponent(component: ComponentProtocol): boolean {
let isValid = true;
// 检查字段数量限制
if (component.syncVars.length > this.options.maxFieldCount) {
this.addError('semantic', `Component ${component.typeName} has too many SyncVars (${component.syncVars.length}/${this.options.maxFieldCount})`);
isValid = false;
}
// 检查 RPC 数量限制
if (component.rpcs.length > this.options.maxRpcCount) {
this.addError('semantic', `Component ${component.typeName} has too many RPCs (${component.rpcs.length}/${this.options.maxRpcCount})`);
isValid = false;
}
// 检查字段名冲突
const fieldNames = new Set<string>();
for (const field of component.syncVars) {
if (fieldNames.has(field.name)) {
this.addError('semantic', `Duplicate SyncVar name: ${field.name} in ${component.typeName}`);
isValid = false;
}
fieldNames.add(field.name);
}
// 检查 RPC 名冲突
const rpcNames = new Set<string>();
for (const rpc of component.rpcs) {
if (rpcNames.has(rpc.name)) {
this.addError('semantic', `Duplicate RPC name: ${rpc.name} in ${component.typeName}`);
isValid = false;
}
rpcNames.add(rpc.name);
}
return isValid;
}
/**
* 优化字段顺序
* 将频繁变化的字段和固定大小的字段排在前面,以提高序列化效率
*/
private optimizeFieldOrdering(fields: ProtocolField[]): ProtocolField[] {
const optimized = [...fields];
// 按照优化策略排序
optimized.sort((a, b) => {
// 优先级高的在前
const priorityA = this.getFieldPriority(a);
const priorityB = this.getFieldPriority(b);
if (priorityA !== priorityB) {
return priorityB - priorityA; // 优先级高的在前
}
// 固定大小类型在前
const fixedA = this.isFixedSizeType(a.type) ? 1 : 0;
const fixedB = this.isFixedSizeType(b.type) ? 1 : 0;
if (fixedA !== fixedB) {
return fixedB - fixedA;
}
// 按类型大小排序,小的在前
const sizeA = this.getTypeSize(a.type);
const sizeB = this.getTypeSize(b.type);
return sizeA - sizeB;
});
// 重新分配字段 ID保持顺序
optimized.forEach((field, index) => {
field.id = index + 1;
});
return optimized;
}
/**
* 优化字段类型
* 将通用类型提升为更高效的序列化类型
*/
private optimizeFieldTypes(fields: ProtocolField[]): ProtocolField[] {
return fields.map(field => {
const optimized = { ...field };
// 类型提升规则
switch (field.type) {
case SerializeType.FLOAT64:
// 检查是否可以使用 float32
if (this.canUseFloat32(field)) {
optimized.type = SerializeType.FLOAT32;
this.addWarning('performance', `Promoted field ${field.name} from float64 to float32`);
}
break;
case SerializeType.INT64:
// 检查是否可以使用 int32
if (this.canUseInt32(field)) {
optimized.type = SerializeType.INT32;
this.addWarning('performance', `Promoted field ${field.name} from int64 to int32`);
}
break;
case SerializeType.JSON:
// 检查是否可以使用更高效的类型
const betterType = this.inferBetterType(field);
if (betterType && betterType !== SerializeType.JSON) {
optimized.type = betterType;
this.addWarning('performance', `Promoted field ${field.name} from JSON to ${betterType}`);
}
break;
}
return optimized;
});
}
/**
* 优化 RPC 方法
*/
private optimizeRpcs(rpcs: ProtocolRpc[]): ProtocolRpc[] {
return rpcs.map(rpc => {
const optimized = { ...rpc };
// 优化参数类型
optimized.parameters = rpc.parameters.map(param => ({
...param,
type: this.optimizeParameterType(param.type)
}));
// 设置默认选项
if (optimized.reliable === undefined) {
optimized.reliable = rpc.type === 'server-rpc'; // 服务端 RPC 默认可靠
}
return optimized;
});
}
/**
* 推导批量处理优化
*/
private inferBatchOptimization(component: ComponentProtocol): void {
// 检查是否适合批量处理
const hasManyInstances = this.estimateInstanceCount(component) > 10;
const hasSimpleTypes = component.syncVars.every(field =>
this.isSimpleType(field.type) && !field.repeated
);
if (hasManyInstances && hasSimpleTypes) {
component.batchEnabled = true;
this.addWarning('performance', `Enabled batch optimization for ${component.typeName}`);
}
// 检查是否适合增量同步
const hasLargeData = component.syncVars.some(field =>
this.isLargeDataType(field.type) || field.repeated
);
if (hasLargeData) {
component.deltaEnabled = true;
this.addWarning('performance', `Enabled delta synchronization for ${component.typeName}`);
}
}
/**
* 跨组件优化
*/
private performCrossComponentOptimizations(components: Map<string, ComponentProtocol>): void {
// 检查重复字段模式,提取为全局类型
const fieldPatterns = this.findCommonFieldPatterns(Array.from(components.values()));
for (const [pattern, count] of fieldPatterns) {
if (count >= 3) { // 如果有3个或更多组件使用相同模式
this.addWarning('style', `Common field pattern found: ${pattern} (used ${count} times). Consider extracting to a shared type.`);
}
}
// 检查 ID 冲突
this.validateIdUniqueness(Array.from(components.values()));
}
/**
* 提取全局类型
*/
private extractGlobalTypes(
components: Map<string, ComponentProtocol>,
globalTypes: Map<string, ProtocolField[]>
): void {
// 预定义常用游戏类型
globalTypes.set('Vector2', [
{ name: 'x', type: SerializeType.FLOAT32, id: 1 },
{ name: 'y', type: SerializeType.FLOAT32, id: 2 }
]);
globalTypes.set('Vector3', [
{ name: 'x', type: SerializeType.FLOAT32, id: 1 },
{ name: 'y', type: SerializeType.FLOAT32, id: 2 },
{ name: 'z', type: SerializeType.FLOAT32, id: 3 }
]);
globalTypes.set('Quaternion', [
{ name: 'x', type: SerializeType.FLOAT32, id: 1 },
{ name: 'y', type: SerializeType.FLOAT32, id: 2 },
{ name: 'z', type: SerializeType.FLOAT32, id: 3 },
{ name: 'w', type: SerializeType.FLOAT32, id: 4 }
]);
globalTypes.set('Color', [
{ name: 'r', type: SerializeType.FLOAT32, id: 1 },
{ name: 'g', type: SerializeType.FLOAT32, id: 2 },
{ name: 'b', type: SerializeType.FLOAT32, id: 3 },
{ name: 'a', type: SerializeType.FLOAT32, id: 4, optional: true, defaultValue: 1.0 }
]);
}
/**
* 验证协议模式
*/
private validateSchema(schema: ProtocolSchema): void {
// 检查版本兼容性
if (this.options.enableCompatibilityCheck) {
this.validateCompatibility(schema);
}
// 检查全局一致性
this.validateGlobalConsistency(schema);
}
// 辅助方法
private getFieldPriority(field: ProtocolField): number {
// 根据字段名推断优先级
const highPriorityNames = ['position', 'rotation', 'health', 'transform'];
const mediumPriorityNames = ['velocity', 'speed', 'direction'];
const fieldName = field.name.toLowerCase();
if (highPriorityNames.some(name => fieldName.includes(name))) {
return 10;
}
if (mediumPriorityNames.some(name => fieldName.includes(name))) {
return 5;
}
return 1;
}
private isFixedSizeType(type: SerializeType): boolean {
const fixedTypes = [
SerializeType.BOOLEAN,
SerializeType.INT8, SerializeType.UINT8,
SerializeType.INT16, SerializeType.UINT16,
SerializeType.INT32, SerializeType.UINT32,
SerializeType.INT64, SerializeType.UINT64,
SerializeType.FLOAT32, SerializeType.FLOAT64,
SerializeType.VECTOR2, SerializeType.VECTOR3,
SerializeType.QUATERNION, SerializeType.COLOR
];
return fixedTypes.includes(type);
}
private getTypeSize(type: SerializeType): number {
const sizes = {
[SerializeType.BOOLEAN]: 1,
[SerializeType.INT8]: 1,
[SerializeType.UINT8]: 1,
[SerializeType.INT16]: 2,
[SerializeType.UINT16]: 2,
[SerializeType.INT32]: 4,
[SerializeType.UINT32]: 4,
[SerializeType.INT64]: 8,
[SerializeType.UINT64]: 8,
[SerializeType.FLOAT32]: 4,
[SerializeType.FLOAT64]: 8,
[SerializeType.VECTOR2]: 8,
[SerializeType.VECTOR3]: 12,
[SerializeType.QUATERNION]: 16,
[SerializeType.COLOR]: 16,
[SerializeType.STRING]: 100, // 估算
[SerializeType.BYTES]: 100,
[SerializeType.ARRAY]: 200,
[SerializeType.MAP]: 200,
[SerializeType.OBJECT]: 500,
[SerializeType.JSON]: 1000
};
return sizes[type] || 100;
}
private canUseFloat32(field: ProtocolField): boolean {
// 简单启发式:位置、旋转等游戏相关字段通常可以使用 float32
const float32FriendlyNames = ['position', 'rotation', 'scale', 'velocity', 'speed'];
return float32FriendlyNames.some(name => field.name.toLowerCase().includes(name));
}
private canUseInt32(field: ProtocolField): boolean {
// 大多数游戏中的整数值都可以用 int32 表示
const int32FriendlyNames = ['id', 'count', 'level', 'score', 'health', 'mana'];
return int32FriendlyNames.some(name => field.name.toLowerCase().includes(name));
}
private inferBetterType(field: ProtocolField): SerializeType | null {
// 根据字段名推断更好的类型
const fieldName = field.name.toLowerCase();
if (fieldName.includes('position') || fieldName.includes('vector')) {
return SerializeType.VECTOR3;
}
if (fieldName.includes('rotation') || fieldName.includes('quaternion')) {
return SerializeType.QUATERNION;
}
if (fieldName.includes('color')) {
return SerializeType.COLOR;
}
return null;
}
private optimizeParameterType(type: SerializeType): SerializeType {
// RPC 参数类型优化
if (type === SerializeType.FLOAT64) {
return SerializeType.FLOAT32; // RPC 通常不需要高精度
}
if (type === SerializeType.INT64) {
return SerializeType.INT32;
}
return type;
}
private estimateInstanceCount(component: ComponentProtocol): number {
// 基于组件名称估算实例数量
const highVolumeNames = ['transform', 'position', 'movement', 'particle'];
const mediumVolumeNames = ['player', 'enemy', 'bullet', 'item'];
const typeName = component.typeName.toLowerCase();
if (highVolumeNames.some(name => typeName.includes(name))) {
return 100;
}
if (mediumVolumeNames.some(name => typeName.includes(name))) {
return 20;
}
return 5;
}
private isSimpleType(type: SerializeType): boolean {
const simpleTypes = [
SerializeType.BOOLEAN,
SerializeType.INT32, SerializeType.UINT32,
SerializeType.FLOAT32,
SerializeType.VECTOR2, SerializeType.VECTOR3,
SerializeType.QUATERNION
];
return simpleTypes.includes(type);
}
private isLargeDataType(type: SerializeType): boolean {
const largeTypes = [
SerializeType.STRING,
SerializeType.BYTES,
SerializeType.ARRAY,
SerializeType.MAP,
SerializeType.OBJECT,
SerializeType.JSON
];
return largeTypes.includes(type);
}
private findCommonFieldPatterns(components: ComponentProtocol[]): Map<string, number> {
const patterns = new Map<string, number>();
for (const component of components) {
const pattern = component.syncVars
.map(field => `${field.name}:${field.type}`)
.sort()
.join(',');
patterns.set(pattern, (patterns.get(pattern) || 0) + 1);
}
return patterns;
}
private validateIdUniqueness(components: ComponentProtocol[]): void {
const fieldIds = new Map<number, string>();
const rpcIds = new Map<number, string>();
for (const component of components) {
// 检查字段 ID 冲突
for (const field of component.syncVars) {
const existing = fieldIds.get(field.id);
if (existing && existing !== `${component.typeName}.${field.name}`) {
this.addError('semantic', `Field ID conflict: ${field.id} used by both ${existing} and ${component.typeName}.${field.name}`);
}
fieldIds.set(field.id, `${component.typeName}.${field.name}`);
}
// 检查 RPC ID 冲突
for (const rpc of component.rpcs) {
const existing = rpcIds.get(rpc.id);
if (existing && existing !== `${component.typeName}.${rpc.name}`) {
this.addError('semantic', `RPC ID conflict: ${rpc.id} used by both ${existing} and ${component.typeName}.${rpc.name}`);
}
rpcIds.set(rpc.id, `${component.typeName}.${rpc.name}`);
}
}
}
private validateCompatibility(schema: ProtocolSchema): void {
// 这里可以添加向后兼容性检查逻辑
// 比如检查字段删除、类型变更等
}
private validateGlobalConsistency(schema: ProtocolSchema): void {
// 检查全局类型的一致性使用
for (const [typeName, fields] of schema.types) {
const usageCount = Array.from(schema.components.values())
.flatMap(comp => comp.syncVars)
.filter(field => field.type === typeName as SerializeType)
.length;
if (usageCount === 0) {
this.addWarning('style', `Global type ${typeName} is defined but not used`);
}
}
}
private addError(type: ProtocolError['type'], message: string): void {
this.errors.push({ type, message });
}
private addWarning(type: ProtocolWarning['type'], message: string): void {
this.warnings.push({ type, message });
}
public getErrors(): ProtocolError[] {
return [...this.errors];
}
public getWarnings(): ProtocolWarning[] {
return [...this.warnings];
}
}

View File

@@ -1,5 +0,0 @@
/**
* 协议编译器导出
*/
export * from './ProtocolInferenceEngine';

View File

@@ -1,8 +0,0 @@
/**
* 协议编译器模块导出
*/
export * from './types';
// 协议分析器需要开发时依赖,暂时禁用
// export * from './analyzer';
export * from './compiler';

View File

@@ -1,289 +0,0 @@
/**
* 网络协议编译系统类型定义
*/
/**
* 序列化类型枚举
*/
export enum SerializeType {
// 基础类型
BOOLEAN = 'boolean',
INT8 = 'int8',
UINT8 = 'uint8',
INT16 = 'int16',
UINT16 = 'uint16',
INT32 = 'int32',
UINT32 = 'uint32',
INT64 = 'int64',
UINT64 = 'uint64',
FLOAT32 = 'float32',
FLOAT64 = 'float64',
STRING = 'string',
BYTES = 'bytes',
// 常用游戏类型
VECTOR2 = 'Vector2',
VECTOR3 = 'Vector3',
QUATERNION = 'Quaternion',
COLOR = 'Color',
// 容器类型
ARRAY = 'array',
MAP = 'map',
// 复杂类型
OBJECT = 'object',
JSON = 'json'
}
/**
* 字段定义
*/
export interface ProtocolField {
/** 字段名 */
name: string;
/** 序列化类型 */
type: SerializeType;
/** 字段ID用于向后兼容 */
id: number;
/** 是否可选 */
optional?: boolean;
/** 是否重复(数组) */
repeated?: boolean;
/** 元素类型(用于数组和映射) */
elementType?: SerializeType;
/** 键类型(用于映射) */
keyType?: SerializeType;
/** 值类型(用于映射) */
valueType?: SerializeType;
/** 默认值 */
defaultValue?: any;
/** 自定义序列化器 */
customSerializer?: string;
}
/**
* RPC 参数定义
*/
export interface RpcParameter {
/** 参数名 */
name: string;
/** 参数类型 */
type: SerializeType;
/** 是否可选 */
optional?: boolean;
/** 是否为数组 */
isArray?: boolean;
}
/**
* RPC 定义
*/
export interface ProtocolRpc {
/** 方法名 */
name: string;
/** RPC ID */
id: number;
/** RPC 类型 */
type: 'client-rpc' | 'server-rpc';
/** 参数列表 */
parameters: RpcParameter[];
/** 返回类型 */
returnType?: SerializeType;
/** 是否需要权限 */
requiresAuth?: boolean;
/** 是否可靠传输 */
reliable?: boolean;
/** 频率限制 */
rateLimit?: number;
}
/**
* 网络组件协议定义
*/
export interface ComponentProtocol {
/** 组件类型名 */
typeName: string;
/** 协议版本 */
version: number;
/** SyncVar 字段 */
syncVars: ProtocolField[];
/** RPC 方法 */
rpcs: ProtocolRpc[];
/** 是否启用批量处理 */
batchEnabled?: boolean;
/** 是否启用增量同步 */
deltaEnabled?: boolean;
}
/**
* 协议模式定义
*/
export interface ProtocolSchema {
/** 模式版本 */
version: string;
/** 组件协议映射 */
components: Map<string, ComponentProtocol>;
/** 全局类型定义 */
types: Map<string, ProtocolField[]>;
/** 协议兼容性信息 */
compatibility: {
minVersion: string;
maxVersion: string;
};
}
/**
* 序列化器接口
*/
export interface IProtocolSerializer {
/** 序列化单个对象 */
serialize(obj: any, type: SerializeType): Uint8Array;
/** 反序列化单个对象 */
deserialize(data: Uint8Array, type: SerializeType): any;
/** 批量序列化 */
serializeBatch(objects: any[], type: SerializeType): Uint8Array;
/** 批量反序列化 */
deserializeBatch(data: Uint8Array, type: SerializeType): any[];
/** 增量序列化 */
serializeDelta(oldObj: any, newObj: any, type: SerializeType): Uint8Array | null;
/** 应用增量 */
applyDelta(baseObj: any, delta: Uint8Array, type: SerializeType): any;
}
/**
* 协议编译器配置
*/
export interface ProtocolCompilerConfig {
/** 输入目录 */
inputDir: string;
/** 输出目录 */
outputDir: string;
/** TypeScript 配置文件路径 */
tsconfigPath?: string;
/** 是否启用优化 */
optimize?: boolean;
/** 是否生成调试信息 */
debug?: boolean;
/** 自定义类型映射 */
typeMapping?: Map<string, SerializeType>;
/** 排除的文件模式 */
excludePatterns?: string[];
}
/**
* 协议分析结果
*/
export interface ProtocolAnalysisResult {
/** 分析的文件列表 */
files: string[];
/** 发现的网络组件 */
components: ComponentProtocol[];
/** 类型依赖图 */
dependencies: Map<string, string[]>;
/** 分析错误 */
errors: ProtocolError[];
/** 分析警告 */
warnings: ProtocolWarning[];
}
/**
* 协议错误
*/
export interface ProtocolError {
/** 错误类型 */
type: 'syntax' | 'type' | 'semantic' | 'compatibility';
/** 错误消息 */
message: string;
/** 文件路径 */
file?: string;
/** 行号 */
line?: number;
/** 列号 */
column?: number;
}
/**
* 协议警告
*/
export interface ProtocolWarning {
/** 警告类型 */
type: 'performance' | 'compatibility' | 'style';
/** 警告消息 */
message: string;
/** 文件路径 */
file?: string;
/** 行号 */
line?: number;
/** 列号 */
column?: number;
}
/**
* 代码生成选项
*/
export interface CodeGenerationOptions {
/** 目标平台 */
platform: 'node' | 'browser' | 'universal';
/** 代码风格 */
style: 'typescript' | 'javascript';
/** 是否生成类型定义 */
generateTypes?: boolean;
/** 是否生成文档 */
generateDocs?: boolean;
/** 模块格式 */
moduleFormat?: 'es' | 'cjs' | 'umd';
/** 压缩级别 */
minification?: 'none' | 'basic' | 'aggressive';
}
/**
* 运行时协议信息
*/
export interface RuntimeProtocolInfo {
/** 协议版本 */
version: string;
/** 组件数量 */
componentCount: number;
/** 总字段数 */
fieldCount: number;
/** 总 RPC 数 */
rpcCount: number;
/** 内存使用情况 */
memoryUsage: {
schemas: number;
serializers: number;
cache: number;
};
/** 性能统计 */
performance: {
serializeTime: number;
deserializeTime: number;
cacheHits: number;
cacheMisses: number;
};
}
/**
* 协议事件类型
*/
export type ProtocolEventType =
| 'protocol-loaded'
| 'protocol-updated'
| 'serializer-registered'
| 'compatibility-check'
| 'performance-warning';
/**
* 协议事件数据
*/
export interface ProtocolEventData {
type: ProtocolEventType;
timestamp: number;
data: any;
}
/**
* 协议事件处理器
*/
export type ProtocolEventHandler = (event: ProtocolEventData) => void;

View File

@@ -1,5 +0,0 @@
/**
* 协议类型定义导出
*/
export * from './ProtocolTypes';

View File

@@ -0,0 +1,283 @@
/**
* 网络消息协议定义
*/
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;

View File

@@ -1,355 +0,0 @@
/**
* 网络序列化器
*
* 提供高效的网络消息序列化和反序列化
*/
import { INetworkSerializer, NetworkValue, SerializationSchema } from '../types/NetworkTypes';
/**
* 序列化类型映射
*/
interface SerializationTypeMap {
[typeName: string]: SerializationSchema<any>;
}
/**
* 基础网络序列化器实现
*/
export class NetworkSerializer implements INetworkSerializer {
private typeMap: SerializationTypeMap = {};
constructor() {
this.registerBuiltinTypes();
}
/**
* 注册内置类型
*/
private registerBuiltinTypes(): void {
// 基础类型
this.registerType<string>('string', {
serialize: (str: string) => new TextEncoder().encode(str),
deserialize: (data: Uint8Array) => new TextDecoder().decode(data),
getSize: (str: string) => new TextEncoder().encode(str).length
});
this.registerType<number>('number', {
serialize: (num: number) => {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setFloat64(0, num);
return new Uint8Array(buffer);
},
deserialize: (data: Uint8Array) => {
const view = new DataView(data.buffer);
return view.getFloat64(0);
},
getSize: () => 8
});
this.registerType<boolean>('boolean', {
serialize: (bool: boolean) => new Uint8Array([bool ? 1 : 0]),
deserialize: (data: Uint8Array) => data[0] === 1,
getSize: () => 1
});
this.registerType<number>('int32', {
serialize: (num: number) => {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt32(0, num);
return new Uint8Array(buffer);
},
deserialize: (data: Uint8Array) => {
const view = new DataView(data.buffer);
return view.getInt32(0);
},
getSize: () => 4
});
this.registerType<number>('uint32', {
serialize: (num: number) => {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, num);
return new Uint8Array(buffer);
},
deserialize: (data: Uint8Array) => {
const view = new DataView(data.buffer);
return view.getUint32(0);
},
getSize: () => 4
});
// Vector3 类型
this.registerType<{x: number, y: number, z?: number}>('Vector3', {
serialize: (vec: { x: number; y: number; z?: number }) => {
const buffer = new ArrayBuffer(12);
const view = new DataView(buffer);
view.setFloat32(0, vec.x);
view.setFloat32(4, vec.y);
view.setFloat32(8, vec.z || 0);
return new Uint8Array(buffer);
},
deserialize: (data: Uint8Array) => {
const view = new DataView(data.buffer);
return {
x: view.getFloat32(0),
y: view.getFloat32(4),
z: view.getFloat32(8)
};
},
getSize: () => 12
});
// JSON 类型(用于复杂对象)
this.registerType('json', {
serialize: (obj: any) => {
const jsonStr = JSON.stringify(obj);
return new TextEncoder().encode(jsonStr);
},
deserialize: (data: Uint8Array) => {
const jsonStr = new TextDecoder().decode(data);
return JSON.parse(jsonStr);
},
getSize: (obj: any) => {
const jsonStr = JSON.stringify(obj);
return new TextEncoder().encode(jsonStr).length;
}
});
}
/**
* 注册序列化类型
*/
public registerType<T = NetworkValue>(typeName: string, typeSchema: SerializationSchema<T>): void {
if (typeof typeSchema.serialize !== 'function' ||
typeof typeSchema.deserialize !== 'function') {
throw new Error(`Invalid type schema for ${typeName}: must have serialize and deserialize methods`);
}
this.typeMap[typeName] = {
serialize: typeSchema.serialize as any,
deserialize: typeSchema.deserialize as any,
getSize: typeSchema.getSize as any || ((obj: any) => this.serialize(obj, typeName).length)
};
}
/**
* 序列化对象
*/
public serialize(obj: any, type?: string): Uint8Array {
if (type && this.typeMap[type]) {
return this.typeMap[type].serialize(obj);
}
// 自动类型检测
const detectedType = this.detectType(obj);
if (this.typeMap[detectedType]) {
return this.typeMap[detectedType].serialize(obj);
}
// 默认使用 JSON 序列化
const jsonHandler = this.typeMap['json'];
if (jsonHandler?.serialize) {
return jsonHandler.serialize(obj);
}
// 最终回退方案
return new TextEncoder().encode(JSON.stringify(obj));
}
/**
* 反序列化对象
*/
public deserialize<T = any>(data: Uint8Array, type?: string): T {
if (type && this.typeMap[type]) {
return this.typeMap[type].deserialize(data);
}
// 如果没有指定类型,尝试使用 JSON 反序列化
try {
const jsonHandler = this.typeMap['json'];
if (jsonHandler?.deserialize) {
return jsonHandler.deserialize(data);
}
// 最终回退方案
const jsonString = new TextDecoder().decode(data);
return JSON.parse(jsonString);
} catch (error) {
throw new Error(`Failed to deserialize data: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* 获取序列化后的大小
*/
public getSerializedSize(obj: any, type?: string): number {
if (type && this.typeMap[type]?.getSize) {
return this.typeMap[type].getSize(obj);
}
const detectedType = this.detectType(obj);
if (this.typeMap[detectedType]?.getSize) {
return this.typeMap[detectedType].getSize(obj);
}
const jsonHandler = this.typeMap['json'];
return jsonHandler?.getSize ? jsonHandler.getSize(obj) : JSON.stringify(obj).length;
}
/**
* 自动检测对象类型
*/
private detectType(obj: any): string {
if (typeof obj === 'string') return 'string';
if (typeof obj === 'number') return 'number';
if (typeof obj === 'boolean') return 'boolean';
if (obj && typeof obj === 'object') {
// 检测 Vector3 类型
if ('x' in obj && 'y' in obj && typeof obj.x === 'number' && typeof obj.y === 'number') {
return 'Vector3';
}
}
return 'json';
}
/**
* 批量序列化多个值
*/
public serializeBatch(values: Array<{ value: any; type?: string }>): Uint8Array {
const serializedParts: Uint8Array[] = [];
let totalSize = 0;
// 序列化每个值
for (const item of values) {
const serialized = this.serialize(item.value, item.type);
serializedParts.push(serialized);
totalSize += serialized.length + 4; // +4 为长度信息
}
// 创建总缓冲区
const result = new Uint8Array(totalSize + 4); // +4 为值的数量
const view = new DataView(result.buffer);
let offset = 0;
// 写入值的数量
view.setUint32(offset, values.length);
offset += 4;
// 写入每个序列化的值
for (const serialized of serializedParts) {
// 写入长度
view.setUint32(offset, serialized.length);
offset += 4;
// 写入数据
result.set(serialized, offset);
offset += serialized.length;
}
return result;
}
/**
* 批量反序列化
*/
public deserializeBatch(data: Uint8Array, types?: string[]): any[] {
const view = new DataView(data.buffer);
let offset = 0;
// 读取值的数量
const count = view.getUint32(offset);
offset += 4;
const results: any[] = [];
// 读取每个值
for (let i = 0; i < count; i++) {
// 读取长度
const length = view.getUint32(offset);
offset += 4;
// 读取数据
const valueData = data.slice(offset, offset + length);
offset += length;
// 反序列化
const type = types?.[i];
const value = this.deserialize(valueData, type);
results.push(value);
}
return results;
}
/**
* 压缩序列化数据
*/
public compress(data: Uint8Array): Uint8Array {
// 这里可以集成压缩算法,如 LZ4、gzip 等
// 目前返回原数据
return data;
}
/**
* 解压缩数据
*/
public decompress(data: Uint8Array): Uint8Array {
// 这里可以集成解压缩算法
// 目前返回原数据
return data;
}
/**
* 创建增量序列化数据
*/
public serializeDelta(oldValue: any, newValue: any, type?: string): Uint8Array | null {
// 基础实现:如果值相同则返回 null否则序列化新值
if (this.isEqual(oldValue, newValue)) {
return null;
}
return this.serialize(newValue, type);
}
/**
* 应用增量数据
*/
public applyDelta(_baseValue: any, deltaData: Uint8Array, type?: string): any {
// 基础实现:直接反序列化增量数据
// baseValue 在更复杂的增量实现中会被使用
return this.deserialize(deltaData, type);
}
/**
* 检查两个值是否相等
*/
private isEqual(a: any, b: any): boolean {
if (a === b) return true;
if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
return JSON.stringify(a) === JSON.stringify(b);
}
return false;
}
/**
* 获取已注册的类型列表
*/
public getRegisteredTypes(): string[] {
return Object.keys(this.typeMap);
}
/**
* 检查类型是否已注册
*/
public hasType(typeName: string): boolean {
return typeName in this.typeMap;
}
}

View File

@@ -1,5 +0,0 @@
/**
* 序列化工具导出
*/
export * from './NetworkSerializer';

View File

@@ -1,375 +1,251 @@
/**
* 网络核心类型定义
* 网络核心类型定义
*/
// 通用类型定义
export type NetworkValue = string | number | boolean | NetworkValue[] | { [key: string]: NetworkValue };
export type SerializableObject = Record<string, NetworkValue>;
export type Constructor<T = {}> = new (...args: unknown[]) => T;
export type MethodDecorator<T = unknown> = (target: unknown, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
/**
* 网络消息类型枚举
*/
export enum MessageType {
// 连接管理
CONNECT = 'connect',
DISCONNECT = 'disconnect',
HEARTBEAT = 'heartbeat',
// 装饰器目标类型 - 使用更灵活的定义
export interface DecoratorTarget extends Record<string, unknown> {
constructor: Constructor;
}
// 数据同步
SYNC_VAR = 'sync_var',
SYNC_BATCH = 'sync_batch',
SYNC_SNAPSHOT = 'sync_snapshot',
// 网络数据类型约束
export interface SerializedData {
type: string;
data: Uint8Array;
checksum?: string;
}
// RPC调用
RPC_CALL = 'rpc_call',
RPC_RESPONSE = 'rpc_response',
// RPC参数类型
export type RpcParameterType = NetworkValue;
export type RpcReturnType = NetworkValue | void | Promise<NetworkValue | void>;
// 实体管理
ENTITY_CREATE = 'entity_create',
ENTITY_DESTROY = 'entity_destroy',
ENTITY_UPDATE = 'entity_update',
// 序列化模式接口 - 使用泛型支持特定类型
export interface SerializationSchema<T = NetworkValue> {
serialize: (obj: T) => Uint8Array;
deserialize: (data: Uint8Array) => T;
getSize?: (obj: T) => number;
// 房间管理
JOIN_ROOM = 'join_room',
LEAVE_ROOM = 'leave_room',
ROOM_STATE = 'room_state',
// 游戏事件
GAME_EVENT = 'game_event',
// 系统消息
ERROR = 'error',
WARNING = 'warning',
INFO = 'info'
}
/**
* 网络端类型
* 网络消息基础接口
*/
export type NetworkSide = 'client' | 'server' | 'host';
export interface INetworkMessage {
/** 消息类型 */
type: MessageType;
/** 消息唯一ID */
messageId: string;
/** 时间戳 */
timestamp: number;
/** 发送者ID */
senderId: string;
/** 消息数据 */
data: any;
/** 是否可靠传输 */
reliable?: boolean;
/** 消息优先级 */
priority?: number;
}
/**
* 网络连接状态
* 同步权限类型
*/
export type NetworkConnectionState =
| 'disconnected'
| 'connecting'
| 'connected'
| 'disconnecting'
| 'reconnecting'
| 'failed';
export enum AuthorityType {
/** 服务端权限 */
Server = 'server',
/** 客户端权限 */
Client = 'client',
/** 共享权限 */
Shared = 'shared'
}
/**
* 网络消息类型
* 网络作用域
*/
export type NetworkMessageType =
| 'syncvar'
| 'client-rpc'
| 'server-rpc'
| 'spawn'
| 'destroy'
| 'ownership'
| 'scene-change'
| 'snapshot'
| 'ping'
| 'custom';
export enum NetworkScope {
/** 全局可见 */
Global = 'global',
/** 房间内可见 */
Room = 'room',
/** 仅拥有者可见 */
Owner = 'owner',
/** 附近玩家可见 */
Nearby = 'nearby',
/** 自定义作用域 */
Custom = 'custom'
}
/**
* 网络配置
* 同步模式
*/
export interface NetworkConfig {
/** 端口号 */
port: number;
/** 主机地址 */
host: string;
/** 最大连接数 */
maxConnections: number;
/** 同步频率 (Hz) */
syncRate: number;
/** 快照频率 (Hz) */
snapshotRate: number;
/** 是否启用压缩 */
compression: boolean;
/** 是否启用加密 */
encryption: boolean;
/** 网络超时时间 (ms) */
timeout: number;
/** 重连尝试次数 */
maxReconnectAttempts: number;
/** 重连间隔 (ms) */
reconnectInterval: number;
export 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 NetworkStats {
/** 连接数量 */
connectionCount: number;
/** 已发送字节数 */
export interface INetworkStats {
/** 总发送字节数 */
bytesSent: number;
/** 接收字节数 */
/** 接收字节数 */
bytesReceived: number;
/** 发送消息数 */
/** 发送消息数 */
messagesSent: number;
/** 接收消息数 */
/** 接收消息数 */
messagesReceived: number;
/** 平均延迟 (ms) */
/** 平均延迟 */
averageLatency: number;
/** 丢包率 (%) */
/** 丢包率 */
packetLoss: number;
/** 带宽使用率 (bytes/s) */
bandwidth: number;
/** 连接时长 */
connectionTime: number;
}
/**
* 网络消息基类
* 向量2D
*/
export interface NetworkMessage {
/** 消息类型 */
type: NetworkMessageType;
/** 网络对象ID */
networkId: number;
/** 消息数据 */
data: SerializableObject;
/** 时间戳 */
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;
/** 消息ID */
messageId?: string;
/** 发送者ID */
senderId?: number;
/** 接收者ID (可选,用于定向发送) */
targetId?: number;
/** 是否可靠传输 */
reliable?: boolean;
/** 优先级 */
priority?: number;
}
/**
* SyncVar 消息
*/
export interface SyncVarMessage extends NetworkMessage {
type: 'syncvar';
/** 组件类型名 */
componentType: string;
/** 属性名 */
propertyName: string;
/** 属性值 */
value: NetworkValue;
/** 变化类型 */
changeType?: 'set' | 'add' | 'remove' | 'clear';
}
/**
* RPC 消息
*/
export interface RpcMessage extends NetworkMessage {
type: 'client-rpc' | 'server-rpc';
/** 组件类型名 */
componentType: string;
/** 方法名 */
methodName: string;
/** 参数列表 */
args: RpcParameterType[];
/** RPC ID (用于响应) */
rpcId?: string;
/** 是否需要响应 */
requiresResponse?: boolean;
}
/**
* 对象生成消息
*/
export interface SpawnMessage extends NetworkMessage {
type: 'spawn';
/** 预制体名称或ID */
prefabName: string;
/** 生成位置 */
position?: { x: number; y: number; z?: number };
/** 生成旋转 */
rotation?: { x: number; y: number; z: number; w: number };
/** 所有者ID */
ownerId: number;
/** 初始数据 */
initData?: SerializableObject;
}
/**
* 对象销毁消息
*/
export interface DestroyMessage extends NetworkMessage {
type: 'destroy';
/** 销毁原因 */
reason?: string;
}
/**
* 所有权转移消息
*/
export interface OwnershipMessage extends NetworkMessage {
type: 'ownership';
/** 新所有者ID */
newOwnerId: number;
/** 旧所有者ID */
oldOwnerId: number;
}
/**
* 快照消息
*/
export interface SnapshotMessage extends NetworkMessage {
type: 'snapshot';
/** 快照ID */
snapshotId: number;
/** 快照数据 */
snapshot: SerializableObject;
/** 包含的网络对象ID列表 */
networkIds: number[];
}
/**
* SyncVar 元数据
*/
export interface SyncVarMetadata {
/** 属性名 */
propertyName: string;
/** 是否仅权威端可修改 */
authorityOnly: boolean;
/** 变化回调函数名 */
onChanged?: string;
/** 序列化类型 */
serializeType?: string;
/** 是否使用增量同步 */
deltaSync?: boolean;
/** 同步优先级 */
priority?: number;
}
/**
* RPC 元数据
*/
export interface RpcMetadata {
/** 方法名 */
methodName: string;
/** RPC 类型 */
rpcType: 'client-rpc' | 'server-rpc';
/** 是否需要权限验证 */
requiresAuth?: boolean;
/** 是否可靠传输 */
reliable?: boolean;
/** 是否需要响应 */
requiresResponse?: boolean;
}
/**
* 网络组件元数据
*/
export interface NetworkComponentMetadata {
/** 组件类型名 */
componentType: string;
/** SyncVar 列表 */
syncVars: SyncVarMetadata[];
/** RPC 列表 */
rpcs: RpcMetadata[];
/** 是否自动生成协议 */
autoGenerateProtocol?: boolean;
}
/**
* 网络对象接口
*/
export interface INetworkObject {
/** 网络ID */
networkId: number;
/** 所有者客户端ID */
ownerId: number;
/** 是否拥有权威 */
hasAuthority: boolean;
/** 是否为本地对象 */
isLocal: boolean;
/** 网络组件列表 */
networkComponents: INetworkComponent[];
}
/**
* 网络组件接口
*/
export interface INetworkComponent {
/** 网络对象引用 */
networkObject: INetworkObject | null;
/** 网络ID */
networkId: number;
/** 是否拥有权威 */
hasAuthority: boolean;
/** 组件类型名 */
componentType: string;
}
/**
* 网络传输层接口
*/
export interface INetworkTransport {
/** 启动服务端 */
startServer(config: NetworkConfig): Promise<void>;
/** 连接到服务端 */
connectToServer(host: string, port: number): Promise<void>;
/** 断开连接 */
disconnect(): Promise<void>;
/** 发送消息 */
sendMessage(message: NetworkMessage, targetId?: number): Promise<void>;
/** 广播消息 */
broadcastMessage(message: NetworkMessage, excludeIds?: number[]): Promise<void>;
/** 设置消息处理器 */
onMessage(handler: (message: NetworkMessage, fromId?: number) => void): void;
/** 设置连接事件处理器 */
onConnection(handler: (clientId: number, isConnected: boolean) => void): void;
}
/**
* 序列化器接口
*/
export interface INetworkSerializer {
/** 序列化对象 */
serialize(obj: NetworkValue, type?: string): Uint8Array;
/** 反序列化对象 */
deserialize<T extends NetworkValue = NetworkValue>(data: Uint8Array, type?: string): T;
/** 注册类型 */
registerType<T = NetworkValue>(typeName: string, typeSchema: SerializationSchema<T>): void;
/** 获取序列化后的大小 */
getSerializedSize(obj: NetworkValue, type?: string): number;
}
/**
* 网络事件处理器
*/
export interface NetworkEventHandlers {
/** 连接成功 */
onConnected?: () => void;
/** 连接断开 */
onDisconnected?: (reason?: string) => void;
/** 客户端连接 */
onClientConnected?: (clientId: number) => void;
/** 客户端断开 */
onClientDisconnected?: (clientId: number, reason?: string) => void;
/** 网络错误 */
onError?: (error: Error) => void;
/** 延迟变化 */
onLatencyUpdate?: (latency: number) => void;
}
/**
* 网络调试信息
*/
export interface NetworkDebugInfo {
/** 连接信息 */
connections: {
[clientId: number]: {
id: number;
address: string;
latency: number;
connected: boolean;
lastSeen: number;
};
};
/** 网络对象列表 */
networkObjects: {
[networkId: number]: {
id: number;
ownerId: number;
componentTypes: string[];
syncVarCount: number;
rpcCount: number;
};
};
/** 统计信息 */
stats: NetworkStats;
}

View File

@@ -0,0 +1,228 @@
/**
* 传输层接口定义
*/
/**
* 传输层抽象接口
*/
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: Buffer | string): void;
/**
* 广播数据到所有客户端
* @param data 数据
* @param exclude 排除的客户端ID列表
*/
broadcast(data: Buffer | 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: Buffer | 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: Buffer | string): void;
/**
* 监听服务器消息
* @param handler 处理函数
*/
onMessage(handler: (data: Buffer | 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;
};
}

View File

@@ -1,4 +0,0 @@
/**
* 类型定义导出
*/
export * from './NetworkTypes';

View File

@@ -1,16 +0,0 @@
/**
* reflect-metadata 类型扩展
*/
/// <reference types="reflect-metadata" />
declare namespace Reflect {
function defineMetadata(metadataKey: any, metadataValue: any, target: any, propertyKey?: string | symbol): void;
function getMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any;
function getOwnMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): any;
function hasMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean;
function hasOwnMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean;
function deleteMetadata(metadataKey: any, target: any, propertyKey?: string | symbol): boolean;
function getMetadataKeys(target: any, propertyKey?: string | symbol): any[];
function getOwnMetadataKeys(target: any, propertyKey?: string | symbol): any[];
}

View File

@@ -1,242 +0,0 @@
/**
* 网络工具函数
*/
/**
* 生成网络ID
*/
export function generateNetworkId(): number {
return Math.floor(Math.random() * 0x7FFFFFFF) + 1;
}
/**
* 生成消息ID
*/
export function generateMessageId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
/**
* 计算两点之间的距离
*/
export function calculateDistance(
pos1: { x: number; y: number; z?: number },
pos2: { x: number; y: number; z?: number }
): number {
const dx = pos1.x - pos2.x;
const dy = pos1.y - pos2.y;
const dz = (pos1.z || 0) - (pos2.z || 0);
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* 检查环境是否为 Node.js
*/
export function isNodeEnvironment(): boolean {
return typeof process !== 'undefined' && process.versions && !!process.versions.node;
}
/**
* 检查环境是否为浏览器
*/
export function isBrowserEnvironment(): boolean {
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
}
/**
* 获取时间戳(毫秒)
*/
export function getTimestamp(): number {
return Date.now();
}
/**
* 获取高精度时间戳(如果可用)
*/
export function getHighResTimestamp(): number {
if (typeof performance !== 'undefined' && performance.now) {
return performance.now();
}
return Date.now();
}
/**
* 限制调用频率
*/
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): T {
let inThrottle: boolean;
let context: any;
return (function(this: any, ...args: any[]) {
context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
}) as T;
}
/**
* 防抖函数
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): T {
let timeoutId: NodeJS.Timeout;
let context: any;
return (function(this: any, ...args: any[]) {
context = this;
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(context, args), delay);
}) as T;
}
/**
* 深拷贝对象
*/
export function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime()) as T;
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item)) as T;
}
if (typeof obj === 'object') {
const cloned = {} as T;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
return obj;
}
/**
* 检查对象是否为空
*/
export function isEmpty(obj: any): boolean {
if (obj === null || obj === undefined) return true;
if (typeof obj === 'string' || Array.isArray(obj)) return obj.length === 0;
if (typeof obj === 'object') return Object.keys(obj).length === 0;
return false;
}
/**
* 格式化字节大小
*/
export function formatBytes(bytes: number, decimals = 2): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
/**
* 格式化延迟时间
*/
export function formatLatency(milliseconds: number): string {
if (milliseconds < 1000) {
return `${Math.round(milliseconds)}ms`;
} else {
return `${(milliseconds / 1000).toFixed(1)}s`;
}
}
/**
* 获取网络质量描述
*/
export function getNetworkQuality(latency: number, packetLoss: number): string {
if (latency < 50 && packetLoss < 1) return 'Excellent';
if (latency < 100 && packetLoss < 2) return 'Good';
if (latency < 200 && packetLoss < 5) return 'Fair';
if (latency < 500 && packetLoss < 10) return 'Poor';
return 'Very Poor';
}
/**
* 计算网络统计平均值
*/
export function calculateNetworkAverage(values: number[], maxSamples = 100): number {
if (values.length === 0) return 0;
// 保留最近的样本
const samples = values.slice(-maxSamples);
const sum = samples.reduce((acc, val) => acc + val, 0);
return sum / samples.length;
}
/**
* 验证网络配置
*/
export function validateNetworkConfig(config: any): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (typeof config.port !== 'number' || config.port <= 0 || config.port > 65535) {
errors.push('Port must be a number between 1 and 65535');
}
if (typeof config.host !== 'string' || config.host.length === 0) {
errors.push('Host must be a non-empty string');
}
if (typeof config.maxConnections !== 'number' || config.maxConnections <= 0) {
errors.push('Max connections must be a positive number');
}
if (typeof config.syncRate !== 'number' || config.syncRate <= 0) {
errors.push('Sync rate must be a positive number');
}
return {
valid: errors.length === 0,
errors
};
}
/**
* 重试函数
*/
export async function retry<T>(
fn: () => Promise<T>,
maxAttempts: number,
delay: number = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt === maxAttempts) {
throw lastError;
}
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
throw lastError!;
}

View File

@@ -1,5 +0,0 @@
/**
* 工具函数导出
*/
export * from './NetworkUtils';

View File

@@ -0,0 +1,148 @@
/**
* 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');
});
});
});

View File

@@ -1,9 +1,24 @@
/**
* Jest测试环境设置
*/
// 导入reflect-metadata以支持装饰器
import 'reflect-metadata';
global.beforeEach(() => {
jest.clearAllMocks();
// 全局测试配置
beforeAll(() => {
// 设置测试环境
process.env.NODE_ENV = 'test';
});
global.afterEach(() => {
jest.restoreAllMocks();
afterAll(() => {
// 清理测试环境
});
beforeEach(() => {
// 每个测试前的准备工作
});
afterEach(() => {
// 每个测试后的清理工作
});

View File

@@ -0,0 +1,57 @@
/**
* 类型定义测试
*/
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');
});
});
});

View File

@@ -8,6 +8,7 @@
"outDir": "./bin",
"rootDir": "./src",
"strict": true,
"composite": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
@@ -40,7 +41,11 @@
"node_modules",
"bin",
"**/*.test.ts",
"**/*.spec.ts",
"src/protocol/analyzer/**/*"
"**/*.spec.ts"
],
"references": [
{
"path": "../core"
}
]
}

19
tsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/math" },
{ "path": "./packages/ecs-network" }
],
"files": []
}