更新network库及core库优化
This commit is contained in:
@@ -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);
|
||||
@@ -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/'
|
||||
]
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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']
|
||||
}
|
||||
];
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* 网络身份组件
|
||||
*
|
||||
* 标识网络对象的唯一身份,管理网络组件和权威性
|
||||
* 所有需要网络同步的实体都必须拥有此组件
|
||||
*/
|
||||
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { INetworkBehaviour } from './Types/NetworkTypes';
|
||||
|
||||
export class NetworkIdentity extends Component {
|
||||
/** 网络对象的唯一标识符 */
|
||||
public networkId: number = 0;
|
||||
|
||||
/** 所有者客户端ID,0 表示服务端拥有 */
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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的正确性和错误处理,
|
||||
// 真正的端到端网络测试需要在集成测试中进行
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"tests/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"bin",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user