更新network库及core库优化

This commit is contained in:
YHH
2025-08-12 09:39:07 +08:00
parent c178e2fbcc
commit 9f76d37a82
117 changed files with 17988 additions and 4099 deletions

View File

@@ -1,126 +0,0 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
console.log('🚀 使用 Rollup 构建ECS网络插件...');
async function main() {
try {
// 清理旧的dist目录
if (fs.existsSync('./dist')) {
console.log('🧹 清理旧的构建文件...');
execSync('rimraf ./dist', { stdio: 'inherit' });
}
// 执行Rollup构建
console.log('📦 执行 Rollup 构建...');
execSync('npx rollup -c rollup.config.cjs', { stdio: 'inherit' });
// 生成package.json
console.log('📋 生成 package.json...');
generatePackageJson();
// 复制其他文件
console.log('📁 复制必要文件...');
copyFiles();
// 输出构建结果
showBuildResults();
console.log('✅ 构建完成!');
console.log('\n🚀 发布命令:');
console.log('cd dist && npm publish');
} catch (error) {
console.error('❌ 构建失败:', error.message);
process.exit(1);
}
}
function generatePackageJson() {
const sourcePackage = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const distPackage = {
name: sourcePackage.name,
version: sourcePackage.version,
description: sourcePackage.description,
main: 'index.cjs',
module: 'index.mjs',
unpkg: 'index.umd.js',
types: 'index.d.ts',
exports: {
'.': {
import: './index.mjs',
require: './index.cjs',
types: './index.d.ts'
}
},
files: [
'index.mjs',
'index.mjs.map',
'index.cjs',
'index.cjs.map',
'index.umd.js',
'index.umd.js.map',
'index.d.ts',
'README.md',
'LICENSE'
],
keywords: [
'ecs',
'networking',
'frame-sync',
'tsrpc',
'serialization',
'multiplayer',
'game-engine',
'typescript',
'rollup'
],
author: sourcePackage.author,
license: sourcePackage.license,
repository: sourcePackage.repository,
bugs: sourcePackage.bugs,
homepage: sourcePackage.homepage,
engines: {
node: '>=16.0.0'
},
dependencies: sourcePackage.dependencies,
peerDependencies: sourcePackage.peerDependencies,
sideEffects: false
};
fs.writeFileSync('./dist/package.json', JSON.stringify(distPackage, null, 2));
}
function copyFiles() {
const filesToCopy = [
{ src: './README.md', dest: './dist/README.md' },
{ src: './LICENSE', dest: './dist/LICENSE' }
];
filesToCopy.forEach(({ src, dest }) => {
if (fs.existsSync(src)) {
fs.copyFileSync(src, dest);
console.log(` ✓ 复制: ${path.basename(dest)}`);
} else {
console.log(` ⚠️ 文件不存在: ${src}`);
}
});
}
function showBuildResults() {
const distDir = './dist';
const files = ['index.mjs', 'index.cjs', 'index.umd.js', 'index.d.ts'];
console.log('\n📊 构建结果:');
files.forEach(file => {
const filePath = path.join(distDir, file);
if (fs.existsSync(filePath)) {
const size = fs.statSync(filePath).size;
console.log(` ${file}: ${(size / 1024).toFixed(1)}KB`);
}
});
}
main().catch(console.error);

View File

@@ -1,73 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testPathIgnorePatterns: ['/node_modules/', '\\.performance\\.test\\.ts$'],
collectCoverage: false,
collectCoverageFrom: [
'src/**/*.ts',
'!src/index.ts',
'!src/**/index.ts',
'!**/*.d.ts',
'!src/**/*.test.ts',
'!src/**/*.spec.ts'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
// 设置覆盖度阈值
coverageThreshold: {
global: {
branches: 6,
functions: 17,
lines: 16,
statements: 15
}
},
verbose: true,
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: {
target: 'ES2020',
module: 'CommonJS',
moduleResolution: 'node',
lib: ['ES2020', 'DOM'],
strict: true,
esModuleInterop: true,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
experimentalDecorators: true,
emitDecoratorMetadata: true,
isolatedModules: false,
allowJs: true,
resolveJsonModule: true,
baseUrl: '.',
paths: {
'@esengine/ecs-framework': ['../core/src'],
'@esengine/ecs-framework/*': ['../core/src/*']
}
},
useESM: false,
}],
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@esengine/ecs-framework$': '<rootDir>/../core/src/index.ts',
'^@esengine/ecs-framework/(.*)$': '<rootDir>/../core/src/$1',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
// 测试超时设置
testTimeout: 10000,
// 清除模块缓存
clearMocks: true,
restoreMocks: true,
// 忽略某些模块
modulePathIgnorePatterns: [
'<rootDir>/bin/',
'<rootDir>/dist/',
'<rootDir>/node_modules/'
]
};

View File

@@ -1,80 +0,0 @@
{
"name": "@esengine/ecs-framework-network",
"version": "1.0.4",
"description": "ECS框架网络插件 - 提供TSRPC网络通信、帧同步和快照功能",
"type": "module",
"main": "bin/index.js",
"types": "bin/index.d.ts",
"files": [
"bin/**/*",
"README.md",
"LICENSE"
],
"keywords": [
"ecs",
"networking",
"frame-sync",
"tsrpc",
"serialization",
"multiplayer",
"game-engine",
"typescript"
],
"scripts": {
"clean": "rimraf bin dist",
"build:ts": "tsc",
"prebuild": "npm run clean",
"build": "npm run build:ts",
"build:watch": "tsc --watch",
"rebuild": "npm run clean && npm run build",
"build:npm": "npm run build && node build-rollup.cjs",
"publish:npm": "npm run build:npm && cd dist && npm publish",
"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",
"publish:patch": "npm version patch && npm run build:npm && cd dist && npm publish",
"publish:minor": "npm version minor && npm run build:npm && cd dist && npm publish",
"publish:major": "npm version major && npm run build:npm && cd dist && npm publish"
},
"author": "yhh",
"license": "MIT",
"dependencies": {
"isomorphic-ws": "^5.0.0",
"reflect-metadata": "^0.2.2",
"tsbuffer": "^2.2.10",
"tsrpc": "^3.4.19",
"uuid": "^10.0.0",
"ws": "^8.18.0"
},
"peerDependencies": {
"@esengine/ecs-framework": ">=2.1.29"
},
"devDependencies": {
"@esengine/ecs-framework": "*",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/node": "^20.19.0",
"@types/uuid": "^10.0.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"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "git",
"url": "https://github.com/esengine/ecs-framework.git",
"directory": "packages/network"
}
}

View File

@@ -1,133 +0,0 @@
const resolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const terser = require('@rollup/plugin-terser');
const dts = require('rollup-plugin-dts').default;
const { readFileSync } = require('fs');
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
const banner = `/**
* @esengine/ecs-framework-network v${pkg.version}
* ECS框架网络插件 - protobuf序列化、帧同步和快照功能
*
* @author ${pkg.author}
* @license ${pkg.license}
*/`;
// 外部依赖 - 不打包到bundle中
const external = [
'@esengine/ecs-framework',
'protobufjs',
'reflect-metadata'
];
const commonPlugins = [
resolve({
browser: true,
preferBuiltins: false
}),
commonjs({
include: /node_modules/
})
];
module.exports = [
// ES模块构建
{
input: 'bin/index.js',
output: {
file: 'dist/index.mjs',
format: 'es',
banner,
sourcemap: true,
exports: 'named'
},
plugins: [
...commonPlugins,
terser({
format: {
comments: /^!/
}
})
],
external,
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
unknownGlobalSideEffects: false
}
},
// CommonJS构建
{
input: 'bin/index.js',
output: {
file: 'dist/index.cjs',
format: 'cjs',
banner,
sourcemap: true,
exports: 'named'
},
plugins: [
...commonPlugins,
terser({
format: {
comments: /^!/
}
})
],
external,
treeshake: {
moduleSideEffects: false
}
},
// UMD构建 - 用于CDN和浏览器直接使用
{
input: 'bin/index.js',
output: {
file: 'dist/index.umd.js',
format: 'umd',
name: 'ECSNetwork',
banner,
sourcemap: true,
exports: 'named',
globals: {
'@esengine/ecs-framework': 'ECS',
'protobufjs': 'protobuf',
'reflect-metadata': 'Reflect'
}
},
plugins: [
...commonPlugins,
terser({
format: {
comments: /^!/
}
})
],
external: ['@esengine/ecs-framework', 'protobufjs', 'reflect-metadata'],
treeshake: {
moduleSideEffects: false
}
},
// 类型定义构建
{
input: 'bin/index.d.ts',
output: {
file: 'dist/index.d.ts',
format: 'es',
banner: `/**
* @esengine/ecs-framework-network v${pkg.version}
* TypeScript definitions
*/`
},
plugins: [
dts({
respectExternal: true
})
],
external: ['@esengine/ecs-framework']
}
];

View File

@@ -1,185 +0,0 @@
/**
* 网络注册表
*
* 管理所有网络对象的注册和查找
*/
import { createLogger } from '@esengine/ecs-framework';
import { NetworkIdentity } from '../NetworkIdentity';
const logger = createLogger('NetworkRegistry');
export class NetworkRegistry {
private static _instance: NetworkRegistry | null = null;
/** 网络对象映射表 networkId -> NetworkIdentity */
private networkObjects: Map<number, NetworkIdentity> = new Map();
/** 下一个可用的网络ID */
private nextNetworkId: number = 1;
/** 本地玩家对象 */
private localPlayer: NetworkIdentity | null = null;
public static get instance(): NetworkRegistry {
if (!NetworkRegistry._instance) {
NetworkRegistry._instance = new NetworkRegistry();
}
return NetworkRegistry._instance;
}
private constructor() {}
/**
* 注册网络对象
* @param identity 网络身份组件
* @param networkId 指定的网络ID如果不提供则自动分配
* @returns 分配的网络ID
*/
public register(identity: NetworkIdentity, networkId?: number): number {
// 使用指定ID或自动分配
const assignedId = networkId || this.nextNetworkId++;
// 检查ID是否已被占用
if (this.networkObjects.has(assignedId)) {
logger.error(`网络ID ${assignedId} 已被占用`);
throw new Error(`Network ID ${assignedId} is already in use`);
}
// 注册对象
identity.networkId = assignedId;
this.networkObjects.set(assignedId, identity);
// 确保下一个ID不冲突
if (assignedId >= this.nextNetworkId) {
this.nextNetworkId = assignedId + 1;
}
logger.debug(`注册网络对象: ID=${assignedId}, Type=${identity.entity?.name || 'Unknown'}`);
return assignedId;
}
/**
* 注销网络对象
* @param networkId 网络ID
*/
public unregister(networkId: number): void {
const identity = this.networkObjects.get(networkId);
if (identity) {
this.networkObjects.delete(networkId);
// 如果是本地玩家,清除引用
if (this.localPlayer === identity) {
this.localPlayer = null;
}
logger.debug(`注销网络对象: ID=${networkId}`);
}
}
/**
* 根据网络ID查找对象
* @param networkId 网络ID
* @returns 网络身份组件
*/
public find(networkId: number): NetworkIdentity | null {
return this.networkObjects.get(networkId) || null;
}
/**
* 获取所有网络对象
*/
public getAllNetworkObjects(): NetworkIdentity[] {
return Array.from(this.networkObjects.values());
}
/**
* 获取所有拥有权威的对象
*/
public getAuthorityObjects(): NetworkIdentity[] {
return Array.from(this.networkObjects.values()).filter(identity => identity.hasAuthority);
}
/**
* 获取指定客户端拥有的对象
* @param ownerId 客户端ID
*/
public getObjectsByOwner(ownerId: number): NetworkIdentity[] {
return Array.from(this.networkObjects.values()).filter(identity => identity.ownerId === ownerId);
}
/**
* 设置本地玩家
* @param identity 本地玩家的网络身份组件
*/
public setLocalPlayer(identity: NetworkIdentity): void {
// 清除之前的本地玩家标记
if (this.localPlayer) {
this.localPlayer.isLocalPlayer = false;
}
// 设置新的本地玩家
this.localPlayer = identity;
identity.setAsLocalPlayer();
logger.info(`设置本地玩家: ID=${identity.networkId}`);
}
/**
* 获取本地玩家
*/
public getLocalPlayer(): NetworkIdentity | null {
return this.localPlayer;
}
/**
* 清理指定客户端断开连接后的对象
* @param ownerId 断开连接的客户端ID
*/
public cleanupDisconnectedClient(ownerId: number): void {
const ownedObjects = this.getObjectsByOwner(ownerId);
for (const identity of ownedObjects) {
// 移除权威,转移给服务端
identity.setAuthority(false, 0);
// 如果是本地玩家,清除引用
if (identity === this.localPlayer) {
this.localPlayer = null;
}
}
logger.info(`清理断开连接客户端 ${ownerId}${ownedObjects.length} 个对象`);
}
/**
* 检查网络ID是否存在
* @param networkId 网络ID
*/
public exists(networkId: number): boolean {
return this.networkObjects.has(networkId);
}
/**
* 重置注册表(用于测试)
*/
public reset(): void {
this.networkObjects.clear();
this.nextNetworkId = 1;
this.localPlayer = null;
logger.info('网络注册表已重置');
}
/**
* 获取统计信息
*/
public getStats() {
const objects = Array.from(this.networkObjects.values());
return {
totalObjects: objects.length,
authorityObjects: objects.filter(o => o.hasAuthority).length,
localPlayerCount: this.localPlayer ? 1 : 0,
nextNetworkId: this.nextNetworkId
};
}
}

View File

@@ -1,223 +0,0 @@
/**
* RPC 管理器
*
* 负责处理客户端 RPC 和命令的注册、调用和消息路由
*/
import { createLogger } from '@esengine/ecs-framework';
import { NetworkBehaviour } from '../NetworkBehaviour';
import { NetworkRegistry } from './NetworkRegistry';
import { RpcMessage, RpcMetadata } from '../Types/NetworkTypes';
import { getClientRpcMetadata } from '../decorators/ClientRpc';
import { getCommandMetadata } from '../decorators/Command';
const logger = createLogger('RpcManager');
/**
* RPC 调用信息
*/
interface RpcCall {
networkId: number;
componentType: string;
methodName: string;
args: any[];
timestamp: number;
isClientRpc: boolean;
}
export class RpcManager {
private static _instance: RpcManager | null = null;
/** 已注册的网络组件类型 */
private registeredComponents: Set<string> = new Set();
/** 待发送的 RPC 调用队列 */
private pendingRpcCalls: RpcCall[] = [];
public static get instance(): RpcManager {
if (!RpcManager._instance) {
RpcManager._instance = new RpcManager();
}
return RpcManager._instance;
}
private constructor() {}
/**
* 注册网络组件的 RPC 方法
*/
public registerComponent(component: NetworkBehaviour): void {
const componentType = component.constructor.name;
if (this.registeredComponents.has(componentType)) {
return; // 已经注册过了
}
// 获取 ClientRpc 和 Command 元数据
const clientRpcMetadata = getClientRpcMetadata(component.constructor);
const commandMetadata = getCommandMetadata(component.constructor);
if (clientRpcMetadata.length === 0 && commandMetadata.length === 0) {
return; // 没有 RPC 方法
}
this.registeredComponents.add(componentType);
logger.debug(`注册 RPC 组件: ${componentType}, ClientRpc: ${clientRpcMetadata.length}, Commands: ${commandMetadata.length}`);
}
/**
* 添加 ClientRpc 调用到队列
*/
public addClientRpcCall(
networkId: number,
componentType: string,
methodName: string,
args: any[] = []
): void {
const rpcCall: RpcCall = {
networkId,
componentType,
methodName,
args,
timestamp: Date.now(),
isClientRpc: true
};
this.pendingRpcCalls.push(rpcCall);
logger.debug(`添加 ClientRpc 调用: ${componentType}.${methodName}`);
}
/**
* 添加 Command 调用到队列
*/
public addCommandCall(
networkId: number,
componentType: string,
methodName: string,
args: any[] = []
): void {
const rpcCall: RpcCall = {
networkId,
componentType,
methodName,
args,
timestamp: Date.now(),
isClientRpc: false
};
this.pendingRpcCalls.push(rpcCall);
logger.debug(`添加 Command 调用: ${componentType}.${methodName}`);
}
/**
* 获取待发送的 RPC 消息
*/
public getPendingRpcMessages(): RpcMessage[] {
const messages: RpcMessage[] = [];
for (const rpcCall of this.pendingRpcCalls) {
messages.push({
type: 'rpc',
networkId: rpcCall.networkId,
data: {
componentType: rpcCall.componentType,
methodName: rpcCall.methodName,
args: rpcCall.args
},
methodName: rpcCall.methodName,
args: rpcCall.args,
isClientRpc: rpcCall.isClientRpc,
timestamp: rpcCall.timestamp
});
}
// 清空待发送队列
this.pendingRpcCalls.length = 0;
return messages;
}
/**
* 处理收到的 RPC 消息
*/
public handleRpcMessage(message: RpcMessage): void {
const networkIdentity = NetworkRegistry.instance.find(message.networkId);
if (!networkIdentity) {
logger.warn(`找不到网络ID为 ${message.networkId} 的对象`);
return;
}
// 找到对应的组件
const targetComponent = networkIdentity.networkBehaviours
.find(b => b.constructor.name === message.data.componentType);
if (!targetComponent) {
logger.warn(`找不到组件: ${message.data.componentType} (网络ID: ${message.networkId})`);
return;
}
// 验证方法是否存在
const method = (targetComponent as any)[message.methodName];
if (typeof method !== 'function') {
logger.warn(`方法不存在: ${message.data.componentType}.${message.methodName}`);
return;
}
// 验证权限
if (!this.validateRpcPermission(targetComponent as NetworkBehaviour, message)) {
return;
}
try {
// 执行方法
method.apply(targetComponent, message.args);
logger.debug(`执行 RPC: ${message.data.componentType}.${message.methodName}`);
} catch (error) {
logger.error(`RPC 执行失败: ${message.data.componentType}.${message.methodName}`, error);
}
}
/**
* 验证 RPC 调用权限
*/
private validateRpcPermission(component: NetworkBehaviour, message: RpcMessage): boolean {
let metadata: RpcMetadata[] = [];
if (message.isClientRpc) {
metadata = getClientRpcMetadata(component.constructor);
} else {
metadata = getCommandMetadata(component.constructor);
}
const rpcMeta = metadata.find(m => m.methodName === message.methodName);
if (!rpcMeta) {
logger.warn(`未找到 RPC 元数据: ${message.data.componentType}.${message.methodName}`);
return false;
}
// 检查权限要求
if (rpcMeta.requiresAuthority && !component.hasAuthority) {
logger.warn(`RPC 权限不足: ${message.data.componentType}.${message.methodName}`);
return false;
}
return true;
}
/**
* 清理所有待发送的 RPC 调用
*/
public clearPendingCalls(): void {
this.pendingRpcCalls.length = 0;
}
/**
* 获取统计信息
*/
public getStats() {
return {
registeredComponents: this.registeredComponents.size,
pendingRpcCalls: this.pendingRpcCalls.length
};
}
}

View File

@@ -1,222 +0,0 @@
/**
* 同步变量管理器
*
* 负责管理 SyncVar 的同步逻辑,包括变化检测、权限验证和消息发送
*/
import { createLogger } from '@esengine/ecs-framework';
import { NetworkBehaviour } from '../NetworkBehaviour';
import { getSyncVarMetadata, SYNCVAR_METADATA_KEY } from '../decorators/SyncVar';
import { SyncVarMessage, SyncVarMetadata } from '../Types/NetworkTypes';
const logger = createLogger('SyncVarManager');
/**
* SyncVar 变化记录
*/
interface SyncVarChange {
networkId: number;
componentType: string;
propertyName: string;
oldValue: any;
newValue: any;
timestamp: number;
}
export class SyncVarManager {
private static _instance: SyncVarManager | null = null;
/** 待同步的变化列表 */
private pendingChanges: SyncVarChange[] = [];
/** 已注册的网络组件实例 */
private registeredComponents: Map<string, NetworkBehaviour[]> = new Map();
public static get instance(): SyncVarManager {
if (!SyncVarManager._instance) {
SyncVarManager._instance = new SyncVarManager();
}
return SyncVarManager._instance;
}
private constructor() {}
/**
* 注册网络组件,为其设置 SyncVar 支持
*/
public registerComponent(component: NetworkBehaviour): void {
const componentType = component.constructor.name;
// 获取 SyncVar 元数据
const metadata = getSyncVarMetadata(component.constructor);
if (metadata.length === 0) {
return; // 没有 SyncVar无需处理
}
// 添加到注册列表
if (!this.registeredComponents.has(componentType)) {
this.registeredComponents.set(componentType, []);
}
this.registeredComponents.get(componentType)!.push(component);
// 为组件添加变化通知方法
this.addSyncVarSupport(component, metadata);
logger.debug(`注册网络组件: ${componentType},包含 ${metadata.length} 个 SyncVar`);
}
/**
* 注销网络组件
*/
public unregisterComponent(component: NetworkBehaviour): void {
const componentType = component.constructor.name;
const components = this.registeredComponents.get(componentType);
if (components) {
const index = components.indexOf(component);
if (index !== -1) {
components.splice(index, 1);
}
if (components.length === 0) {
this.registeredComponents.delete(componentType);
}
}
logger.debug(`注销网络组件: ${componentType}`);
}
/**
* 处理收到的 SyncVar 消息
*/
public handleSyncVarMessage(message: SyncVarMessage): void {
const components = this.registeredComponents.get(message.componentType);
if (!components) {
logger.warn(`收到未知组件类型的 SyncVar 消息: ${message.componentType}`);
return;
}
// 找到对应的网络对象
const targetComponent = components.find(c => c.networkId === message.networkId);
if (!targetComponent) {
logger.warn(`找不到网络ID为 ${message.networkId} 的组件: ${message.componentType}`);
return;
}
// 应用同步值
this.applySyncVar(targetComponent, message.propertyName, message.value);
}
/**
* 获取待发送的 SyncVar 消息
*/
public getPendingMessages(): SyncVarMessage[] {
const messages: SyncVarMessage[] = [];
for (const change of this.pendingChanges) {
messages.push({
type: 'syncvar',
networkId: change.networkId,
componentType: change.componentType,
propertyName: change.propertyName,
value: change.newValue,
data: change.newValue,
timestamp: change.timestamp
});
}
// 清空待同步列表
this.pendingChanges.length = 0;
return messages;
}
/**
* 为网络组件添加 SyncVar 支持
*/
private addSyncVarSupport(component: NetworkBehaviour, metadata: SyncVarMetadata[]): void {
// 添加变化通知方法
(component as any).notifySyncVarChanged = (propertyName: string, oldValue: any, newValue: any) => {
this.onSyncVarChanged(component, propertyName, oldValue, newValue, metadata);
};
}
/**
* 处理 SyncVar 变化
*/
private onSyncVarChanged(
component: NetworkBehaviour,
propertyName: string,
oldValue: any,
newValue: any,
metadata: SyncVarMetadata[]
): void {
// 找到对应的元数据
const syncVarMeta = metadata.find(m => m.propertyName === propertyName);
if (!syncVarMeta) {
return;
}
// 权限检查
if (syncVarMeta.authorityOnly && !component.hasAuthority) {
logger.warn(`权限不足,无法修改 SyncVar: ${component.constructor.name}.${propertyName}`);
// 回滚值
(component as any)[`_${propertyName}`] = oldValue;
return;
}
// 记录变化
const change: SyncVarChange = {
networkId: component.networkId,
componentType: component.constructor.name,
propertyName,
oldValue,
newValue,
timestamp: Date.now()
};
this.pendingChanges.push(change);
logger.debug(`SyncVar 变化: ${change.componentType}.${propertyName} = ${newValue}`);
}
/**
* 应用同步变量值
*/
private applySyncVar(component: NetworkBehaviour, propertyName: string, value: any): void {
try {
// 直接设置内部值,跳过 setter 的权限检查
(component as any)[`_${propertyName}`] = value;
// 获取并调用变化回调
const metadata = getSyncVarMetadata(component.constructor);
const syncVarMeta = metadata.find(m => m.propertyName === propertyName);
if (syncVarMeta?.onChanged && typeof (component as any)[syncVarMeta.onChanged] === 'function') {
(component as any)[syncVarMeta.onChanged](undefined, value);
}
logger.debug(`应用 SyncVar: ${component.constructor.name}.${propertyName} = ${value}`);
} catch (error) {
logger.error(`应用 SyncVar 失败: ${component.constructor.name}.${propertyName}`, error);
}
}
/**
* 清理所有待同步变化
*/
public clearPendingChanges(): void {
this.pendingChanges.length = 0;
}
/**
* 获取统计信息
*/
public getStats() {
return {
registeredComponents: this.registeredComponents.size,
pendingChanges: this.pendingChanges.length,
totalInstances: Array.from(this.registeredComponents.values())
.reduce((sum, components) => sum + components.length, 0)
};
}
}

View File

@@ -1,141 +0,0 @@
/**
* 网络组件基类
*
* 所有需要网络同步功能的组件都应继承此类
* 提供 SyncVar 和 RPC 功能的基础实现
*/
import { Component } from '@esengine/ecs-framework';
import { INetworkBehaviour } from './Types/NetworkTypes';
import { NetworkIdentity } from './NetworkIdentity';
import { NetworkManager } from './NetworkManager';
export abstract class NetworkBehaviour extends Component implements INetworkBehaviour {
/** 网络身份组件引用 */
public networkIdentity: NetworkIdentity | null = null;
/**
* 是否拥有权威
* 权威端可以修改带有 authorityOnly 标记的 SyncVar
*/
public get hasAuthority(): boolean {
return this.networkIdentity?.hasAuthority || false;
}
/**
* 是否为本地玩家
*/
public get isLocalPlayer(): boolean {
return this.networkIdentity?.isLocalPlayer || false;
}
/**
* 是否在服务端运行
*/
public get isServer(): boolean {
return NetworkManager.isServer;
}
/**
* 是否在客户端运行
*/
public get isClient(): boolean {
return NetworkManager.isClient;
}
/**
* 网络ID
*/
public get networkId(): number {
return this.networkIdentity?.networkId || 0;
}
/**
* 组件初始化时自动注册到网络身份
*/
public start(): void {
this.registerNetworkBehaviour();
}
/**
* 组件销毁时自动从网络身份注销
*/
public destroy(): void {
this.unregisterNetworkBehaviour();
}
/**
* 注册到网络身份组件
*/
private registerNetworkBehaviour(): void {
if (!this.entity) return;
const networkIdentity = this.entity.getComponent?.(NetworkIdentity);
if (networkIdentity) {
networkIdentity.addNetworkBehaviour(this);
}
}
/**
* 从网络身份组件注销
*/
private unregisterNetworkBehaviour(): void {
if (this.networkIdentity) {
this.networkIdentity.removeNetworkBehaviour(this);
}
}
/**
* 检查权威性
* 用于验证是否可以执行需要权威的操作
*/
protected checkAuthority(): boolean {
if (!this.hasAuthority) {
console.warn(`[NetworkBehaviour] 操作被拒绝:${this.constructor.name} 没有权威`);
return false;
}
return true;
}
/**
* 发送客户端RPC
* 只能在服务端调用,向指定客户端或所有客户端发送消息
*/
protected sendClientRpc(methodName: string, args: any[] = [], targetClient?: number): void {
if (!this.isServer) {
console.warn(`[NetworkBehaviour] ClientRpc 只能在服务端调用: ${methodName}`);
return;
}
NetworkManager.instance?.sendClientRpc(
this.networkId,
this.constructor.name,
methodName,
args,
targetClient
);
}
/**
* 发送命令到服务端
* 只能在客户端调用,向服务端发送命令
*/
protected sendCommand(methodName: string, args: any[] = []): void {
if (!this.isClient) {
console.warn(`[NetworkBehaviour] Command 只能在客户端调用: ${methodName}`);
return;
}
if (!this.hasAuthority) {
console.warn(`[NetworkBehaviour] Command 需要权威才能调用: ${methodName}`);
return;
}
NetworkManager.instance?.sendCommand(
this.networkId,
this.constructor.name,
methodName,
args
);
}
}

View File

@@ -1,105 +0,0 @@
/**
* 网络身份组件
*
* 标识网络对象的唯一身份,管理网络组件和权威性
* 所有需要网络同步的实体都必须拥有此组件
*/
import { Component } from '@esengine/ecs-framework';
import { INetworkBehaviour } from './Types/NetworkTypes';
export class NetworkIdentity extends Component {
/** 网络对象的唯一标识符 */
public networkId: number = 0;
/** 所有者客户端ID0 表示服务端拥有 */
public ownerId: number = 0;
/** 是否拥有权威,权威端可以修改 SyncVar 和发送 RPC */
public hasAuthority: boolean = false;
/** 是否为本地玩家对象 */
public isLocalPlayer: boolean = false;
/** 挂载的网络组件列表 */
public networkBehaviours: INetworkBehaviour[] = [];
/**
* 添加网络组件
* @param behaviour 要添加的网络组件
*/
public addNetworkBehaviour(behaviour: INetworkBehaviour): void {
if (this.networkBehaviours.includes(behaviour)) {
return; // 已经添加过了
}
this.networkBehaviours.push(behaviour);
behaviour.networkIdentity = this;
}
/**
* 移除网络组件
* @param behaviour 要移除的网络组件
*/
public removeNetworkBehaviour(behaviour: INetworkBehaviour): void {
const index = this.networkBehaviours.indexOf(behaviour);
if (index !== -1) {
this.networkBehaviours.splice(index, 1);
behaviour.networkIdentity = null;
}
}
/**
* 设置权威性
* @param hasAuthority 是否拥有权威
* @param ownerId 所有者客户端ID
*/
public setAuthority(hasAuthority: boolean, ownerId: number = 0): void {
this.hasAuthority = hasAuthority;
this.ownerId = ownerId;
}
/**
* 设置为本地玩家
*/
public setAsLocalPlayer(): void {
this.isLocalPlayer = true;
this.hasAuthority = true;
}
/**
* 获取指定类型的网络组件
*/
public getNetworkBehaviour<T extends INetworkBehaviour>(type: new (...args: any[]) => T): T | null {
return this.networkBehaviours.find(b => b instanceof type) as T || null;
}
/**
* 获取所有指定类型的网络组件
*/
public getNetworkBehaviours<T extends INetworkBehaviour>(type: new (...args: any[]) => T): T[] {
return this.networkBehaviours.filter(b => b instanceof type) as T[];
}
/**
* 组件启动时的处理
*/
public start(): void {
// 如果没有分配网络ID从网络管理器获取
if (this.networkId === 0) {
// 这里需要从 NetworkManager 获取新的网络ID
// 实现延后到 NetworkManager 完成
}
}
/**
* 组件销毁时的清理
*/
public destroy(): void {
// 清理所有网络组件的引用
this.networkBehaviours.forEach(behaviour => {
behaviour.networkIdentity = null;
});
this.networkBehaviours.length = 0;
}
}

View File

@@ -1,449 +0,0 @@
/**
* 网络管理器
*
* 网络库的核心管理类,负责:
* - 客户端/服务端连接管理
* - 网络消息路由和处理
* - SyncVar 同步调度
* - RPC 调用管理
*/
import { createLogger, Component } from '@esengine/ecs-framework';
import {
NetworkConfig,
NetworkSide,
NetworkConnectionState,
NetworkStats,
NetworkEventHandlers,
SyncVarMessage,
RpcMessage,
NetworkMessage
} from './Types/NetworkTypes';
import { NetworkRegistry } from './Core/NetworkRegistry';
import { SyncVarManager } from './Core/SyncVarManager';
import { RpcManager } from './Core/RpcManager';
import { NetworkIdentity } from './NetworkIdentity';
import { NetworkBehaviour } from './NetworkBehaviour';
import { TsrpcTransport, TransportEventHandlers } from './transport/TsrpcTransport';
const logger = createLogger('NetworkManager');
export class NetworkManager extends Component {
private static _instance: NetworkManager | null = null;
/** 当前网络端类型 */
private networkSide: NetworkSide = 'client';
/** 连接状态 */
private connectionState: NetworkConnectionState = 'disconnected';
/** 网络配置 */
private config: NetworkConfig = {
port: 7777,
host: 'localhost',
maxConnections: 100,
syncRate: 20,
compression: false
};
/** 网络统计信息 */
private stats: NetworkStats = {
connectionCount: 0,
bytesSent: 0,
bytesReceived: 0,
messagesSent: 0,
messagesReceived: 0,
averageLatency: 0
};
/** 事件处理器 */
private eventHandlers: Partial<NetworkEventHandlers> = {};
/** 同步定时器 */
private syncTimer: NodeJS.Timeout | null = null;
/** TSRPC传输层 */
private transport: TsrpcTransport | null = null;
public static get instance(): NetworkManager | null {
return NetworkManager._instance;
}
public static get isServer(): boolean {
return NetworkManager._instance?.networkSide === 'server' ||
NetworkManager._instance?.networkSide === 'host';
}
public static get isClient(): boolean {
return NetworkManager._instance?.networkSide === 'client' ||
NetworkManager._instance?.networkSide === 'host';
}
public static get isConnected(): boolean {
return NetworkManager._instance?.connectionState === 'connected';
}
constructor() {
super();
if (NetworkManager._instance) {
throw new Error('NetworkManager 已存在实例,请使用 NetworkManager.instance');
}
NetworkManager._instance = this;
}
/**
* 启动服务端
* @param config 网络配置
*/
public async startServer(config?: Partial<NetworkConfig>): Promise<void> {
if (this.connectionState !== 'disconnected') {
throw new Error('网络管理器已在运行中');
}
this.networkSide = 'server';
this.config = { ...this.config, ...config };
this.connectionState = 'connecting';
try {
// 初始化TSRPC传输层
await this.initializeTransport();
// 启动TSRPC服务端
await this.transport!.startServer();
this.connectionState = 'connected';
this.startSyncLoop();
logger.info(`服务端已启动,端口: ${this.config.port}`);
this.eventHandlers.onConnected?.();
} catch (error) {
this.connectionState = 'disconnected';
logger.error('启动服务端失败:', error);
this.eventHandlers.onError?.(error as Error);
throw error;
}
}
/**
* 连接到服务端
* @param host 服务端地址
* @param port 服务端端口
*/
public async connectToServer(host?: string, port?: number): Promise<void> {
if (this.connectionState !== 'disconnected') {
throw new Error('已经连接或正在连接中');
}
this.networkSide = 'client';
this.config.host = host || this.config.host;
this.config.port = port || this.config.port;
this.connectionState = 'connecting';
try {
// 初始化TSRPC传输层
await this.initializeTransport();
// 连接到TSRPC服务端
await this.transport!.connectToServer();
this.connectionState = 'connected';
this.startSyncLoop();
logger.info(`已连接到服务端: ${this.config.host}:${this.config.port}`);
this.eventHandlers.onConnected?.();
} catch (error) {
this.connectionState = 'disconnected';
logger.error('连接服务端失败:', error);
this.eventHandlers.onError?.(error as Error);
throw error;
}
}
/**
* 断开连接
*/
public async disconnect(): Promise<void> {
if (this.connectionState === 'disconnected') {
return;
}
this.connectionState = 'disconnected';
this.stopSyncLoop();
// 关闭TSRPC传输层连接
if (this.transport) {
await this.transport.disconnect();
}
logger.info('网络连接已断开');
this.eventHandlers.onDisconnected?.();
}
/**
* 注册网络对象
* @param entity 包含 NetworkIdentity 的实体
* @returns 分配的网络ID
*/
public registerNetworkObject(entity: any): number {
const networkIdentity = entity.getComponent?.(NetworkIdentity);
if (!networkIdentity) {
throw new Error('实体必须包含 NetworkIdentity 组件才能注册为网络对象');
}
// 注册到网络注册表
const networkId = NetworkRegistry.instance.register(networkIdentity);
// 注册所有网络组件到管理器
const networkBehaviours = entity.getComponents?.()?.filter((c: any) => c instanceof NetworkBehaviour) || [];
for (const behaviour of networkBehaviours) {
SyncVarManager.instance.registerComponent(behaviour as NetworkBehaviour);
RpcManager.instance.registerComponent(behaviour as NetworkBehaviour);
}
logger.debug(`注册网络对象: ${entity.name}, ID: ${networkId}`);
return networkId;
}
/**
* 发送客户端 RPC服务端调用
*/
public sendClientRpc(
networkId: number,
componentType: string,
methodName: string,
args: any[] = [],
targetClient?: number
): void {
if (!NetworkManager.isServer) {
logger.warn('ClientRpc 只能在服务端调用');
return;
}
const message: RpcMessage = {
type: 'rpc',
networkId,
data: {
componentType,
methodName,
args
},
methodName,
args,
isClientRpc: true,
timestamp: Date.now()
};
this.sendMessage(message, targetClient);
}
/**
* 发送命令到服务端(客户端调用)
*/
public sendCommand(
networkId: number,
componentType: string,
methodName: string,
args: any[] = []
): void {
if (!NetworkManager.isClient) {
logger.warn('Command 只能在客户端调用');
return;
}
const message: RpcMessage = {
type: 'rpc',
networkId,
data: {
componentType,
methodName,
args
},
methodName,
args,
isClientRpc: false,
timestamp: Date.now()
};
this.sendMessage(message);
}
/**
* 设置事件处理器
*/
public on<K extends keyof NetworkEventHandlers>(
event: K,
handler: NetworkEventHandlers[K]
): void {
this.eventHandlers[event] = handler;
}
/**
* 获取网络统计信息
*/
public getStats(): NetworkStats {
return { ...this.stats };
}
/**
* 获取连接状态
*/
public getConnectionState(): NetworkConnectionState {
return this.connectionState;
}
/**
* 组件销毁时清理
*/
public destroy(): void {
this.disconnect();
NetworkManager._instance = null;
}
/**
* 初始化传输层
*/
private async initializeTransport(): Promise<void> {
if (this.transport) {
return; // 已经初始化
}
// 创建TSRPC传输层
this.transport = new TsrpcTransport(this.config);
// 设置传输层事件处理器
const transportHandlers: TransportEventHandlers = {
onConnected: () => {
logger.debug('传输层连接已建立');
},
onDisconnected: (reason) => {
logger.debug(`传输层连接已断开: ${reason}`);
if (this.connectionState === 'connected') {
this.connectionState = 'disconnected';
this.eventHandlers.onDisconnected?.(reason);
}
},
onClientConnected: (clientId) => {
logger.debug(`客户端 ${clientId} 已连接到传输层`);
this.eventHandlers.onClientConnected?.(clientId);
},
onClientDisconnected: (clientId, reason) => {
logger.debug(`客户端 ${clientId} 已从传输层断开: ${reason}`);
this.eventHandlers.onClientDisconnected?.(clientId, reason);
// 清理断开连接的客户端对象
NetworkRegistry.instance.cleanupDisconnectedClient(clientId);
},
onMessage: (message, fromClientId) => {
this.handleMessage(message);
},
onError: (error) => {
logger.error('传输层错误:', error);
this.eventHandlers.onError?.(error);
}
};
this.transport.setEventHandlers(transportHandlers);
logger.debug('TSRPC传输层已初始化');
}
/**
* 启动同步循环
*/
private startSyncLoop(): void {
if (this.syncTimer) {
return;
}
const interval = 1000 / (this.config.syncRate || 20);
this.syncTimer = setInterval(() => {
this.processSyncVars();
}, interval);
logger.debug(`同步循环已启动,频率: ${this.config.syncRate} Hz`);
}
/**
* 停止同步循环
*/
private stopSyncLoop(): void {
if (this.syncTimer) {
clearInterval(this.syncTimer);
this.syncTimer = null;
logger.debug('同步循环已停止');
}
}
/**
* 处理 SyncVar 同步
*/
private processSyncVars(): void {
if (!NetworkManager.isServer) {
return; // 只有服务端发送同步消息
}
const syncVarMessages = SyncVarManager.instance.getPendingMessages();
for (const message of syncVarMessages) {
this.sendMessage(message).catch(error => {
logger.error('发送SyncVar消息失败:', error);
});
}
// 处理 RPC 消息
const rpcMessages = RpcManager.instance.getPendingRpcMessages();
for (const message of rpcMessages) {
this.sendMessage(message).catch(error => {
logger.error('发送RPC消息失败:', error);
});
}
}
/**
* 发送网络消息
*/
private async sendMessage(message: NetworkMessage, targetClient?: number): Promise<void> {
if (!this.transport) {
logger.warn('传输层未初始化,无法发送消息');
return;
}
try {
await this.transport.sendMessage(message, targetClient);
this.stats.messagesSent++;
logger.debug(`发送消息: ${message.type}, 网络ID: ${message.networkId}`);
} catch (error) {
logger.error('发送消息失败:', error);
}
}
/**
* 处理收到的网络消息(供外部调用)
*/
public handleIncomingMessage(message: NetworkMessage): void {
this.handleMessage(message);
}
/**
* 处理收到的网络消息
*/
private handleMessage(message: NetworkMessage): void {
this.stats.messagesReceived++;
switch (message.type) {
case 'syncvar':
SyncVarManager.instance.handleSyncVarMessage(message as SyncVarMessage);
break;
case 'rpc':
RpcManager.instance.handleRpcMessage(message as RpcMessage);
break;
default:
logger.warn(`未知消息类型: ${message.type}`);
}
}
}

View File

@@ -1,107 +0,0 @@
/**
* ClientRpc 装饰器
*
* 用于标记可以从服务端调用的客户端方法
* 只能在服务端调用会向指定客户端或所有客户端发送RPC消息
*/
import 'reflect-metadata';
import { RpcMetadata } from '../Types/NetworkTypes';
/**
* ClientRpc 装饰器选项
*/
export interface ClientRpcOptions {
/** 是否需要权威验证,默认为 true */
requiresAuthority?: boolean;
}
/**
* 存储 ClientRpc 元数据的 Symbol
*/
export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc_metadata');
/**
* ClientRpc 装饰器
*
* @param options 装饰器选项
* @returns 方法装饰器函数
*
* @example
* ```typescript
* class PlayerController extends NetworkBehaviour {
* @ClientRpc()
* public showEffect(effectType: string, position: Vector3): void {
* // 这个方法会在客户端执行
* console.log(`显示特效: ${effectType} at ${position}`);
* }
*
* private triggerEffect(): void {
* if (this.isServer) {
* // 服务端调用,会发送到所有客户端
* this.showEffect('explosion', new Vector3(0, 0, 0));
* }
* }
* }
* ```
*/
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
return function (target: any, 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.constructor);
if (!metadata) {
metadata = [];
Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, metadata, target.constructor);
}
// 添加当前方法的元数据
const rpcMetadata: RpcMetadata = {
methodName: propertyKey,
isClientRpc: true,
requiresAuthority: options.requiresAuthority !== false // 默认为 true
};
metadata.push(rpcMetadata);
// 保存原始方法
const originalMethod = descriptor.value;
// 包装方法以添加网络功能
descriptor.value = function (this: any, ...args: any[]) {
// 如果在服务端调用发送RPC消息
if (this.isServer) {
if (rpcMetadata.requiresAuthority && !this.hasAuthority) {
console.warn(`[ClientRpc] 权限不足,无法调用: ${propertyKey}`);
return;
}
// 发送客户端RPC
this.sendClientRpc?.(propertyKey, args);
} else if (this.isClient) {
// 在客户端直接执行原始方法
return originalMethod.apply(this, args);
}
};
return descriptor;
};
}
/**
* 获取类的 ClientRpc 元数据
*/
export function getClientRpcMetadata(target: any): RpcMetadata[] {
return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || [];
}
/**
* 检查方法是否为 ClientRpc
*/
export function isClientRpc(target: any, methodName: string): boolean {
const metadata = getClientRpcMetadata(target);
return metadata.some(m => m.methodName === methodName);
}

View File

@@ -1,108 +0,0 @@
/**
* Command 装饰器
*
* 用于标记可以从客户端调用的服务端方法
* 只能在客户端调用,会向服务端发送命令消息
*/
import 'reflect-metadata';
import { RpcMetadata } from '../Types/NetworkTypes';
/**
* Command 装饰器选项
*/
export interface CommandOptions {
/** 是否需要权威验证,默认为 true */
requiresAuthority?: boolean;
}
/**
* 存储 Command 元数据的 Symbol
*/
export const COMMAND_METADATA_KEY = Symbol('command_metadata');
/**
* Command 装饰器
*
* @param options 装饰器选项
* @returns 方法装饰器函数
*
* @example
* ```typescript
* class PlayerController extends NetworkBehaviour {
* @Command()
* public movePlayer(direction: Vector3): void {
* // 这个方法会在服务端执行
* console.log(`玩家移动: ${direction}`);
* // 更新玩家位置逻辑...
* }
*
* private handleInput(): void {
* if (this.isClient && this.hasAuthority) {
* // 客户端调用,会发送到服务端
* this.movePlayer(new Vector3(1, 0, 0));
* }
* }
* }
* ```
*/
export function Command(options: CommandOptions = {}): MethodDecorator {
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
if (typeof propertyKey !== 'string') {
throw new Error('Command can only be applied to string method names');
}
// 获取或创建元数据数组
let metadata: RpcMetadata[] = Reflect.getMetadata(COMMAND_METADATA_KEY, target.constructor);
if (!metadata) {
metadata = [];
Reflect.defineMetadata(COMMAND_METADATA_KEY, metadata, target.constructor);
}
// 添加当前方法的元数据
const rpcMetadata: RpcMetadata = {
methodName: propertyKey,
isClientRpc: false,
requiresAuthority: options.requiresAuthority !== false // 默认为 true
};
metadata.push(rpcMetadata);
// 保存原始方法
const originalMethod = descriptor.value;
// 包装方法以添加网络功能
descriptor.value = function (this: any, ...args: any[]) {
// 如果在客户端调用,发送命令消息
if (this.isClient) {
if (rpcMetadata.requiresAuthority && !this.hasAuthority) {
console.warn(`[Command] 权限不足,无法调用: ${propertyKey}`);
return;
}
// 发送命令到服务端
this.sendCommand?.(propertyKey, args);
} else if (this.isServer) {
// 在服务端直接执行原始方法
return originalMethod.apply(this, args);
}
};
return descriptor;
};
}
/**
* 获取类的 Command 元数据
*/
export function getCommandMetadata(target: any): RpcMetadata[] {
return Reflect.getMetadata(COMMAND_METADATA_KEY, target) || [];
}
/**
* 检查方法是否为 Command
*/
export function isCommand(target: any, methodName: string): boolean {
const metadata = getCommandMetadata(target);
return metadata.some(m => m.methodName === methodName);
}

View File

@@ -1,116 +0,0 @@
/**
* SyncVar 装饰器
*
* 用于标记需要在网络间自动同步的属性
* 当属性值发生变化时,会自动同步到其他网络端
*/
import 'reflect-metadata';
import { SyncVarMetadata } from '../Types/NetworkTypes';
/**
* SyncVar 装饰器选项
*/
export interface SyncVarOptions {
/** 是否仅权威端可修改,默认为 true */
authorityOnly?: boolean;
/** 变化回调函数名,属性变化时会调用此方法 */
onChanged?: string;
}
/**
* 存储 SyncVar 元数据的 Symbol
*/
export const SYNCVAR_METADATA_KEY = Symbol('syncvar_metadata');
/**
* SyncVar 装饰器
*
* @param options 装饰器选项
* @returns 属性装饰器函数
*
* @example
* ```typescript
* class PlayerController extends NetworkBehaviour {
* @SyncVar({ onChanged: 'onHealthChanged' })
* public health: number = 100;
*
* @SyncVar({ authorityOnly: false })
* public playerName: string = '';
*
* private onHealthChanged(oldValue: number, newValue: number): void {
* console.log(`Health changed from ${oldValue} to ${newValue}`);
* }
* }
* ```
*/
export function SyncVar(options: SyncVarOptions = {}): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
if (typeof propertyKey !== 'string') {
throw new Error('SyncVar can only be applied to string property keys');
}
// 获取或创建元数据数组
let metadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, target.constructor);
if (!metadata) {
metadata = [];
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, metadata, target.constructor);
}
// 添加当前属性的元数据
const syncVarMetadata: SyncVarMetadata = {
propertyName: propertyKey,
authorityOnly: options.authorityOnly !== false, // 默认为 true
onChanged: options.onChanged
};
metadata.push(syncVarMetadata);
// 创建属性的内部存储
const internalKey = `_${propertyKey}`;
const changeKey = `_${propertyKey}_changed`;
// 重新定义属性的 getter 和 setter
Object.defineProperty(target, propertyKey, {
get: function (this: any) {
return this[internalKey];
},
set: function (this: any, newValue: any) {
const oldValue = this[internalKey];
// 检查值是否真的发生了变化
if (oldValue === newValue) {
return;
}
this[internalKey] = newValue;
this[changeKey] = true;
// 调用变化回调
if (options.onChanged && typeof this[options.onChanged] === 'function') {
this[options.onChanged](oldValue, newValue);
}
// 通知同步管理器
this.notifySyncVarChanged?.(propertyKey, oldValue, newValue);
},
enumerable: true,
configurable: true
});
};
}
/**
* 获取类的 SyncVar 元数据
*/
export function getSyncVarMetadata(target: any): SyncVarMetadata[] {
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
}
/**
* 检查属性是否为 SyncVar
*/
export function isSyncVar(target: any, propertyName: string): boolean {
const metadata = getSyncVarMetadata(target);
return metadata.some(m => m.propertyName === propertyName);
}

View File

@@ -1,27 +0,0 @@
/**
* ECS Network Library
*
* 基于 ECS 架构和 TSRPC 的网络同步库
* 提供简洁易用的网络组件和同步机制
*/
// 核心组件
export { NetworkManager } from './NetworkManager';
export { NetworkIdentity } from './NetworkIdentity';
export { NetworkBehaviour } from './NetworkBehaviour';
// 装饰器
export { SyncVar } from './decorators/SyncVar';
export { ClientRpc } from './decorators/ClientRpc';
export { Command } from './decorators/Command';
// 核心管理器
export { SyncVarManager } from './Core/SyncVarManager';
export { RpcManager } from './Core/RpcManager';
export { NetworkRegistry } from './Core/NetworkRegistry';
// 传输层
export * from './transport';
// 类型定义
export * from './Types/NetworkTypes';

View File

@@ -1,431 +0,0 @@
/**
* TSRPC 客户端传输层
*
* 封装TSRPC客户端功能提供服务端连接和消息收发
*/
import { WsClient } from 'tsrpc';
import { createLogger } from '@esengine/ecs-framework';
import { serviceProto, ServiceType } from './protocols/serviceProto';
import { NetworkConfig, NetworkMessage } from '../Types/NetworkTypes';
import {
ReqJoinRoom, ResJoinRoom,
ReqServerStatus, ResServerStatus,
ReqPing, ResPing,
MsgNetworkMessage,
MsgSyncVar,
MsgRpcCall,
MsgNetworkObjectSpawn,
MsgNetworkObjectDespawn,
MsgClientDisconnected,
MsgAuthorityChange
} from './protocols/NetworkProtocols';
const logger = createLogger('TsrpcClient');
/**
* 连接状态
*/
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
/**
* TSRPC客户端包装器
*/
export class TsrpcClient {
private client: WsClient<ServiceType> | null = null;
private config: NetworkConfig;
private connectionState: ConnectionState = 'disconnected';
private clientId: number = 0;
private roomId: string = '';
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private reconnectInterval: number = 2000;
private heartbeatInterval: NodeJS.Timeout | null = null;
/** 统计信息 */
private stats = {
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0,
latency: 0
};
/** 事件处理器 */
public onConnected?: () => void;
public onDisconnected?: (reason?: string) => void;
public onReconnecting?: () => void;
public onMessage?: (message: NetworkMessage) => void;
public onError?: (error: Error) => void;
constructor(config: NetworkConfig) {
this.config = config;
}
/**
* 连接到服务端
*/
public async connect(): Promise<void> {
if (this.connectionState !== 'disconnected') {
throw new Error('客户端已连接或正在连接中');
}
this.connectionState = 'connecting';
try {
// 创建WebSocket客户端
this.client = new WsClient(serviceProto, {
server: `ws://${this.config.host}:${this.config.port}`,
// 自动重连配置
heartbeat: {
interval: 30000,
timeout: 5000
}
});
this.setupEventHandlers();
// 连接到服务端
const connectResult = await this.client.connect();
if (!connectResult.isSucc) {
throw new Error(`连接失败: ${connectResult.errMsg}`);
}
// 加入房间
const joinResult = await this.client.callApi('network/JoinRoom', {
roomId: this.config.roomId,
clientInfo: {
version: '1.0.0',
platform: typeof window !== 'undefined' ? 'browser' : 'node'
}
});
if (!joinResult.isSucc) {
throw new Error(`加入房间失败: ${joinResult.err.message}`);
}
this.clientId = joinResult.res.clientId;
this.roomId = joinResult.res.roomId;
this.connectionState = 'connected';
this.reconnectAttempts = 0;
// 启动心跳
this.startHeartbeat();
logger.info(`连接成功客户端ID: ${this.clientId}, 房间: ${this.roomId}`);
this.onConnected?.();
} catch (error) {
this.connectionState = 'disconnected';
logger.error('连接服务端失败:', error);
this.onError?.(error as Error);
throw error;
}
}
/**
* 断开连接
*/
public async disconnect(): Promise<void> {
if (this.connectionState === 'disconnected') {
return;
}
this.connectionState = 'disconnected';
this.stopHeartbeat();
if (this.client) {
await this.client.disconnect();
this.client = null;
}
logger.info('客户端已断开连接');
this.onDisconnected?.();
}
/**
* 发送消息到服务端
*/
public async sendMessage(message: NetworkMessage): Promise<void> {
if (!this.isConnected()) {
throw new Error('客户端未连接');
}
try {
const tsrpcMessage: MsgNetworkMessage = {
type: message.type as 'syncvar' | 'rpc',
networkId: message.networkId,
data: message.data,
timestamp: message.timestamp
};
await this.client!.sendMsg('network/NetworkMessage', tsrpcMessage);
this.stats.messagesSent++;
logger.debug(`发送消息: ${message.type}, 网络ID: ${message.networkId}`);
} catch (error) {
logger.error('发送消息失败:', error);
throw error;
}
}
/**
* 发送SyncVar同步消息
*/
public async sendSyncVar(
networkId: number,
componentType: string,
propertyName: string,
value: any
): Promise<void> {
if (!this.isConnected()) {
throw new Error('客户端未连接');
}
try {
const message: MsgSyncVar = {
networkId,
componentType,
propertyName,
value,
timestamp: Date.now()
};
await this.client!.sendMsg('network/SyncVar', message);
this.stats.messagesSent++;
logger.debug(`发送SyncVar: ${componentType}.${propertyName} = ${value}`);
} catch (error) {
logger.error('发送SyncVar失败:', error);
throw error;
}
}
/**
* 发送RPC调用消息
*/
public async sendRpcCall(
networkId: number,
componentType: string,
methodName: string,
args: any[],
isClientRpc: boolean
): Promise<void> {
if (!this.isConnected()) {
throw new Error('客户端未连接');
}
try {
const message: MsgRpcCall = {
networkId,
componentType,
methodName,
args,
isClientRpc,
timestamp: Date.now()
};
await this.client!.sendMsg('network/RpcCall', message);
this.stats.messagesSent++;
logger.debug(`发送RPC: ${componentType}.${methodName}(${isClientRpc ? 'ClientRpc' : 'Command'})`);
} catch (error) {
logger.error('发送RPC失败:', error);
throw error;
}
}
/**
* 查询服务端状态
*/
public async getServerStatus(): Promise<ResServerStatus> {
if (!this.isConnected()) {
throw new Error('客户端未连接');
}
const result = await this.client!.callApi('network/ServerStatus', {});
if (!result.isSucc) {
throw new Error(`查询服务端状态失败: ${result.err.message}`);
}
return result.res;
}
/**
* 发送心跳
*/
public async ping(): Promise<number> {
if (!this.isConnected()) {
throw new Error('客户端未连接');
}
const startTime = Date.now();
const result = await this.client!.callApi('network/Ping', {
timestamp: startTime
});
if (!result.isSucc) {
throw new Error(`心跳失败: ${result.err.message}`);
}
const latency = Date.now() - startTime;
this.stats.latency = latency;
return latency;
}
/**
* 获取连接状态
*/
public getConnectionState(): ConnectionState {
return this.connectionState;
}
/**
* 是否已连接
*/
public isConnected(): boolean {
return this.connectionState === 'connected' && (this.client?.isConnected || false);
}
/**
* 获取客户端ID
*/
public getClientId(): number {
return this.clientId;
}
/**
* 获取统计信息
*/
public getStats() {
return { ...this.stats };
}
/**
* 设置事件处理器
*/
private setupEventHandlers(): void {
if (!this.client) return;
// 连接断开处理
this.client.flows.postDisconnectFlow.push((v) => {
if (this.connectionState !== 'disconnected') {
logger.warn('连接意外断开,尝试重连...');
this.connectionState = 'reconnecting';
this.onReconnecting?.();
this.attemptReconnect();
}
return v;
});
// 消息监听
this.client.listenMsg('network/NetworkMessage', msg => {
this.stats.messagesReceived++;
const networkMessage: NetworkMessage = {
type: msg.type,
networkId: msg.networkId,
data: msg.data,
timestamp: msg.timestamp
};
this.onMessage?.(networkMessage);
});
// SyncVar消息监听
this.client.listenMsg('network/SyncVar', msg => {
this.stats.messagesReceived++;
const networkMessage: NetworkMessage = {
type: 'syncvar',
networkId: msg.networkId,
data: {
componentType: msg.componentType,
propertyName: msg.propertyName,
value: msg.value
},
timestamp: msg.timestamp
};
this.onMessage?.(networkMessage);
});
// RPC消息监听
this.client.listenMsg('network/RpcCall', msg => {
this.stats.messagesReceived++;
const networkMessage: NetworkMessage = {
type: 'rpc',
networkId: msg.networkId,
data: {
componentType: msg.componentType,
methodName: msg.methodName,
args: msg.args
},
timestamp: msg.timestamp
};
this.onMessage?.(networkMessage);
});
// 客户端断开通知
this.client.listenMsg('network/ClientDisconnected', msg => {
logger.info(`客户端 ${msg.clientId} 断开连接: ${msg.reason}`);
});
// 权威转移通知
this.client.listenMsg('network/AuthorityChange', msg => {
logger.info(`网络对象 ${msg.networkId} 权威转移给客户端 ${msg.newOwnerId}`);
});
}
/**
* 尝试重连
*/
private async attemptReconnect(): Promise<void> {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
logger.error('重连次数已达上限,停止重连');
this.connectionState = 'disconnected';
this.onDisconnected?.('max_reconnect_attempts_reached');
return;
}
this.reconnectAttempts++;
logger.info(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
try {
await new Promise(resolve => setTimeout(resolve, this.reconnectInterval));
// 重新连接
await this.connect();
} catch (error) {
logger.error(`重连失败:`, error);
// 继续尝试重连
this.attemptReconnect();
}
}
/**
* 启动心跳
*/
private startHeartbeat(): void {
this.heartbeatInterval = setInterval(async () => {
try {
await this.ping();
} catch (error) {
logger.warn('心跳失败:', error);
}
}, 30000); // 30秒心跳
}
/**
* 停止心跳
*/
private stopHeartbeat(): void {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
}

View File

@@ -1,364 +0,0 @@
/**
* TSRPC 服务端传输层
*
* 封装TSRPC服务端功能提供网络消息处理和客户端管理
*/
import { WsServer } from 'tsrpc';
import { createLogger } from '@esengine/ecs-framework';
import { serviceProto, ServiceType } from './protocols/serviceProto';
import { NetworkConfig, NetworkMessage } from '../Types/NetworkTypes';
import {
ReqJoinRoom, ResJoinRoom,
ReqServerStatus, ResServerStatus,
ReqPing, ResPing,
MsgNetworkMessage,
MsgSyncVar,
MsgRpcCall,
MsgNetworkObjectSpawn,
MsgNetworkObjectDespawn,
MsgClientDisconnected,
MsgAuthorityChange
} from './protocols/NetworkProtocols';
const logger = createLogger('TsrpcServer');
/**
* 客户端连接信息
*/
interface ClientConnection {
/** 客户端ID */
id: number;
/** 连接对象 */
connection: any;
/** 连接时间 */
connectTime: number;
/** 最后活跃时间 */
lastActivity: number;
/** 客户端信息 */
clientInfo?: {
version: string;
platform: string;
};
}
/**
* TSRPC服务端包装器
*/
export class TsrpcServer {
private server: WsServer<ServiceType> | null = null;
private clients: Map<number, ClientConnection> = new Map();
private nextClientId: number = 1;
private config: NetworkConfig;
private startTime: number = 0;
/** 统计信息 */
private stats = {
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0
};
/** 事件处理器 */
public onClientConnected?: (clientId: number) => void;
public onClientDisconnected?: (clientId: number, reason?: string) => void;
public onMessage?: (message: NetworkMessage, fromClientId: number) => void;
constructor(config: NetworkConfig) {
this.config = config;
}
/**
* 启动服务端
*/
public async start(): Promise<void> {
if (this.server) {
throw new Error('服务端已经在运行中');
}
// 创建TSRPC WebSocket服务端
this.server = new WsServer(serviceProto, {
port: this.config.port || 7777
});
this.startTime = Date.now();
this.setupApiHandlers();
this.setupMessageHandlers();
this.setupConnectionHandlers();
// 启动服务端
await this.server.start();
logger.info(`TSRPC服务端已启动端口: ${this.config.port}`);
}
/**
* 停止服务端
*/
public async stop(): Promise<void> {
if (this.server) {
await this.server.stop();
this.server = null;
this.clients.clear();
logger.info('TSRPC服务端已停止');
}
}
/**
* 向指定客户端发送消息
*/
public sendToClient(clientId: number, message: any): void {
const client = this.clients.get(clientId);
if (!client) {
logger.warn(`客户端不存在: ${clientId}`);
return;
}
try {
// 发送消息给指定连接
this.server?.broadcastMsg(message.type, message.data || message, [client.connection]);
this.stats.messagesSent++;
logger.debug(`向客户端 ${clientId} 发送消息: ${message.type || 'unknown'}`);
} catch (error) {
logger.error(`向客户端 ${clientId} 发送消息失败:`, error);
}
}
/**
* 向所有客户端广播消息
*/
public broadcast(message: any, excludeClientId?: number): void {
if (!this.server) {
logger.warn('服务端未启动,无法广播消息');
return;
}
try {
if (excludeClientId) {
// 排除指定客户端
const targetConnections = Array.from(this.clients.entries())
.filter(([clientId, client]) => clientId !== excludeClientId)
.map(([clientId, client]) => client.connection);
this.server.broadcastMsg(message.type, message.data || message, targetConnections);
this.stats.messagesSent += targetConnections.length;
} else {
// 广播给所有客户端
this.server.broadcastMsg(message.type, message.data || message);
this.stats.messagesSent += this.clients.size;
}
} catch (error) {
logger.error('广播消息失败:', error);
}
logger.debug(`广播消息给 ${this.clients.size} 个客户端: ${message.type || 'unknown'}`);
}
/**
* 获取连接的客户端列表
*/
public getConnectedClients(): number[] {
return Array.from(this.clients.keys());
}
/**
* 获取客户端数量
*/
public getClientCount(): number {
return this.clients.size;
}
/**
* 获取服务端统计信息
*/
public getStats() {
return {
...this.stats,
clientCount: this.clients.size,
uptime: Date.now() - this.startTime
};
}
/**
* 设置API处理器
*/
private setupApiHandlers(): void {
if (!this.server) return;
// 客户端加入房间
this.server.implementApi('network/JoinRoom', call => {
const clientId = this.nextClientId++;
const client: ClientConnection = {
id: clientId,
connection: call.conn,
connectTime: Date.now(),
lastActivity: Date.now(),
clientInfo: call.req.clientInfo
};
this.clients.set(clientId, client);
logger.info(`客户端 ${clientId} 连接成功`);
// 通知上层
this.onClientConnected?.(clientId);
// 返回响应
call.succ({
clientId,
roomId: call.req.roomId || 'default',
serverInfo: {
version: '1.0.0',
syncRate: this.config.syncRate || 20
}
});
});
// 服务端状态查询
this.server.implementApi('network/ServerStatus', call => {
const stats = this.getStats();
call.succ({
clientCount: stats.clientCount,
networkObjectCount: 0, // 这里需要从NetworkRegistry获取
uptime: stats.uptime,
networkStats: {
messagesSent: stats.messagesSent,
messagesReceived: stats.messagesReceived,
bytesSent: stats.bytesSent,
bytesReceived: stats.bytesReceived
}
});
});
// 心跳检测
this.server.implementApi('network/Ping', call => {
call.succ({
serverTimestamp: Date.now(),
clientTimestamp: call.req.timestamp
});
});
}
/**
* 设置消息处理器
*/
private setupMessageHandlers(): void {
if (!this.server) return;
// 网络消息处理
this.server.listenMsg('network/NetworkMessage', msg => {
const clientId = this.getClientIdByConnection(msg.conn);
if (clientId) {
this.stats.messagesReceived++;
this.updateClientActivity(clientId);
// 转换为内部消息格式
const networkMessage: NetworkMessage = {
type: msg.msg.type,
networkId: msg.msg.networkId,
data: msg.msg.data,
timestamp: msg.msg.timestamp
};
this.onMessage?.(networkMessage, clientId);
}
});
// SyncVar消息处理
this.server.listenMsg('network/SyncVar', msg => {
const clientId = this.getClientIdByConnection(msg.conn);
if (clientId) {
this.stats.messagesReceived++;
this.updateClientActivity(clientId);
// 转换并广播给其他客户端
const syncVarMessage: MsgSyncVar = msg.msg;
this.broadcast({
type: 'network/SyncVar',
data: syncVarMessage
}, clientId);
}
});
// RPC调用消息处理
this.server.listenMsg('network/RpcCall', msg => {
const clientId = this.getClientIdByConnection(msg.conn);
if (clientId) {
this.stats.messagesReceived++;
this.updateClientActivity(clientId);
const rpcMessage: MsgRpcCall = msg.msg;
if (rpcMessage.isClientRpc) {
// 服务端到客户端的RPC广播给所有客户端
this.broadcast({
type: 'network/RpcCall',
data: rpcMessage
});
} else {
// 客户端到服务端的Command只在服务端处理
const networkMessage: NetworkMessage = {
type: 'rpc',
networkId: rpcMessage.networkId,
data: {
componentType: rpcMessage.componentType,
methodName: rpcMessage.methodName,
args: rpcMessage.args
},
timestamp: rpcMessage.timestamp
};
this.onMessage?.(networkMessage, clientId);
}
}
});
}
/**
* 设置连接处理器
*/
private setupConnectionHandlers(): void {
if (!this.server) return;
// 连接断开处理
this.server.flows.postDisconnectFlow.push(conn => {
const clientId = this.getClientIdByConnection(conn);
if (clientId) {
this.clients.delete(clientId);
logger.info(`客户端 ${clientId} 断开连接`);
// 通知其他客户端
this.broadcast({
type: 'network/ClientDisconnected',
data: { clientId, reason: 'disconnected' }
});
// 通知上层
this.onClientDisconnected?.(clientId, 'disconnected');
}
return conn;
});
}
/**
* 根据连接对象获取客户端ID
*/
private getClientIdByConnection(conn: any): number | null {
for (const [clientId, client] of this.clients) {
if (client.connection === conn) {
return clientId;
}
}
return null;
}
/**
* 更新客户端活跃时间
*/
private updateClientActivity(clientId: number): void {
const client = this.clients.get(clientId);
if (client) {
client.lastActivity = Date.now();
}
}
}

View File

@@ -1,338 +0,0 @@
/**
* TSRPC 传输管理器
*
* 统一管理TSRPC服务端和客户端提供通用的传输接口
*/
import { createLogger } from '@esengine/ecs-framework';
import { TsrpcServer } from './TsrpcServer';
import { TsrpcClient } from './TsrpcClient';
import { NetworkConfig, NetworkMessage, NetworkSide } from '../Types/NetworkTypes';
const logger = createLogger('TsrpcTransport');
/**
* 传输事件处理器
*/
export interface TransportEventHandlers {
/** 连接建立 */
onConnected?: () => void;
/** 连接断开 */
onDisconnected?: (reason?: string) => void;
/** 客户端连接(仅服务端) */
onClientConnected?: (clientId: number) => void;
/** 客户端断开(仅服务端) */
onClientDisconnected?: (clientId: number, reason?: string) => void;
/** 收到消息 */
onMessage?: (message: NetworkMessage, fromClientId?: number) => void;
/** 发生错误 */
onError?: (error: Error) => void;
}
/**
* TSRPC传输层管理器
*/
export class TsrpcTransport {
private server: TsrpcServer | null = null;
private client: TsrpcClient | null = null;
private networkSide: NetworkSide = 'client';
private config: NetworkConfig;
private eventHandlers: TransportEventHandlers = {};
constructor(config: NetworkConfig) {
this.config = config;
}
/**
* 启动服务端
*/
public async startServer(): Promise<void> {
if (this.server) {
throw new Error('服务端已经在运行中');
}
this.networkSide = 'server';
this.server = new TsrpcServer(this.config);
// 设置服务端事件处理器
this.server.onClientConnected = (clientId) => {
logger.info(`客户端 ${clientId} 已连接`);
this.eventHandlers.onClientConnected?.(clientId);
};
this.server.onClientDisconnected = (clientId, reason) => {
logger.info(`客户端 ${clientId} 已断开: ${reason}`);
this.eventHandlers.onClientDisconnected?.(clientId, reason);
};
this.server.onMessage = (message, fromClientId) => {
this.eventHandlers.onMessage?.(message, fromClientId);
};
await this.server.start();
logger.info('TSRPC服务端已启动');
this.eventHandlers.onConnected?.();
}
/**
* 连接到服务端
*/
public async connectToServer(): Promise<void> {
if (this.client) {
throw new Error('客户端已经连接或正在连接中');
}
this.networkSide = 'client';
this.client = new TsrpcClient(this.config);
// 设置客户端事件处理器
this.client.onConnected = () => {
logger.info('已连接到服务端');
this.eventHandlers.onConnected?.();
};
this.client.onDisconnected = (reason) => {
logger.info(`已断开连接: ${reason}`);
this.eventHandlers.onDisconnected?.(reason);
};
this.client.onMessage = (message) => {
this.eventHandlers.onMessage?.(message);
};
this.client.onError = (error) => {
logger.error('客户端错误:', error);
this.eventHandlers.onError?.(error);
};
await this.client.connect();
}
/**
* 断开连接/停止服务端
*/
public async disconnect(): Promise<void> {
if (this.server) {
await this.server.stop();
this.server = null;
logger.info('TSRPC服务端已停止');
}
if (this.client) {
await this.client.disconnect();
this.client = null;
logger.info('TSRPC客户端已断开');
}
this.eventHandlers.onDisconnected?.();
}
/**
* 发送消息
*/
public async sendMessage(message: NetworkMessage, targetClientId?: number): Promise<void> {
if (this.networkSide === 'server' && this.server) {
// 服务端模式:发送给指定客户端或广播
if (targetClientId) {
this.server.sendToClient(targetClientId, {
type: 'network/NetworkMessage',
data: message
});
} else {
this.server.broadcast({
type: 'network/NetworkMessage',
data: message
});
}
} else if (this.networkSide === 'client' && this.client) {
// 客户端模式:发送给服务端
await this.client.sendMessage(message);
} else {
throw new Error('传输层未初始化或状态错误');
}
}
/**
* 发送SyncVar消息
*/
public async sendSyncVar(
networkId: number,
componentType: string,
propertyName: string,
value: any,
targetClientId?: number
): Promise<void> {
if (this.networkSide === 'server' && this.server) {
const message = {
type: 'network/SyncVar',
data: {
networkId,
componentType,
propertyName,
value,
timestamp: Date.now()
}
};
if (targetClientId) {
this.server.sendToClient(targetClientId, message);
} else {
this.server.broadcast(message);
}
} else if (this.networkSide === 'client' && this.client) {
await this.client.sendSyncVar(networkId, componentType, propertyName, value);
} else {
throw new Error('传输层未初始化或状态错误');
}
}
/**
* 发送RPC消息
*/
public async sendRpcCall(
networkId: number,
componentType: string,
methodName: string,
args: any[],
isClientRpc: boolean,
targetClientId?: number
): Promise<void> {
if (this.networkSide === 'server' && this.server) {
const message = {
type: 'network/RpcCall',
data: {
networkId,
componentType,
methodName,
args,
isClientRpc,
timestamp: Date.now()
}
};
if (targetClientId) {
this.server.sendToClient(targetClientId, message);
} else {
this.server.broadcast(message);
}
} else if (this.networkSide === 'client' && this.client) {
await this.client.sendRpcCall(networkId, componentType, methodName, args, isClientRpc);
} else {
throw new Error('传输层未初始化或状态错误');
}
}
/**
* 获取连接状态
*/
public isConnected(): boolean {
if (this.networkSide === 'server' && this.server) {
return true; // 服务端启动即为连接状态
} else if (this.networkSide === 'client' && this.client) {
return this.client.isConnected();
}
return false;
}
/**
* 获取网络端类型
*/
public getNetworkSide(): NetworkSide {
return this.networkSide;
}
/**
* 获取客户端ID仅客户端模式
*/
public getClientId(): number {
if (this.networkSide === 'client' && this.client) {
return this.client.getClientId();
}
return 0;
}
/**
* 获取连接的客户端列表(仅服务端模式)
*/
public getConnectedClients(): number[] {
if (this.networkSide === 'server' && this.server) {
return this.server.getConnectedClients();
}
return [];
}
/**
* 获取客户端数量(仅服务端模式)
*/
public getClientCount(): number {
if (this.networkSide === 'server' && this.server) {
return this.server.getClientCount();
}
return 0;
}
/**
* 获取统计信息
*/
public getStats() {
if (this.networkSide === 'server' && this.server) {
return this.server.getStats();
} else if (this.networkSide === 'client' && this.client) {
const clientStats = this.client.getStats();
return {
messagesSent: clientStats.messagesSent,
messagesReceived: clientStats.messagesReceived,
bytesSent: clientStats.bytesSent,
bytesReceived: clientStats.bytesReceived,
clientCount: 0,
uptime: 0
};
}
return {
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0,
clientCount: 0,
uptime: 0
};
}
/**
* 查询服务端状态(仅客户端模式)
*/
public async getServerStatus() {
if (this.networkSide === 'client' && this.client) {
return await this.client.getServerStatus();
}
throw new Error('只能在客户端模式下查询服务端状态');
}
/**
* 发送心跳(仅客户端模式)
*/
public async ping(): Promise<number> {
if (this.networkSide === 'client' && this.client) {
return await this.client.ping();
}
throw new Error('只能在客户端模式下发送心跳');
}
/**
* 设置事件处理器
*/
public setEventHandlers(handlers: TransportEventHandlers): void {
this.eventHandlers = { ...this.eventHandlers, ...handlers };
}
/**
* 设置单个事件处理器
*/
public on<K extends keyof TransportEventHandlers>(
event: K,
handler: TransportEventHandlers[K]
): void {
this.eventHandlers[event] = handler;
}
}

View File

@@ -1,9 +0,0 @@
/**
* 传输层模块导出
*/
export { TsrpcTransport } from './TsrpcTransport';
export { TsrpcServer } from './TsrpcServer';
export { TsrpcClient } from './TsrpcClient';
export * from './protocols/NetworkProtocols';
export { serviceProto, ServiceType } from './protocols/serviceProto';

View File

@@ -1,184 +0,0 @@
/**
* 网络库 TSRPC 协议定义
* 定义所有网络消息的类型和结构
*/
/**
* 客户端连接请求
*/
export interface ReqJoinRoom {
/** 房间ID可选 */
roomId?: string;
/** 客户端信息 */
clientInfo?: {
/** 客户端版本 */
version: string;
/** 客户端平台 */
platform: string;
};
}
/**
* 客户端连接响应
*/
export interface ResJoinRoom {
/** 分配的客户端ID */
clientId: number;
/** 房间ID */
roomId: string;
/** 服务端信息 */
serverInfo: {
/** 服务端版本 */
version: string;
/** 同步频率 */
syncRate: number;
};
}
/**
* 网络消息广播
*/
export interface MsgNetworkMessage {
/** 消息类型 */
type: 'syncvar' | 'rpc';
/** 网络对象ID */
networkId: number;
/** 消息数据 */
data: any;
/** 时间戳 */
timestamp: number;
/** 发送者客户端ID */
senderId?: number;
}
/**
* SyncVar 同步消息
*/
export interface MsgSyncVar {
/** 网络对象ID */
networkId: number;
/** 组件类型名 */
componentType: string;
/** 属性名 */
propertyName: string;
/** 新的属性值 */
value: any;
/** 时间戳 */
timestamp: number;
}
/**
* RPC 调用消息
*/
export interface MsgRpcCall {
/** 网络对象ID */
networkId: number;
/** 组件类型名 */
componentType: string;
/** 方法名 */
methodName: string;
/** 参数 */
args: any[];
/** 是否为客户端RPC */
isClientRpc: boolean;
/** 时间戳 */
timestamp: number;
}
/**
* 网络对象生成通知
*/
export interface MsgNetworkObjectSpawn {
/** 网络对象ID */
networkId: number;
/** 实体名称 */
entityName: string;
/** 所有者客户端ID */
ownerId: number;
/** 是否拥有权威 */
hasAuthority: boolean;
/** 初始组件数据 */
components: Array<{
/** 组件类型 */
type: string;
/** 组件数据 */
data: any;
}>;
}
/**
* 网络对象销毁通知
*/
export interface MsgNetworkObjectDespawn {
/** 网络对象ID */
networkId: number;
}
/**
* 客户端断开连接通知
*/
export interface MsgClientDisconnected {
/** 断开连接的客户端ID */
clientId: number;
/** 断开原因 */
reason?: string;
}
/**
* 权威转移通知
*/
export interface MsgAuthorityChange {
/** 网络对象ID */
networkId: number;
/** 新的权威所有者ID */
newOwnerId: number;
/** 是否拥有权威 */
hasAuthority: boolean;
}
/**
* 服务端状态查询请求
*/
export interface ReqServerStatus {}
/**
* 服务端状态响应
*/
export interface ResServerStatus {
/** 连接的客户端数量 */
clientCount: number;
/** 网络对象数量 */
networkObjectCount: number;
/** 服务器运行时间(毫秒) */
uptime: number;
/** 网络统计 */
networkStats: {
/** 发送的消息数 */
messagesSent: number;
/** 接收的消息数 */
messagesReceived: number;
/** 发送的字节数 */
bytesSent: number;
/** 接收的字节数 */
bytesReceived: number;
};
}
/**
* 心跳请求
*/
export interface ReqPing {
/** 客户端时间戳 */
timestamp: number;
}
/**
* 心跳响应
*/
export interface ResPing {
/** 服务端时间戳 */
serverTimestamp: number;
/** 客户端时间戳(回传) */
clientTimestamp: number;
}

View File

@@ -1,108 +0,0 @@
/**
* TSRPC 服务协议定义
* 定义API调用和消息类型
*/
import { ServiceProto } from 'tsrpc';
import {
ReqJoinRoom, ResJoinRoom,
ReqServerStatus, ResServerStatus,
ReqPing, ResPing,
MsgNetworkMessage,
MsgSyncVar,
MsgRpcCall,
MsgNetworkObjectSpawn,
MsgNetworkObjectDespawn,
MsgClientDisconnected,
MsgAuthorityChange
} from './NetworkProtocols';
/**
* 网络服务协议
* 定义所有可用的API和消息类型
*/
export const serviceProto: ServiceProto<ServiceType> = {
"services": [
{
"id": 0,
"name": "network/JoinRoom",
"type": "api"
},
{
"id": 1,
"name": "network/ServerStatus",
"type": "api"
},
{
"id": 2,
"name": "network/Ping",
"type": "api"
},
{
"id": 3,
"name": "network/NetworkMessage",
"type": "msg"
},
{
"id": 4,
"name": "network/SyncVar",
"type": "msg"
},
{
"id": 5,
"name": "network/RpcCall",
"type": "msg"
},
{
"id": 6,
"name": "network/NetworkObjectSpawn",
"type": "msg"
},
{
"id": 7,
"name": "network/NetworkObjectDespawn",
"type": "msg"
},
{
"id": 8,
"name": "network/ClientDisconnected",
"type": "msg"
},
{
"id": 9,
"name": "network/AuthorityChange",
"type": "msg"
}
],
"types": {}
};
/**
* 服务类型定义
* 用于类型安全的API调用和消息发送
*/
export interface ServiceType {
api: {
"network/JoinRoom": {
req: ReqJoinRoom;
res: ResJoinRoom;
};
"network/ServerStatus": {
req: ReqServerStatus;
res: ResServerStatus;
};
"network/Ping": {
req: ReqPing;
res: ResPing;
};
};
msg: {
"network/NetworkMessage": MsgNetworkMessage;
"network/SyncVar": MsgSyncVar;
"network/RpcCall": MsgRpcCall;
"network/NetworkObjectSpawn": MsgNetworkObjectSpawn;
"network/NetworkObjectDespawn": MsgNetworkObjectDespawn;
"network/ClientDisconnected": MsgClientDisconnected;
"network/AuthorityChange": MsgAuthorityChange;
};
}

View File

@@ -1,162 +0,0 @@
/**
* 网络库核心类型定义
*/
/**
* 网络连接状态
*/
export type NetworkConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
/**
* 网络端类型
*/
export type NetworkSide = 'client' | 'server' | 'host';
/**
* 网络消息类型
*/
export interface NetworkMessage {
/** 消息类型 */
type: string;
/** 网络对象ID */
networkId: number;
/** 消息数据 */
data: any;
/** 时间戳 */
timestamp: number;
}
/**
* 同步变量消息
*/
export interface SyncVarMessage extends NetworkMessage {
type: 'syncvar';
/** 组件类型名 */
componentType: string;
/** 属性名 */
propertyName: string;
/** 属性值 */
value: any;
}
/**
* RPC消息
*/
export interface RpcMessage extends NetworkMessage {
type: 'rpc';
/** RPC方法名 */
methodName: string;
/** RPC参数 */
args: any[];
/** 是否为客户端RPC */
isClientRpc: boolean;
}
/**
* 网络配置选项
*/
export interface NetworkConfig {
/** 服务器端口 */
port?: number;
/** 服务器地址 */
host?: string;
/** 房间ID */
roomId?: string;
/** 最大连接数 */
maxConnections?: number;
/** 同步频率 (Hz) */
syncRate?: number;
/** 是否启用压缩 */
compression?: boolean;
}
/**
* 网络统计信息
*/
export interface NetworkStats {
/** 连接数 */
connectionCount: number;
/** 发送的字节数 */
bytesSent: number;
/** 接收的字节数 */
bytesReceived: number;
/** 发送的消息数 */
messagesSent: number;
/** 接收的消息数 */
messagesReceived: number;
/** 平均延迟 (ms) */
averageLatency: number;
}
/**
* 网络事件处理器
*/
export interface NetworkEventHandlers {
/** 连接建立 */
onConnected: () => void;
/** 连接断开 */
onDisconnected: (reason?: string) => void;
/** 客户端加入 */
onClientConnected: (clientId: number) => void;
/** 客户端离开 */
onClientDisconnected: (clientId: number, reason?: string) => void;
/** 发生错误 */
onError: (error: Error) => void;
}
/**
* 网络行为接口
* 所有网络组件都需要实现此接口
*/
export interface INetworkBehaviour {
/** 网络身份组件引用 */
networkIdentity: any | null;
/** 是否拥有权威 */
hasAuthority: boolean;
/** 是否为本地玩家 */
isLocalPlayer: boolean;
/** 是否在服务端 */
isServer: boolean;
/** 是否在客户端 */
isClient: boolean;
}
/**
* 同步变量元数据
*/
export interface SyncVarMetadata {
/** 属性名 */
propertyName: string;
/** 是否仅权威端可修改 */
authorityOnly: boolean;
/** 变化回调函数名 */
onChanged?: string;
}
/**
* RPC元数据
*/
export interface RpcMetadata {
/** 方法名 */
methodName: string;
/** 是否为客户端RPC */
isClientRpc: boolean;
/** 是否需要权威验证 */
requiresAuthority: boolean;
}
/**
* 网络连接信息
*/
export interface NetworkConnection {
/** 连接ID */
id: number;
/** 连接状态 */
state: NetworkConnectionState;
/** 延迟 (ms) */
latency: number;
/** 最后活跃时间 */
lastActivity: number;
}

View File

@@ -1,258 +0,0 @@
/**
* 网络库基础功能测试
*/
import 'reflect-metadata';
import { NetworkManager } from '../src/NetworkManager';
import { NetworkIdentity } from '../src/NetworkIdentity';
import { NetworkBehaviour } from '../src/NetworkBehaviour';
import { SyncVar } from '../src/decorators/SyncVar';
import { ClientRpc } from '../src/decorators/ClientRpc';
import { Command } from '../src/decorators/Command';
import { NetworkRegistry } from '../src/Core/NetworkRegistry';
import { SyncVarManager } from '../src/Core/SyncVarManager';
import { RpcManager } from '../src/Core/RpcManager';
// 测试用的玩家组件
class TestPlayerComponent extends NetworkBehaviour {
@SyncVar({ onChanged: 'onHealthChanged' })
public health: number = 100;
@SyncVar()
public playerName: string = 'Player';
public lastHealthChangeValue: number = 0;
@ClientRpc()
public showDamageEffect(damage: number, position: { x: number; y: number }): void {
console.log(`显示伤害特效: ${damage} at (${position.x}, ${position.y})`);
}
@Command()
public movePlayer(direction: { x: number; y: number }): void {
console.log(`移动玩家: (${direction.x}, ${direction.y})`);
}
private onHealthChanged(oldValue: number, newValue: number): void {
this.lastHealthChangeValue = newValue;
console.log(`生命值变化: ${oldValue} -> ${newValue}`);
}
}
// 模拟实体类
class MockEntity {
private components: any[] = [];
public name: string = 'TestEntity';
public addComponent(component: any): void {
this.components.push(component);
component.entity = this;
}
public getComponent(componentType: any): any {
return this.components.find(c => c instanceof componentType);
}
public getComponents(): any[] {
return this.components;
}
}
describe('网络库基础功能测试', () => {
let networkManager: NetworkManager;
let entity: MockEntity;
let networkIdentity: NetworkIdentity;
let playerComponent: TestPlayerComponent;
beforeEach(() => {
// 重置单例
(NetworkManager as any)._instance = null;
NetworkRegistry.instance.reset();
SyncVarManager.instance.clearPendingChanges();
RpcManager.instance.clearPendingCalls();
// 创建网络管理器
networkManager = new NetworkManager();
// 创建测试实体
entity = new MockEntity();
networkIdentity = new NetworkIdentity();
playerComponent = new TestPlayerComponent();
entity.addComponent(networkIdentity);
entity.addComponent(playerComponent);
// 手动调用组件初始化以注册网络行为
playerComponent.start();
});
afterEach(() => {
networkManager?.destroy();
});
describe('网络身份管理', () => {
test('网络对象注册和查找', () => {
const networkId = networkManager.registerNetworkObject(entity);
expect(networkId).toBeGreaterThan(0);
expect(networkIdentity.networkId).toBe(networkId);
const foundIdentity = NetworkRegistry.instance.find(networkId);
expect(foundIdentity).toBe(networkIdentity);
});
test('网络权威设置', () => {
networkManager.registerNetworkObject(entity);
expect(networkIdentity.hasAuthority).toBe(false);
expect(playerComponent.hasAuthority).toBe(false);
networkIdentity.setAuthority(true, 1);
expect(networkIdentity.hasAuthority).toBe(true);
expect(networkIdentity.ownerId).toBe(1);
expect(playerComponent.hasAuthority).toBe(true);
});
test('本地玩家设置', () => {
networkManager.registerNetworkObject(entity);
expect(networkIdentity.isLocalPlayer).toBe(false);
expect(playerComponent.isLocalPlayer).toBe(false);
NetworkRegistry.instance.setLocalPlayer(networkIdentity);
expect(networkIdentity.isLocalPlayer).toBe(true);
expect(networkIdentity.hasAuthority).toBe(true);
expect(playerComponent.isLocalPlayer).toBe(true);
});
});
describe('SyncVar 同步变量', () => {
test('SyncVar 属性同步', () => {
networkManager.registerNetworkObject(entity);
networkIdentity.setAuthority(true, 1);
// 修改同步变量
playerComponent.health = 80;
playerComponent.playerName = 'TestPlayer';
// 检查待同步消息
const messages = SyncVarManager.instance.getPendingMessages();
expect(messages.length).toBeGreaterThanOrEqual(2);
// 验证消息内容
const healthMessage = messages.find((m: any) => m.propertyName === 'health');
expect(healthMessage).toBeDefined();
expect(healthMessage?.value).toBe(80);
const nameMessage = messages.find((m: any) => m.propertyName === 'playerName');
expect(nameMessage).toBeDefined();
expect(nameMessage?.value).toBe('TestPlayer');
});
test('SyncVar 变化回调', () => {
networkManager.registerNetworkObject(entity);
networkIdentity.setAuthority(true, 1);
expect(playerComponent.lastHealthChangeValue).toBe(0);
playerComponent.health = 75;
expect(playerComponent.lastHealthChangeValue).toBe(75);
});
test('权威验证', () => {
networkManager.registerNetworkObject(entity);
// 没有权威时不应该能修改
expect(networkIdentity.hasAuthority).toBe(false);
const originalHealth = playerComponent.health;
playerComponent.health = 50;
// 检查是否有待同步消息(没有权威应该没有)
const messages = SyncVarManager.instance.getPendingMessages();
expect(messages.length).toBe(0);
});
});
describe('RPC 远程过程调用', () => {
test('RPC 方法注册', () => {
networkManager.registerNetworkObject(entity);
const stats = RpcManager.instance.getStats();
expect(stats.registeredComponents).toBeGreaterThan(0);
});
test('RPC 消息生成', () => {
networkManager.registerNetworkObject(entity);
// 模拟服务端调用ClientRpc
if (NetworkManager.isServer) {
playerComponent.showDamageEffect(25, { x: 100, y: 200 });
const rpcMessages = RpcManager.instance.getPendingRpcMessages();
const damageMessage = rpcMessages.find((m: any) => m.methodName === 'showDamageEffect');
expect(damageMessage).toBeDefined();
expect(damageMessage?.isClientRpc).toBe(true);
}
});
});
describe('网络管理器状态', () => {
test('网络端类型判断', () => {
// 默认应该是客户端
expect(NetworkManager.isClient).toBe(true);
expect(NetworkManager.isServer).toBe(false);
expect(NetworkManager.isConnected).toBe(false);
});
test('连接状态管理', () => {
expect(networkManager.getConnectionState()).toBe('disconnected');
});
test('网络统计信息', () => {
const stats = networkManager.getStats();
expect(stats).toHaveProperty('connectionCount');
expect(stats).toHaveProperty('messagesSent');
expect(stats).toHaveProperty('messagesReceived');
expect(stats.connectionCount).toBe(0);
});
});
describe('网络注册表管理', () => {
test('多个网络对象管理', () => {
// 创建多个实体
const entity2 = new MockEntity();
const networkIdentity2 = new NetworkIdentity();
entity2.addComponent(networkIdentity2);
const playerComponent2 = new TestPlayerComponent();
entity2.addComponent(playerComponent2);
playerComponent2.start();
const networkId1 = networkManager.registerNetworkObject(entity);
const networkId2 = networkManager.registerNetworkObject(entity2);
expect(networkId1).not.toBe(networkId2);
expect(NetworkRegistry.instance.getAllNetworkObjects().length).toBe(2);
});
test('网络对象注销', () => {
const networkId = networkManager.registerNetworkObject(entity);
expect(NetworkRegistry.instance.exists(networkId)).toBe(true);
NetworkRegistry.instance.unregister(networkId);
expect(NetworkRegistry.instance.exists(networkId)).toBe(false);
expect(NetworkRegistry.instance.find(networkId)).toBeNull();
});
test('按所有者查找对象', () => {
const networkId = networkManager.registerNetworkObject(entity);
networkIdentity.setAuthority(true, 123);
const ownedObjects = NetworkRegistry.instance.getObjectsByOwner(123);
expect(ownedObjects.length).toBe(1);
expect(ownedObjects[0]).toBe(networkIdentity);
});
});
});

View File

@@ -1,145 +0,0 @@
/**
* TSRPC传输层测试
*/
import 'reflect-metadata';
import { TsrpcTransport } from '../src/transport/TsrpcTransport';
import { NetworkConfig } from '../src/Types/NetworkTypes';
// 简化测试,只验证基本功能
describe('TSRPC传输层测试', () => {
let serverTransport: TsrpcTransport;
let clientTransport: TsrpcTransport;
const serverConfig: NetworkConfig = {
port: 18888, // 使用不同端口避免冲突
host: 'localhost',
syncRate: 20
};
const clientConfig: NetworkConfig = {
port: 18888,
host: 'localhost'
};
beforeEach(() => {
serverTransport = new TsrpcTransport(serverConfig);
clientTransport = new TsrpcTransport(clientConfig);
});
afterEach(async () => {
if (serverTransport) {
await serverTransport.disconnect();
}
if (clientTransport) {
await clientTransport.disconnect();
}
});
describe('传输层创建', () => {
test('创建服务端传输层', () => {
expect(serverTransport).toBeDefined();
expect(serverTransport.getNetworkSide()).toBe('client'); // 默认为客户端
expect(serverTransport.isConnected()).toBe(false);
});
test('创建客户端传输层', () => {
expect(clientTransport).toBeDefined();
expect(clientTransport.getNetworkSide()).toBe('client');
expect(clientTransport.isConnected()).toBe(false);
});
});
describe('事件处理器设置', () => {
test('设置事件处理器', () => {
let connectedCalled = false;
let disconnectedCalled = false;
serverTransport.setEventHandlers({
onConnected: () => {
connectedCalled = true;
},
onDisconnected: () => {
disconnectedCalled = true;
}
});
// 验证事件处理器被正确设置
expect(connectedCalled).toBe(false);
expect(disconnectedCalled).toBe(false);
});
test('单独设置事件处理器', () => {
let errorCalled = false;
serverTransport.on('onError', (error) => {
errorCalled = true;
});
expect(errorCalled).toBe(false);
});
});
describe('基本功能验证', () => {
test('获取统计信息', () => {
const stats = serverTransport.getStats();
expect(stats).toHaveProperty('messagesSent');
expect(stats).toHaveProperty('messagesReceived');
expect(stats).toHaveProperty('bytesSent');
expect(stats).toHaveProperty('bytesReceived');
expect(stats).toHaveProperty('clientCount');
expect(stats).toHaveProperty('uptime');
// 初始值应该为0
expect(stats.messagesSent).toBe(0);
expect(stats.messagesReceived).toBe(0);
expect(stats.clientCount).toBe(0);
});
test('客户端模式方法调用异常处理', async () => {
// 客户端模式下调用服务端方法应该抛出错误
await expect(serverTransport.getServerStatus()).rejects.toThrow('只能在客户端模式下查询服务端状态');
await expect(serverTransport.ping()).rejects.toThrow('只能在客户端模式下发送心跳');
});
test('未初始化时发送消息异常处理', async () => {
const testMessage = {
type: 'test',
networkId: 1,
data: { test: 'data' },
timestamp: Date.now()
};
// 未连接时发送消息应该抛出错误
await expect(serverTransport.sendMessage(testMessage)).rejects.toThrow('传输层未初始化或状态错误');
await expect(serverTransport.sendSyncVar(1, 'TestComponent', 'testProp', 'testValue')).rejects.toThrow('传输层未初始化或状态错误');
await expect(serverTransport.sendRpcCall(1, 'TestComponent', 'testMethod', [], true)).rejects.toThrow('传输层未初始化或状态错误');
});
});
describe('网络配置', () => {
test('获取正确的网络端类型', async () => {
// 测试服务端模式
const config: NetworkConfig = {
port: 18889,
host: 'localhost'
};
const transport = new TsrpcTransport(config);
expect(transport.getNetworkSide()).toBe('client'); // 创建时默认为客户端
await transport.disconnect();
});
test('获取客户端ID和连接信息', () => {
expect(serverTransport.getClientId()).toBe(0); // 未连接时为0
expect(serverTransport.getConnectedClients()).toEqual([]); // 客户端模式返回空数组
expect(serverTransport.getClientCount()).toBe(0); // 客户端模式返回0
});
});
// 注意:由于在测试环境中启动真实的网络服务可能很复杂,
// 这里主要测试API的正确性和错误处理
// 真正的端到端网络测试需要在集成测试中进行
});

View File

@@ -1,28 +0,0 @@
/**
* Jest测试设置文件
*/
import 'reflect-metadata';
// 全局Jest配置
expect.extend({});
// 设置测试环境变量
process.env.NODE_ENV = 'test';
// 全局错误处理
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
// 设置全局测试超时
jest.setTimeout(10000);
// 清理函数
afterEach(() => {
// 清理所有定时器
jest.clearAllTimers();
// 清理所有模拟
jest.clearAllMocks();
});

View File

@@ -1,45 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"lib": ["ES2020", "DOM"],
"outDir": "./bin",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"exactOptionalPropertyTypes": false,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noUncheckedIndexedAccess": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"importHelpers": false,
"downlevelIteration": true,
"isolatedModules": false,
"allowJs": true,
"resolveJsonModule": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"bin",
"dist",
"tests",
"**/*.test.ts",
"**/*.spec.ts"
]
}

View File

@@ -1,16 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"skipLibCheck": true
},
"include": [
"src/**/*",
"tests/**/*"
],
"exclude": [
"node_modules",
"bin",
"dist"
]
}