重新整理网络架构,tsrpc/syncvar并行
This commit is contained in:
@@ -1,218 +0,0 @@
|
||||
import { NetworkRole } from '../src/NetworkRole';
|
||||
|
||||
// 模拟Component基类
|
||||
class Component {
|
||||
public update(): void {
|
||||
// 默认空实现
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟INetworkSyncable接口
|
||||
interface INetworkSyncable {
|
||||
getNetworkState(): Uint8Array;
|
||||
applyNetworkState(data: Uint8Array): void;
|
||||
getDirtyFields(): number[];
|
||||
markClean(): void;
|
||||
markFieldDirty(fieldNumber: number): void;
|
||||
isFieldDirty(fieldNumber: number): boolean;
|
||||
}
|
||||
|
||||
// 简化版NetworkComponent用于测试
|
||||
class TestableNetworkComponent extends Component implements INetworkSyncable {
|
||||
private _dirtyFields: Set<number> = new Set();
|
||||
private _fieldTimestamps: Map<number, number> = new Map();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public getRole(): NetworkRole {
|
||||
// 模拟环境检测,默认返回客户端
|
||||
return NetworkRole.CLIENT;
|
||||
}
|
||||
|
||||
public isClient(): boolean {
|
||||
return true; // 在测试中简化为始终是客户端
|
||||
}
|
||||
|
||||
public isServer(): boolean {
|
||||
return false; // 在测试中简化为始终不是服务端
|
||||
}
|
||||
|
||||
public onClientUpdate(): void {
|
||||
// 默认空实现
|
||||
}
|
||||
|
||||
public onServerUpdate(): void {
|
||||
// 默认空实现
|
||||
}
|
||||
|
||||
public override update(): void {
|
||||
if (this.isClient()) {
|
||||
this.onClientUpdate();
|
||||
} else if (this.isServer()) {
|
||||
this.onServerUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public getNetworkState(): Uint8Array {
|
||||
return new Uint8Array([1, 2, 3]); // 模拟数据
|
||||
}
|
||||
|
||||
public applyNetworkState(data: Uint8Array): void {
|
||||
this.markClean();
|
||||
}
|
||||
|
||||
public getDirtyFields(): number[] {
|
||||
return Array.from(this._dirtyFields);
|
||||
}
|
||||
|
||||
public markClean(): void {
|
||||
this._dirtyFields.clear();
|
||||
}
|
||||
|
||||
public markFieldDirty(fieldNumber: number): void {
|
||||
this._dirtyFields.add(fieldNumber);
|
||||
this._fieldTimestamps.set(fieldNumber, Date.now());
|
||||
}
|
||||
|
||||
public isFieldDirty(fieldNumber: number): boolean {
|
||||
return this._dirtyFields.has(fieldNumber);
|
||||
}
|
||||
|
||||
public getFieldTimestamp(fieldNumber: number): number {
|
||||
return this._fieldTimestamps.get(fieldNumber) || 0;
|
||||
}
|
||||
|
||||
public getDirtyFieldsWithTimestamps(): Map<number, number> {
|
||||
const result = new Map<number, number>();
|
||||
for (const fieldNumber of this._dirtyFields) {
|
||||
result.set(fieldNumber, this._fieldTimestamps.get(fieldNumber) || 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class TestNetworkComponent extends TestableNetworkComponent {
|
||||
public value: number = 0;
|
||||
|
||||
constructor(value: number = 0) {
|
||||
super();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public override onClientUpdate(): void {
|
||||
this.value += 1;
|
||||
this.markFieldDirty(1);
|
||||
}
|
||||
|
||||
public override onServerUpdate(): void {
|
||||
this.value += 10;
|
||||
this.markFieldDirty(1);
|
||||
}
|
||||
}
|
||||
|
||||
describe('NetworkComponent', () => {
|
||||
describe('角色功能', () => {
|
||||
test('应该正确获取角色信息', () => {
|
||||
const component = new TestNetworkComponent();
|
||||
|
||||
expect(component.getRole()).toBe(NetworkRole.CLIENT);
|
||||
expect(component.isClient()).toBe(true);
|
||||
expect(component.isServer()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('更新逻辑', () => {
|
||||
test('组件应该调用对应的更新方法', () => {
|
||||
const component = new TestNetworkComponent(5);
|
||||
|
||||
component.update();
|
||||
|
||||
expect(component.value).toBe(6); // 5 + 1 (客户端更新)
|
||||
expect(component.getDirtyFields()).toContain(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('脏字段管理', () => {
|
||||
test('应该正确标记和检查脏字段', () => {
|
||||
const component = new TestNetworkComponent();
|
||||
|
||||
expect(component.isFieldDirty(1)).toBe(false);
|
||||
|
||||
component.markFieldDirty(1);
|
||||
|
||||
expect(component.isFieldDirty(1)).toBe(true);
|
||||
expect(component.getDirtyFields()).toContain(1);
|
||||
});
|
||||
|
||||
test('应该正确清理脏字段', () => {
|
||||
const component = new TestNetworkComponent();
|
||||
|
||||
component.markFieldDirty(1);
|
||||
component.markFieldDirty(2);
|
||||
|
||||
expect(component.getDirtyFields()).toEqual(expect.arrayContaining([1, 2]));
|
||||
|
||||
component.markClean();
|
||||
|
||||
expect(component.getDirtyFields()).toEqual([]);
|
||||
expect(component.isFieldDirty(1)).toBe(false);
|
||||
expect(component.isFieldDirty(2)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该正确记录字段时间戳', () => {
|
||||
const component = new TestNetworkComponent();
|
||||
const beforeTime = Date.now();
|
||||
|
||||
component.markFieldDirty(1);
|
||||
|
||||
const timestamp = component.getFieldTimestamp(1);
|
||||
const afterTime = Date.now();
|
||||
|
||||
expect(timestamp).toBeGreaterThanOrEqual(beforeTime);
|
||||
expect(timestamp).toBeLessThanOrEqual(afterTime);
|
||||
});
|
||||
|
||||
test('应该正确获取脏字段和时间戳', () => {
|
||||
const component = new TestNetworkComponent();
|
||||
|
||||
component.markFieldDirty(1);
|
||||
component.markFieldDirty(3);
|
||||
|
||||
const dirtyFieldsWithTimestamps = component.getDirtyFieldsWithTimestamps();
|
||||
|
||||
expect(dirtyFieldsWithTimestamps.size).toBe(2);
|
||||
expect(dirtyFieldsWithTimestamps.has(1)).toBe(true);
|
||||
expect(dirtyFieldsWithTimestamps.has(3)).toBe(true);
|
||||
expect(dirtyFieldsWithTimestamps.get(1)).toBeGreaterThan(0);
|
||||
expect(dirtyFieldsWithTimestamps.get(3)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('网络状态序列化', () => {
|
||||
test('应该能获取网络状态', () => {
|
||||
const component = new TestNetworkComponent(42);
|
||||
|
||||
expect(() => {
|
||||
const state = component.getNetworkState();
|
||||
expect(state).toBeInstanceOf(Uint8Array);
|
||||
expect(state.length).toBeGreaterThan(0);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('应该能应用网络状态', () => {
|
||||
const sourceComponent = new TestNetworkComponent(100);
|
||||
const targetComponent = new TestNetworkComponent(0);
|
||||
|
||||
const networkState = sourceComponent.getNetworkState();
|
||||
|
||||
expect(() => {
|
||||
targetComponent.applyNetworkState(networkState);
|
||||
}).not.toThrow();
|
||||
|
||||
// 应用状态后应该清理脏字段
|
||||
expect(targetComponent.getDirtyFields()).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
import { NetworkManager } from '../src/Core/NetworkManager';
|
||||
import { MessageHandler } from '../src/Messaging/MessageHandler';
|
||||
import { JsonMessage } from '../src/Messaging/NetworkMessage';
|
||||
|
||||
// 测试消息
|
||||
class TestMessage extends JsonMessage<{ text: string }> {
|
||||
public override readonly messageType: number = 1000;
|
||||
|
||||
constructor(text: string = 'test') {
|
||||
super({ text });
|
||||
}
|
||||
}
|
||||
|
||||
describe('网络核心功能测试', () => {
|
||||
let serverPort: number;
|
||||
|
||||
beforeEach(() => {
|
||||
// 每个测试使用不同端口避免冲突
|
||||
serverPort = 8000 + Math.floor(Math.random() * 2000);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
// 强制重置NetworkManager实例
|
||||
const manager = (NetworkManager as any).Instance;
|
||||
if (manager) {
|
||||
// 直接重置内部状态
|
||||
manager._isServer = false;
|
||||
manager._isClient = false;
|
||||
manager._server = null;
|
||||
manager._client = null;
|
||||
}
|
||||
|
||||
// 重置单例实例
|
||||
(NetworkManager as any)._instance = null;
|
||||
|
||||
// 清理消息处理器
|
||||
MessageHandler.Instance.clear();
|
||||
|
||||
// 短暂等待
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
} catch (error) {
|
||||
console.warn('清理时发生错误:', error);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
describe('NetworkManager', () => {
|
||||
test('应该能启动和停止服务端', async () => {
|
||||
// 启动服务端
|
||||
const startResult = await NetworkManager.StartServer(serverPort);
|
||||
expect(startResult).toBe(true);
|
||||
expect(NetworkManager.isServer).toBe(true);
|
||||
expect(NetworkManager.connectionCount).toBe(0);
|
||||
|
||||
// 停止服务端
|
||||
await NetworkManager.StopServer();
|
||||
expect(NetworkManager.isServer).toBe(false);
|
||||
}, 10000);
|
||||
|
||||
test('应该能启动和停止客户端', async () => {
|
||||
// 先启动服务端
|
||||
const serverStarted = await NetworkManager.StartServer(serverPort);
|
||||
expect(serverStarted).toBe(true);
|
||||
|
||||
// 等待服务端完全启动
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// 启动客户端
|
||||
const connectResult = await NetworkManager.StartClient(`ws://localhost:${serverPort}`);
|
||||
expect(connectResult).toBe(true);
|
||||
expect(NetworkManager.isClient).toBe(true);
|
||||
|
||||
// 停止客户端
|
||||
await NetworkManager.StopClient();
|
||||
expect(NetworkManager.isClient).toBe(false);
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('消息系统', () => {
|
||||
test('应该能注册和处理消息', async () => {
|
||||
let receivedMessage: TestMessage | null = null;
|
||||
|
||||
// 注册消息处理器
|
||||
MessageHandler.Instance.registerHandler(
|
||||
1000,
|
||||
TestMessage,
|
||||
{
|
||||
handle: (message: TestMessage) => {
|
||||
receivedMessage = message;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 创建测试消息
|
||||
const testMessage = new TestMessage('Hello World');
|
||||
|
||||
// 序列化和反序列化测试
|
||||
const serialized = testMessage.serialize();
|
||||
expect(serialized.length).toBeGreaterThan(0);
|
||||
|
||||
// 模拟消息处理
|
||||
await MessageHandler.Instance.handleRawMessage(serialized);
|
||||
|
||||
// 验证消息被正确处理
|
||||
expect(receivedMessage).not.toBeNull();
|
||||
expect(receivedMessage!.payload!.text).toBe('Hello World');
|
||||
});
|
||||
|
||||
test('应该能处理多个处理器', async () => {
|
||||
let handler1Called = false;
|
||||
let handler2Called = false;
|
||||
|
||||
// 注册多个处理器
|
||||
MessageHandler.Instance.registerHandler(1000, TestMessage, {
|
||||
handle: () => { handler1Called = true; }
|
||||
}, 0);
|
||||
|
||||
MessageHandler.Instance.registerHandler(1000, TestMessage, {
|
||||
handle: () => { handler2Called = true; }
|
||||
}, 1);
|
||||
|
||||
// 发送消息
|
||||
const testMessage = new TestMessage('Test');
|
||||
await MessageHandler.Instance.handleMessage(testMessage);
|
||||
|
||||
// 验证两个处理器都被调用
|
||||
expect(handler1Called).toBe(true);
|
||||
expect(handler2Called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// 暂时跳过端到端通信测试,等其他问题修复后再处理
|
||||
describe.skip('端到端通信', () => {
|
||||
test('客户端和服务端应该能相互通信', async () => {
|
||||
// 这个测试有复杂的WebSocket连接同步问题,暂时跳过
|
||||
});
|
||||
});
|
||||
});
|
||||
258
packages/network/tests/NetworkLibrary.test.ts
Normal file
258
packages/network/tests/NetworkLibrary.test.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* 网络库基础功能测试
|
||||
*/
|
||||
|
||||
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 => m.propertyName === 'health');
|
||||
expect(healthMessage).toBeDefined();
|
||||
expect(healthMessage?.value).toBe(80);
|
||||
|
||||
const nameMessage = messages.find(m => 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 => 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,113 +0,0 @@
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { TsrpcSerializer, SyncField } from '../../src/Serialization';
|
||||
import { TsrpcSerializable } from '../../src/Serialization/TsrpcDecorators';
|
||||
|
||||
@TsrpcSerializable()
|
||||
class TestComponent extends Component {
|
||||
@SyncField()
|
||||
public health: number = 100;
|
||||
|
||||
@SyncField()
|
||||
public name: string = 'Test';
|
||||
|
||||
@SyncField()
|
||||
public isActive: boolean = true;
|
||||
}
|
||||
|
||||
describe('TsrpcSerializer', () => {
|
||||
let serializer: TsrpcSerializer;
|
||||
let testComponent: TestComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
serializer = TsrpcSerializer.getInstance();
|
||||
testComponent = new TestComponent();
|
||||
testComponent.health = 80;
|
||||
testComponent.name = 'Player';
|
||||
testComponent.isActive = false;
|
||||
});
|
||||
|
||||
describe('序列化', () => {
|
||||
it('应该能序列化TSRPC组件', () => {
|
||||
const result = serializer.serialize(testComponent);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.type).toBe('tsrpc');
|
||||
expect(result?.componentType).toBe('TestComponent');
|
||||
expect(result?.data).toBeInstanceOf(Uint8Array);
|
||||
expect(result?.size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('不支持的组件应该返回null', () => {
|
||||
// 创建一个没有装饰器的组件类
|
||||
class UnsupportedComponent extends Component {}
|
||||
const unsupportedComponent = new UnsupportedComponent();
|
||||
const result = serializer.serialize(unsupportedComponent);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('反序列化', () => {
|
||||
it('应该能反序列化TSRPC数据', () => {
|
||||
// 先序列化
|
||||
const serializedData = serializer.serialize(testComponent);
|
||||
expect(serializedData).not.toBeNull();
|
||||
|
||||
// 再反序列化
|
||||
const deserializedComponent = serializer.deserialize(serializedData!, TestComponent);
|
||||
|
||||
expect(deserializedComponent).not.toBeNull();
|
||||
expect(deserializedComponent?.health).toBe(80);
|
||||
expect(deserializedComponent?.name).toBe('Player');
|
||||
expect(deserializedComponent?.isActive).toBe(false);
|
||||
});
|
||||
|
||||
it('错误的数据类型应该返回null', () => {
|
||||
const invalidData = {
|
||||
type: 'json' as const,
|
||||
componentType: 'TestComponent',
|
||||
data: {},
|
||||
size: 0
|
||||
};
|
||||
|
||||
const result = serializer.deserialize(invalidData);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('统计信息', () => {
|
||||
it('应该正确更新统计信息', () => {
|
||||
const initialStats = serializer.getStats();
|
||||
|
||||
// 执行序列化
|
||||
serializer.serialize(testComponent);
|
||||
|
||||
const afterSerializeStats = serializer.getStats();
|
||||
expect(afterSerializeStats.serializeCount).toBe(initialStats.serializeCount + 1);
|
||||
|
||||
// 执行反序列化
|
||||
const serializedData = serializer.serialize(testComponent);
|
||||
if (serializedData) {
|
||||
serializer.deserialize(serializedData, TestComponent);
|
||||
}
|
||||
|
||||
const finalStats = serializer.getStats();
|
||||
expect(finalStats.deserializeCount).toBe(initialStats.deserializeCount + 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能功能', () => {
|
||||
it('应该正确计算序列化大小', () => {
|
||||
const initialStats = serializer.getStats();
|
||||
|
||||
// 执行序列化
|
||||
const result = serializer.serialize(testComponent);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.size).toBeGreaterThan(0);
|
||||
|
||||
const finalStats = serializer.getStats();
|
||||
expect(finalStats.averageSerializedSize).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 序列化模块集成测试
|
||||
*/
|
||||
|
||||
// 导入所有测试
|
||||
import './TsrpcSerializer.test';
|
||||
|
||||
// 这个文件确保所有序列化相关的测试都被包含在测试套件中
|
||||
describe('序列化模块集成测试', () => {
|
||||
it('应该包含所有序列化测试', () => {
|
||||
// 这个测试确保模块正确加载
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,261 +0,0 @@
|
||||
import { SyncVar, getSyncVarMetadata, SyncVarManager } from '../src/SyncVar';
|
||||
import { createNetworkComponent } from '../src/SyncVar/SyncVarFactory';
|
||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
||||
import { NetworkComponent } from '../src/NetworkComponent';
|
||||
|
||||
// 测试用的组件类
|
||||
class TestPlayerComponent extends NetworkComponent {
|
||||
private _hasAuthority: boolean = false;
|
||||
|
||||
public hasAuthority(): boolean {
|
||||
return this._hasAuthority;
|
||||
}
|
||||
|
||||
public setAuthority(hasAuthority: boolean): void {
|
||||
this._hasAuthority = hasAuthority;
|
||||
}
|
||||
@SyncVar()
|
||||
public health: number = 100;
|
||||
|
||||
@SyncVar({ hook: 'onNameChanged' })
|
||||
public playerName: string = 'Player';
|
||||
|
||||
@SyncVar({ authorityOnly: true })
|
||||
public isReady: boolean = false;
|
||||
|
||||
@SyncVar()
|
||||
public position = { x: 0, y: 0 };
|
||||
|
||||
// Hook回调函数
|
||||
public onNameChangedCallCount = 0;
|
||||
public lastNameChange: { oldName: string; newName: string } | null = null;
|
||||
|
||||
onNameChanged(oldName: string, newName: string) {
|
||||
this.onNameChangedCallCount++;
|
||||
this.lastNameChange = { oldName, newName };
|
||||
console.log(`Name changed: ${oldName} -> ${newName}`);
|
||||
}
|
||||
}
|
||||
|
||||
class TestComponentWithoutSyncVar extends NetworkComponent {
|
||||
public normalField: number = 42;
|
||||
|
||||
public hasAuthority(): boolean { return true; }
|
||||
}
|
||||
|
||||
describe('SyncVar系统测试', () => {
|
||||
beforeEach(() => {
|
||||
// 清理SyncVar管理器
|
||||
const manager = SyncVarManager.Instance;
|
||||
manager['_componentChanges'].clear();
|
||||
manager['_lastSyncTimes'].clear();
|
||||
});
|
||||
|
||||
describe('装饰器和元数据', () => {
|
||||
test('应该正确收集SyncVar元数据', () => {
|
||||
const metadata = getSyncVarMetadata(TestPlayerComponent);
|
||||
|
||||
expect(metadata.length).toBe(4);
|
||||
|
||||
const healthMeta = metadata.find(m => m.propertyKey === 'health');
|
||||
expect(healthMeta).toBeDefined();
|
||||
expect(healthMeta!.fieldNumber).toBe(1);
|
||||
expect(healthMeta!.options.hook).toBeUndefined();
|
||||
|
||||
const nameMeta = metadata.find(m => m.propertyKey === 'playerName');
|
||||
expect(nameMeta).toBeDefined();
|
||||
expect(nameMeta!.options.hook).toBe('onNameChanged');
|
||||
|
||||
const readyMeta = metadata.find(m => m.propertyKey === 'isReady');
|
||||
expect(readyMeta).toBeDefined();
|
||||
expect(readyMeta!.options.authorityOnly).toBe(true);
|
||||
});
|
||||
|
||||
test('没有SyncVar的组件应该返回空元数据', () => {
|
||||
const metadata = getSyncVarMetadata(TestComponentWithoutSyncVar);
|
||||
expect(metadata.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('代理和变化检测', () => {
|
||||
test('代理应该能检测到字段变化', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 修改SyncVar字段
|
||||
proxy.health = 80;
|
||||
|
||||
const changes = proxy.getSyncVarChanges();
|
||||
expect(changes.length).toBe(1);
|
||||
expect(changes[0].propertyKey).toBe('health');
|
||||
expect(changes[0].oldValue).toBe(100);
|
||||
expect(changes[0].newValue).toBe(80);
|
||||
});
|
||||
|
||||
test('非SyncVar字段不应该被记录', () => {
|
||||
class TestMixedComponent extends NetworkComponent {
|
||||
@SyncVar()
|
||||
public syncField: number = 1;
|
||||
|
||||
public normalField: number = 2;
|
||||
|
||||
public hasAuthority(): boolean { return true; }
|
||||
}
|
||||
|
||||
const instance = new TestMixedComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 修改SyncVar字段
|
||||
proxy.syncField = 10;
|
||||
// 修改普通字段
|
||||
proxy.normalField = 20;
|
||||
|
||||
const changes = proxy.getSyncVarChanges();
|
||||
expect(changes.length).toBe(1);
|
||||
expect(changes[0].propertyKey).toBe('syncField');
|
||||
});
|
||||
|
||||
test('Hook回调应该被触发', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 修改带hook的字段
|
||||
proxy.playerName = 'NewPlayer';
|
||||
|
||||
expect(proxy.onNameChangedCallCount).toBe(1);
|
||||
expect(proxy.lastNameChange).toEqual({
|
||||
oldName: 'Player',
|
||||
newName: 'NewPlayer'
|
||||
});
|
||||
});
|
||||
|
||||
test('相同值不应该触发变化记录', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 设置相同的值
|
||||
proxy.health = 100; // 原始值就是100
|
||||
|
||||
const changes = proxy.getSyncVarChanges();
|
||||
expect(changes.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('同步数据创建和应用', () => {
|
||||
test('应该能创建同步数据', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 修改多个字段
|
||||
proxy.health = 75;
|
||||
proxy.playerName = 'Hero';
|
||||
|
||||
const syncData = proxy.createSyncVarData();
|
||||
expect(syncData).not.toBeNull();
|
||||
expect(syncData.componentType).toBe('TestPlayerComponent');
|
||||
expect(syncData.fieldUpdates.length).toBe(2);
|
||||
});
|
||||
|
||||
test('没有变化时不应该创建同步数据', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
const syncData = proxy.createSyncVarData();
|
||||
expect(syncData).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能应用同步数据', () => {
|
||||
const sourceInstance = new TestPlayerComponent();
|
||||
const sourceProxy = createSyncVarProxy(sourceInstance);
|
||||
|
||||
const targetInstance = new TestPlayerComponent();
|
||||
const targetProxy = createSyncVarProxy(targetInstance);
|
||||
|
||||
// 修改源实例
|
||||
sourceProxy.health = 60;
|
||||
sourceProxy.playerName = 'Warrior';
|
||||
|
||||
// 创建同步数据
|
||||
const syncData = sourceProxy.createSyncVarData();
|
||||
expect(syncData).not.toBeNull();
|
||||
|
||||
// 应用到目标实例
|
||||
targetProxy.applySyncVarData(syncData);
|
||||
|
||||
// 验证目标实例的值已更新
|
||||
expect(targetProxy.health).toBe(60);
|
||||
expect(targetProxy.playerName).toBe('Warrior');
|
||||
|
||||
// 验证hook被触发
|
||||
expect(targetProxy.onNameChangedCallCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('对象类型同步', () => {
|
||||
test('应该能同步对象类型', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 修改对象字段
|
||||
proxy.position = { x: 100, y: 200 };
|
||||
|
||||
const changes = proxy.getSyncVarChanges();
|
||||
expect(changes.length).toBe(1);
|
||||
expect(changes[0].propertyKey).toBe('position');
|
||||
expect(changes[0].newValue).toEqual({ x: 100, y: 200 });
|
||||
});
|
||||
|
||||
test('对象浅比较应该正确工作', () => {
|
||||
const instance = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(instance);
|
||||
|
||||
// 设置相同的对象值
|
||||
proxy.position = { x: 0, y: 0 }; // 原始值
|
||||
|
||||
const changes = proxy.getSyncVarChanges();
|
||||
expect(changes.length).toBe(0); // 应该没有变化
|
||||
});
|
||||
});
|
||||
|
||||
describe('工厂函数', () => {
|
||||
test('createNetworkComponent应该为有SyncVar的组件创建代理', () => {
|
||||
const component = createNetworkComponent(TestPlayerComponent);
|
||||
|
||||
expect(component.hasSyncVars()).toBe(true);
|
||||
|
||||
// 测试代理功能
|
||||
component.health = 90;
|
||||
const changes = component.getSyncVarChanges();
|
||||
expect(changes.length).toBe(1);
|
||||
});
|
||||
|
||||
test('createNetworkComponent应该为没有SyncVar的组件返回原实例', () => {
|
||||
const component = createNetworkComponent(TestComponentWithoutSyncVar);
|
||||
|
||||
expect(component.hasSyncVars()).toBe(false);
|
||||
|
||||
// 修改普通字段不应该有变化记录
|
||||
component.normalField = 999;
|
||||
const changes = component.getSyncVarChanges();
|
||||
expect(changes.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('管理器统计', () => {
|
||||
test('应该能获取管理器统计信息', () => {
|
||||
const component1 = createNetworkComponent(TestPlayerComponent);
|
||||
const component2 = createNetworkComponent(TestPlayerComponent);
|
||||
|
||||
component1.health = 80;
|
||||
component2.health = 70;
|
||||
component2.playerName = 'Test';
|
||||
|
||||
const manager = SyncVarManager.Instance;
|
||||
const stats = manager.getStats();
|
||||
|
||||
expect(stats.totalComponents).toBe(2);
|
||||
expect(stats.totalChanges).toBe(3); // 1 + 2 = 3
|
||||
expect(stats.pendingChanges).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,400 +0,0 @@
|
||||
import { NetworkIdentity, NetworkIdentityRegistry } from '../src/Core/NetworkIdentity';
|
||||
import { SyncVar, SyncVarManager } from '../src/SyncVar';
|
||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
||||
import { SyncVarSyncScheduler } from '../src/SyncVar/SyncVarSyncScheduler';
|
||||
import { SyncVarOptimizer } from '../src/SyncVar/SyncVarOptimizer';
|
||||
import { SyncVarUpdateMessage } from '../src/Messaging/MessageTypes';
|
||||
import { NetworkComponent } from '../src/NetworkComponent';
|
||||
import { NetworkEnvironment, NetworkEnvironmentState } from '../src/Core/NetworkEnvironment';
|
||||
|
||||
// 测试用网络组件
|
||||
class TestGameObject extends NetworkComponent {
|
||||
@SyncVar()
|
||||
public health: number = 100;
|
||||
|
||||
@SyncVar({ hook: 'onPositionChanged' })
|
||||
public position: { x: number; y: number } = { x: 0, y: 0 };
|
||||
|
||||
@SyncVar({ authorityOnly: true })
|
||||
public serverFlag: boolean = false;
|
||||
|
||||
@SyncVar()
|
||||
public playerName: string = 'TestPlayer';
|
||||
|
||||
public positionChangeCount: number = 0;
|
||||
|
||||
onPositionChanged(oldPos: any, newPos: any) {
|
||||
this.positionChangeCount++;
|
||||
console.log(`Position changed from ${JSON.stringify(oldPos)} to ${JSON.stringify(newPos)}`);
|
||||
}
|
||||
}
|
||||
|
||||
describe('SyncVar端到端测试', () => {
|
||||
let gameObject1: TestGameObject;
|
||||
let gameObject2: TestGameObject;
|
||||
let identity1: NetworkIdentity;
|
||||
let identity2: NetworkIdentity;
|
||||
let syncVarManager: SyncVarManager;
|
||||
let syncScheduler: SyncVarSyncScheduler;
|
||||
let optimizer: SyncVarOptimizer;
|
||||
|
||||
// 消息交换模拟
|
||||
let messageExchange: Map<string, SyncVarUpdateMessage[]> = new Map();
|
||||
|
||||
beforeEach(async () => {
|
||||
// 重置环境
|
||||
const env = NetworkEnvironment['Instance'];
|
||||
env['_state'] = NetworkEnvironmentState.None;
|
||||
env['_serverStartTime'] = 0;
|
||||
env['_clientConnectTime'] = 0;
|
||||
NetworkEnvironment.SetServerMode();
|
||||
|
||||
// 清理组件
|
||||
syncVarManager = SyncVarManager.Instance;
|
||||
syncVarManager['_componentChanges'].clear();
|
||||
syncVarManager['_lastSyncTimes'].clear();
|
||||
|
||||
syncScheduler = SyncVarSyncScheduler.Instance;
|
||||
optimizer = new SyncVarOptimizer();
|
||||
messageExchange.clear();
|
||||
|
||||
// 创建测试对象
|
||||
gameObject1 = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
||||
gameObject2 = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
||||
|
||||
// 创建网络身份
|
||||
identity1 = new NetworkIdentity('player1', true);
|
||||
identity2 = new NetworkIdentity('player2', false);
|
||||
|
||||
// 初始化SyncVar系统
|
||||
syncVarManager.initializeComponent(gameObject1);
|
||||
syncVarManager.initializeComponent(gameObject2);
|
||||
|
||||
// 模拟消息发送回调
|
||||
syncScheduler.setMessageSendCallback(async (message: SyncVarUpdateMessage) => {
|
||||
// 将消息添加到交换队列
|
||||
const messages = messageExchange.get(message.networkId) || [];
|
||||
messages.push(message);
|
||||
messageExchange.set(message.networkId, messages);
|
||||
|
||||
console.log(`[E2E] 模拟发送消息: ${message.networkId} -> ${message.fieldUpdates.length} 字段更新`);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// 清理
|
||||
identity1.cleanup();
|
||||
identity2.cleanup();
|
||||
NetworkIdentityRegistry.Instance.clear();
|
||||
syncScheduler.stop();
|
||||
optimizer.cleanup();
|
||||
// NetworkManager.Stop();
|
||||
});
|
||||
|
||||
test('基本SyncVar同步流程', async () => {
|
||||
// 修改gameObject1的属性
|
||||
gameObject1.health = 80;
|
||||
gameObject1.playerName = 'Hero';
|
||||
gameObject1.position = { x: 10, y: 20 };
|
||||
|
||||
// 检查是否有待同步的变化
|
||||
const changes = syncVarManager.getPendingChanges(gameObject1);
|
||||
expect(changes.length).toBe(3);
|
||||
|
||||
// 创建同步消息
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
||||
gameObject1,
|
||||
identity1.networkId,
|
||||
'server',
|
||||
1
|
||||
);
|
||||
|
||||
expect(message).not.toBeNull();
|
||||
expect(message!.fieldUpdates.length).toBe(3);
|
||||
expect(message!.networkId).toBe('player1');
|
||||
|
||||
// 验证字段更新内容
|
||||
const healthUpdate = message!.fieldUpdates.find(u => u.propertyKey === 'health');
|
||||
expect(healthUpdate).toBeDefined();
|
||||
expect(healthUpdate!.newValue).toBe(80);
|
||||
expect(healthUpdate!.oldValue).toBe(100);
|
||||
});
|
||||
|
||||
test('消息序列化和反序列化', async () => {
|
||||
// 修改属性
|
||||
gameObject1.health = 75;
|
||||
gameObject1.position = { x: 5, y: 15 };
|
||||
|
||||
// 创建消息
|
||||
const originalMessage = syncVarManager.createSyncVarUpdateMessage(
|
||||
gameObject1,
|
||||
identity1.networkId
|
||||
);
|
||||
|
||||
expect(originalMessage).not.toBeNull();
|
||||
|
||||
// 序列化
|
||||
const serialized = originalMessage!.serialize();
|
||||
expect(serialized.length).toBeGreaterThan(0);
|
||||
|
||||
// 反序列化
|
||||
const deserializedMessage = new SyncVarUpdateMessage();
|
||||
deserializedMessage.deserialize(serialized);
|
||||
|
||||
// 验证反序列化结果
|
||||
expect(deserializedMessage.networkId).toBe(originalMessage!.networkId);
|
||||
expect(deserializedMessage.componentType).toBe(originalMessage!.componentType);
|
||||
expect(deserializedMessage.fieldUpdates.length).toBe(originalMessage!.fieldUpdates.length);
|
||||
|
||||
// 验证字段内容
|
||||
for (let i = 0; i < originalMessage!.fieldUpdates.length; i++) {
|
||||
const original = originalMessage!.fieldUpdates[i];
|
||||
const deserialized = deserializedMessage.fieldUpdates[i];
|
||||
|
||||
expect(deserialized.fieldNumber).toBe(original.fieldNumber);
|
||||
expect(deserialized.propertyKey).toBe(original.propertyKey);
|
||||
expect(deserialized.newValue).toEqual(original.newValue);
|
||||
}
|
||||
});
|
||||
|
||||
test('SyncVar消息应用', async () => {
|
||||
// 在gameObject1上创建变化
|
||||
gameObject1.health = 60;
|
||||
gameObject1.playerName = 'Warrior';
|
||||
|
||||
// 创建消息
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
||||
gameObject1,
|
||||
identity1.networkId
|
||||
);
|
||||
|
||||
expect(message).not.toBeNull();
|
||||
|
||||
// 清除gameObject1的变化记录
|
||||
syncVarManager.clearChanges(gameObject1);
|
||||
|
||||
// 应用到gameObject2
|
||||
syncVarManager.applySyncVarUpdateMessage(gameObject2, message!);
|
||||
|
||||
// 验证gameObject2的状态
|
||||
expect(gameObject2.health).toBe(60);
|
||||
expect(gameObject2.playerName).toBe('Warrior');
|
||||
|
||||
// 验证Hook被触发
|
||||
expect(gameObject2.positionChangeCount).toBe(0); // position没有改变
|
||||
});
|
||||
|
||||
test('Hook回调触发', async () => {
|
||||
// 修改position触发hook
|
||||
gameObject1.position = { x: 100, y: 200 };
|
||||
|
||||
// 创建并应用消息
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
||||
gameObject1,
|
||||
identity1.networkId
|
||||
);
|
||||
|
||||
expect(message).not.toBeNull();
|
||||
|
||||
// 应用到gameObject2
|
||||
syncVarManager.applySyncVarUpdateMessage(gameObject2, message!);
|
||||
|
||||
// 验证Hook被触发
|
||||
expect(gameObject2.positionChangeCount).toBe(1);
|
||||
expect(gameObject2.position).toEqual({ x: 100, y: 200 });
|
||||
});
|
||||
|
||||
test('权威字段保护', async () => {
|
||||
// 切换到客户端环境
|
||||
const env = NetworkEnvironment['Instance'];
|
||||
env['_state'] = NetworkEnvironmentState.None;
|
||||
NetworkEnvironment.SetClientMode();
|
||||
|
||||
// 客户端尝试修改权威字段
|
||||
gameObject1.serverFlag = true; // 这应该被阻止
|
||||
|
||||
// 检查是否有待同步变化
|
||||
const changes = syncVarManager.getPendingChanges(gameObject1);
|
||||
expect(changes.length).toBe(0); // 应该没有变化被记录
|
||||
|
||||
// 尝试创建消息
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
||||
gameObject1,
|
||||
identity1.networkId
|
||||
);
|
||||
|
||||
expect(message).toBeNull(); // 应该没有消息
|
||||
});
|
||||
|
||||
test('消息优化器功能', async () => {
|
||||
// 配置优化器
|
||||
optimizer.configure({
|
||||
enableMessageMerging: true,
|
||||
mergeTimeWindow: 50,
|
||||
enableRateLimit: true,
|
||||
maxMessagesPerSecond: 10
|
||||
});
|
||||
|
||||
// 快速连续修改属性
|
||||
gameObject1.health = 90;
|
||||
gameObject1.health = 80;
|
||||
gameObject1.health = 70;
|
||||
|
||||
const messages: SyncVarUpdateMessage[] = [];
|
||||
|
||||
// 创建多个消息
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const msg = syncVarManager.createSyncVarUpdateMessage(
|
||||
gameObject1,
|
||||
identity1.networkId,
|
||||
'server',
|
||||
i + 1
|
||||
);
|
||||
if (msg) {
|
||||
messages.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
expect(messages.length).toBeGreaterThan(0);
|
||||
|
||||
// 测试优化器处理
|
||||
let optimizedCount = 0;
|
||||
|
||||
for (const message of messages) {
|
||||
optimizer.optimizeMessage(message, ['observer1'], (optimizedMessages, observers) => {
|
||||
optimizedCount++;
|
||||
expect(optimizedMessages.length).toBeGreaterThan(0);
|
||||
expect(observers.length).toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
|
||||
// 等待合并完成
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// 强制刷新优化器
|
||||
optimizer.flush(() => {
|
||||
optimizedCount++;
|
||||
});
|
||||
|
||||
expect(optimizedCount).toBeGreaterThan(0);
|
||||
|
||||
// 检查统计信息
|
||||
const stats = optimizer.getStats();
|
||||
expect(stats.messagesProcessed).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('网络对象身份管理', async () => {
|
||||
const registry = NetworkIdentityRegistry.Instance;
|
||||
|
||||
// 验证对象已注册
|
||||
const foundIdentity1 = registry.find(identity1.networkId);
|
||||
const foundIdentity2 = registry.find(identity2.networkId);
|
||||
|
||||
expect(foundIdentity1).toBeDefined();
|
||||
expect(foundIdentity2).toBeDefined();
|
||||
expect(foundIdentity1!.networkId).toBe('player1');
|
||||
expect(foundIdentity2!.networkId).toBe('player2');
|
||||
|
||||
// 测试权威对象查询
|
||||
const authorityObjects = registry.getAuthorityObjects();
|
||||
expect(authorityObjects.length).toBe(1);
|
||||
expect(authorityObjects[0].networkId).toBe('player1');
|
||||
|
||||
// 测试激活状态
|
||||
identity1.activate();
|
||||
identity2.activate();
|
||||
|
||||
const activeObjects = registry.getActiveObjects();
|
||||
expect(activeObjects.length).toBe(2);
|
||||
|
||||
// 测试统计信息
|
||||
const stats = registry.getStats();
|
||||
expect(stats.totalObjects).toBe(2);
|
||||
expect(stats.activeObjects).toBe(2);
|
||||
expect(stats.authorityObjects).toBe(1);
|
||||
});
|
||||
|
||||
test('同步调度器集成测试', async () => {
|
||||
// 配置调度器
|
||||
syncScheduler.configure({
|
||||
syncInterval: 50,
|
||||
maxBatchSize: 5,
|
||||
enablePrioritySort: true
|
||||
});
|
||||
|
||||
// 激活网络对象
|
||||
identity1.activate();
|
||||
identity2.activate();
|
||||
|
||||
// 修改多个对象的属性
|
||||
gameObject1.health = 85;
|
||||
gameObject1.playerName = 'Hero1';
|
||||
|
||||
gameObject2.health = 75;
|
||||
gameObject2.playerName = 'Hero2';
|
||||
|
||||
// 启动调度器
|
||||
syncScheduler.start();
|
||||
|
||||
// 等待调度器处理
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// 检查消息交换
|
||||
const messages1 = messageExchange.get('player1') || [];
|
||||
const messages2 = messageExchange.get('player2') || [];
|
||||
|
||||
console.log(`Player1 messages: ${messages1.length}, Player2 messages: ${messages2.length}`);
|
||||
|
||||
// 停止调度器
|
||||
syncScheduler.stop();
|
||||
|
||||
// 检查统计信息
|
||||
const schedulerStats = syncScheduler.getStats();
|
||||
expect(schedulerStats.totalSyncCycles).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('完整的客户端-服务端模拟', async () => {
|
||||
// 服务端环境设置
|
||||
NetworkEnvironment.SetServerMode();
|
||||
const serverObject = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
||||
const serverIdentity = new NetworkIdentity('server_obj', true);
|
||||
serverIdentity.activate();
|
||||
|
||||
syncVarManager.initializeComponent(serverObject);
|
||||
|
||||
// 客户端环境设置
|
||||
const env = NetworkEnvironment['Instance'];
|
||||
env['_state'] = NetworkEnvironmentState.None;
|
||||
NetworkEnvironment.SetClientMode();
|
||||
|
||||
const clientObject = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
||||
syncVarManager.initializeComponent(clientObject);
|
||||
|
||||
// 服务端修改数据
|
||||
NetworkEnvironment.SetServerMode();
|
||||
serverObject.health = 50;
|
||||
serverObject.playerName = 'ServerPlayer';
|
||||
serverObject.position = { x: 30, y: 40 };
|
||||
|
||||
// 创建服务端消息
|
||||
const serverMessage = syncVarManager.createSyncVarUpdateMessage(
|
||||
serverObject,
|
||||
serverIdentity.networkId,
|
||||
'server'
|
||||
);
|
||||
|
||||
expect(serverMessage).not.toBeNull();
|
||||
|
||||
// 切换到客户端接收消息
|
||||
NetworkEnvironment.SetClientMode();
|
||||
syncVarManager.applySyncVarUpdateMessage(clientObject, serverMessage!);
|
||||
|
||||
// 验证客户端状态
|
||||
expect(clientObject.health).toBe(50);
|
||||
expect(clientObject.playerName).toBe('ServerPlayer');
|
||||
expect(clientObject.position).toEqual({ x: 30, y: 40 });
|
||||
expect(clientObject.positionChangeCount).toBe(1);
|
||||
|
||||
console.log('[E2E] 客户端-服务端同步测试完成');
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'reflect-metadata';
|
||||
import { SyncVar } from '../src/SyncVar';
|
||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
||||
import { NetworkComponent } from '../src/NetworkComponent';
|
||||
|
||||
// 简化的测试用网络组件
|
||||
class SimpleTestComponent extends NetworkComponent {
|
||||
@SyncVar()
|
||||
public health: number = 100;
|
||||
|
||||
@SyncVar()
|
||||
public name: string = 'TestPlayer';
|
||||
}
|
||||
|
||||
describe('SyncVar端到端简单测试', () => {
|
||||
test('基本的SyncVar代理创建', () => {
|
||||
const component = new SimpleTestComponent();
|
||||
const proxiedComponent = createSyncVarProxy(component) as SimpleTestComponent;
|
||||
|
||||
expect(proxiedComponent).toBeDefined();
|
||||
expect(proxiedComponent.health).toBe(100);
|
||||
expect(proxiedComponent.name).toBe('TestPlayer');
|
||||
|
||||
// 修改值应该能正常工作
|
||||
proxiedComponent.health = 80;
|
||||
expect(proxiedComponent.health).toBe(80);
|
||||
});
|
||||
|
||||
test('SyncVar变化记录', () => {
|
||||
const component = createSyncVarProxy(new SimpleTestComponent()) as SimpleTestComponent;
|
||||
|
||||
// 修改值
|
||||
component.health = 75;
|
||||
component.name = 'Hero';
|
||||
|
||||
// 检查是否有变化记录
|
||||
const changes = component.getSyncVarChanges();
|
||||
expect(changes.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -1,430 +0,0 @@
|
||||
import { SyncVarUpdateMessage, SyncVarFieldUpdate, MessageType } from '../src/Messaging/MessageTypes';
|
||||
import { SyncVar, getSyncVarMetadata, SyncVarManager } from '../src/SyncVar';
|
||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
||||
import { NetworkEnvironment, NetworkEnvironmentState } from '../src/Core/NetworkEnvironment';
|
||||
import { NetworkComponent } from '../src/NetworkComponent';
|
||||
|
||||
// 测试用的组件类
|
||||
class TestPlayerComponent extends NetworkComponent {
|
||||
private _hasAuthority: boolean = false;
|
||||
|
||||
public hasAuthority(): boolean {
|
||||
return this._hasAuthority;
|
||||
}
|
||||
|
||||
public setAuthority(hasAuthority: boolean): void {
|
||||
this._hasAuthority = hasAuthority;
|
||||
}
|
||||
@SyncVar()
|
||||
public health: number = 100;
|
||||
|
||||
@SyncVar({ hook: 'onNameChanged' })
|
||||
public playerName: string = 'Player';
|
||||
|
||||
@SyncVar({ authorityOnly: true })
|
||||
public isReady: boolean = false;
|
||||
|
||||
@SyncVar()
|
||||
public position = { x: 0, y: 0 };
|
||||
|
||||
// Hook回调函数
|
||||
public onNameChangedCallCount = 0;
|
||||
public lastNameChange: { oldName: string; newName: string } | null = null;
|
||||
|
||||
onNameChanged(oldName: string, newName: string) {
|
||||
this.onNameChangedCallCount++;
|
||||
this.lastNameChange = { oldName, newName };
|
||||
console.log(`Name changed: ${oldName} -> ${newName}`);
|
||||
}
|
||||
}
|
||||
|
||||
describe('SyncVar消息系统测试', () => {
|
||||
let syncVarManager: SyncVarManager;
|
||||
|
||||
beforeEach(() => {
|
||||
// 重置网络环境 - 先清除所有状态再设置服务端
|
||||
const env = NetworkEnvironment['Instance'];
|
||||
env['_state'] = NetworkEnvironmentState.None;
|
||||
env['_serverStartTime'] = 0;
|
||||
env['_clientConnectTime'] = 0;
|
||||
NetworkEnvironment.SetServerMode();
|
||||
|
||||
// 获取SyncVar管理器实例
|
||||
syncVarManager = SyncVarManager.Instance;
|
||||
|
||||
// 清理管理器状态
|
||||
syncVarManager['_componentChanges'].clear();
|
||||
syncVarManager['_lastSyncTimes'].clear();
|
||||
});
|
||||
|
||||
describe('SyncVarUpdateMessage基础功能', () => {
|
||||
test('应该能正确创建SyncVarUpdateMessage', () => {
|
||||
const fieldUpdates: SyncVarFieldUpdate[] = [
|
||||
{
|
||||
fieldNumber: 1,
|
||||
propertyKey: 'health',
|
||||
newValue: 80,
|
||||
oldValue: 100,
|
||||
timestamp: Date.now(),
|
||||
authorityOnly: false
|
||||
}
|
||||
];
|
||||
|
||||
const message = new SyncVarUpdateMessage(
|
||||
'player_001',
|
||||
'TestPlayerComponent',
|
||||
fieldUpdates,
|
||||
false,
|
||||
'server_001',
|
||||
123
|
||||
);
|
||||
|
||||
expect(message.messageType).toBe(MessageType.SYNC_VAR_UPDATE);
|
||||
expect(message.networkId).toBe('player_001');
|
||||
expect(message.componentType).toBe('TestPlayerComponent');
|
||||
expect(message.fieldUpdates.length).toBe(1);
|
||||
expect(message.isFullSync).toBe(false);
|
||||
expect(message.senderId).toBe('server_001');
|
||||
expect(message.syncSequence).toBe(123);
|
||||
});
|
||||
|
||||
test('应该能添加和移除字段更新', () => {
|
||||
const message = new SyncVarUpdateMessage();
|
||||
|
||||
expect(message.hasUpdates()).toBe(false);
|
||||
expect(message.getUpdateCount()).toBe(0);
|
||||
|
||||
const fieldUpdate: SyncVarFieldUpdate = {
|
||||
fieldNumber: 1,
|
||||
propertyKey: 'health',
|
||||
newValue: 80,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
message.addFieldUpdate(fieldUpdate);
|
||||
expect(message.hasUpdates()).toBe(true);
|
||||
expect(message.getUpdateCount()).toBe(1);
|
||||
|
||||
const retrieved = message.getFieldUpdate(1);
|
||||
expect(retrieved).toBeDefined();
|
||||
expect(retrieved?.propertyKey).toBe('health');
|
||||
|
||||
const removed = message.removeFieldUpdate(1);
|
||||
expect(removed).toBe(true);
|
||||
expect(message.hasUpdates()).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能序列化和反序列化消息', () => {
|
||||
const fieldUpdates: SyncVarFieldUpdate[] = [
|
||||
{
|
||||
fieldNumber: 1,
|
||||
propertyKey: 'health',
|
||||
newValue: 75,
|
||||
oldValue: 100,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
{
|
||||
fieldNumber: 2,
|
||||
propertyKey: 'playerName',
|
||||
newValue: 'Hero',
|
||||
oldValue: 'Player',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
];
|
||||
|
||||
const originalMessage = new SyncVarUpdateMessage(
|
||||
'player_001',
|
||||
'TestPlayerComponent',
|
||||
fieldUpdates,
|
||||
true,
|
||||
'server_001',
|
||||
456
|
||||
);
|
||||
|
||||
// 序列化
|
||||
const serializedData = originalMessage.serialize();
|
||||
expect(serializedData.length).toBeGreaterThan(0);
|
||||
|
||||
// 反序列化
|
||||
const deserializedMessage = new SyncVarUpdateMessage();
|
||||
deserializedMessage.deserialize(serializedData);
|
||||
|
||||
// 验证反序列化结果
|
||||
expect(deserializedMessage.networkId).toBe(originalMessage.networkId);
|
||||
expect(deserializedMessage.componentType).toBe(originalMessage.componentType);
|
||||
expect(deserializedMessage.fieldUpdates.length).toBe(originalMessage.fieldUpdates.length);
|
||||
expect(deserializedMessage.isFullSync).toBe(originalMessage.isFullSync);
|
||||
expect(deserializedMessage.senderId).toBe(originalMessage.senderId);
|
||||
expect(deserializedMessage.syncSequence).toBe(originalMessage.syncSequence);
|
||||
});
|
||||
|
||||
test('应该能获取消息统计信息', () => {
|
||||
const message = new SyncVarUpdateMessage();
|
||||
|
||||
// 空消息统计
|
||||
let stats = message.getStats();
|
||||
expect(stats.updateCount).toBe(0);
|
||||
expect(stats.hasAuthorityOnlyFields).toBe(false);
|
||||
expect(stats.oldestUpdateTime).toBe(0);
|
||||
expect(stats.newestUpdateTime).toBe(0);
|
||||
|
||||
// 添加字段更新
|
||||
const now = Date.now();
|
||||
message.addFieldUpdate({
|
||||
fieldNumber: 1,
|
||||
propertyKey: 'health',
|
||||
newValue: 80,
|
||||
timestamp: now - 1000
|
||||
});
|
||||
|
||||
message.addFieldUpdate({
|
||||
fieldNumber: 2,
|
||||
propertyKey: 'isReady',
|
||||
newValue: true,
|
||||
timestamp: now,
|
||||
authorityOnly: true
|
||||
});
|
||||
|
||||
stats = message.getStats();
|
||||
expect(stats.updateCount).toBe(2);
|
||||
expect(stats.hasAuthorityOnlyFields).toBe(true);
|
||||
expect(stats.oldestUpdateTime).toBe(now - 1000);
|
||||
expect(stats.newestUpdateTime).toBe(now);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SyncVarManager消息集成', () => {
|
||||
test('应该能从组件变化创建SyncVarUpdateMessage', () => {
|
||||
const component = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(component);
|
||||
|
||||
// 初始化组件
|
||||
syncVarManager.initializeComponent(proxy);
|
||||
|
||||
// 修改字段
|
||||
proxy.health = 75;
|
||||
proxy.playerName = 'Hero';
|
||||
|
||||
// 创建消息
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
||||
proxy,
|
||||
'player_001',
|
||||
'server_001',
|
||||
100
|
||||
);
|
||||
|
||||
expect(message).not.toBeNull();
|
||||
expect(message!.networkId).toBe('player_001');
|
||||
expect(message!.componentType).toBe('TestPlayerComponent');
|
||||
expect(message!.senderId).toBe('server_001');
|
||||
expect(message!.syncSequence).toBe(100);
|
||||
expect(message!.fieldUpdates.length).toBe(2);
|
||||
|
||||
// 验证字段更新内容
|
||||
const healthUpdate = message!.fieldUpdates.find(u => u.propertyKey === 'health');
|
||||
expect(healthUpdate).toBeDefined();
|
||||
expect(healthUpdate!.newValue).toBe(75);
|
||||
expect(healthUpdate!.oldValue).toBe(100);
|
||||
|
||||
const nameUpdate = message!.fieldUpdates.find(u => u.propertyKey === 'playerName');
|
||||
expect(nameUpdate).toBeDefined();
|
||||
expect(nameUpdate!.newValue).toBe('Hero');
|
||||
expect(nameUpdate!.oldValue).toBe('Player');
|
||||
});
|
||||
|
||||
test('没有变化时应该返回null', () => {
|
||||
const component = new TestPlayerComponent();
|
||||
const proxy = createSyncVarProxy(component);
|
||||
|
||||
syncVarManager.initializeComponent(proxy);
|
||||
|
||||
// 没有修改任何字段
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(proxy);
|
||||
|
||||
expect(message).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能应用SyncVarUpdateMessage到组件', () => {
|
||||
const sourceComponent = new TestPlayerComponent();
|
||||
const sourceProxy = createSyncVarProxy(sourceComponent);
|
||||
|
||||
const targetComponent = new TestPlayerComponent();
|
||||
const targetProxy = createSyncVarProxy(targetComponent);
|
||||
|
||||
// 初始化组件
|
||||
syncVarManager.initializeComponent(sourceProxy);
|
||||
syncVarManager.initializeComponent(targetProxy);
|
||||
|
||||
// 修改源组件
|
||||
sourceProxy.health = 60;
|
||||
sourceProxy.playerName = 'Warrior';
|
||||
|
||||
// 创建消息
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
||||
sourceProxy,
|
||||
'player_001'
|
||||
);
|
||||
|
||||
expect(message).not.toBeNull();
|
||||
|
||||
// 应用到目标组件
|
||||
syncVarManager.applySyncVarUpdateMessage(targetProxy, message!);
|
||||
|
||||
// 验证目标组件状态
|
||||
expect(targetProxy.health).toBe(60);
|
||||
expect(targetProxy.playerName).toBe('Warrior');
|
||||
|
||||
// 验证hook被触发
|
||||
expect(targetProxy.onNameChangedCallCount).toBe(1);
|
||||
expect(targetProxy.lastNameChange).toEqual({
|
||||
oldName: 'Player',
|
||||
newName: 'Warrior'
|
||||
});
|
||||
});
|
||||
|
||||
test('应该能批量创建多个组件的消息', () => {
|
||||
const component1 = createSyncVarProxy(new TestPlayerComponent());
|
||||
const component2 = createSyncVarProxy(new TestPlayerComponent());
|
||||
|
||||
syncVarManager.initializeComponent(component1);
|
||||
syncVarManager.initializeComponent(component2);
|
||||
|
||||
// 修改组件
|
||||
component1.health = 80;
|
||||
component2.playerName = 'Hero2';
|
||||
|
||||
// 批量创建消息
|
||||
const messages = syncVarManager.createBatchSyncVarUpdateMessages(
|
||||
[component1, component2],
|
||||
['player_001', 'player_002'],
|
||||
'server_001',
|
||||
200
|
||||
);
|
||||
|
||||
expect(messages.length).toBe(2);
|
||||
|
||||
expect(messages[0].networkId).toBe('player_001');
|
||||
expect(messages[0].syncSequence).toBe(200);
|
||||
|
||||
expect(messages[1].networkId).toBe('player_002');
|
||||
expect(messages[1].syncSequence).toBe(201);
|
||||
});
|
||||
|
||||
test('应该能过滤有变化的组件', () => {
|
||||
const component1 = createSyncVarProxy(new TestPlayerComponent());
|
||||
const component2 = createSyncVarProxy(new TestPlayerComponent());
|
||||
const component3 = createSyncVarProxy(new TestPlayerComponent());
|
||||
|
||||
syncVarManager.initializeComponent(component1);
|
||||
syncVarManager.initializeComponent(component2);
|
||||
syncVarManager.initializeComponent(component3);
|
||||
|
||||
// 只修改component1和component3
|
||||
component1.health = 80;
|
||||
component3.playerName = 'Hero3';
|
||||
// component2没有修改
|
||||
|
||||
const componentsWithChanges = syncVarManager.filterComponentsWithChanges([
|
||||
component1, component2, component3
|
||||
]);
|
||||
|
||||
expect(componentsWithChanges.length).toBe(2);
|
||||
expect(componentsWithChanges).toContain(component1);
|
||||
expect(componentsWithChanges).toContain(component3);
|
||||
expect(componentsWithChanges).not.toContain(component2);
|
||||
});
|
||||
|
||||
test('应该能获取组件变化统计', () => {
|
||||
const component = createSyncVarProxy(new TestPlayerComponent());
|
||||
syncVarManager.initializeComponent(component);
|
||||
|
||||
// 修改多个字段
|
||||
component.health = 80;
|
||||
component.health = 70; // 再次修改同一字段
|
||||
component.playerName = 'Hero';
|
||||
|
||||
const stats = syncVarManager.getComponentChangeStats(component);
|
||||
|
||||
expect(stats.totalChanges).toBe(3);
|
||||
expect(stats.pendingChanges).toBe(3);
|
||||
expect(stats.lastChangeTime).toBeGreaterThan(0);
|
||||
expect(stats.fieldChangeCounts.get('health')).toBe(2);
|
||||
expect(stats.fieldChangeCounts.get('playerName')).toBe(1);
|
||||
expect(stats.hasAuthorityOnlyChanges).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('权限和环境检查', () => {
|
||||
test('权威字段应该被正确处理', () => {
|
||||
// 重置环境为纯客户端模式
|
||||
const env = NetworkEnvironment['Instance'];
|
||||
env['_state'] = NetworkEnvironmentState.None;
|
||||
env['_serverStartTime'] = 0;
|
||||
env['_clientConnectTime'] = 0;
|
||||
NetworkEnvironment.SetClientMode(); // 切换到纯客户端模式
|
||||
|
||||
const component = createSyncVarProxy(new TestPlayerComponent());
|
||||
// 明确设置没有权限
|
||||
component.setAuthority(false);
|
||||
|
||||
syncVarManager.initializeComponent(component);
|
||||
|
||||
console.log('当前环境:', NetworkEnvironment.isServer ? 'server' : 'client');
|
||||
console.log('isServer:', NetworkEnvironment.isServer);
|
||||
console.log('isClient:', NetworkEnvironment.isClient);
|
||||
console.log('组件权限:', component.hasAuthority());
|
||||
|
||||
// 修改权威字段(客户端没有权限)
|
||||
component.isReady = true;
|
||||
|
||||
// 检查待同步变化
|
||||
const pendingChanges = syncVarManager.getPendingChanges(component);
|
||||
console.log('待同步变化:', pendingChanges);
|
||||
|
||||
const message = syncVarManager.createSyncVarUpdateMessage(component);
|
||||
console.log('创建的消息:', message);
|
||||
|
||||
// 在客户端模式下,权威字段不应该被同步
|
||||
expect(message).toBeNull();
|
||||
});
|
||||
|
||||
test('客户端应该能接受来自服务端的权威字段更新', () => {
|
||||
NetworkEnvironment.SetClientMode(); // 客户端模式
|
||||
|
||||
const component = createSyncVarProxy(new TestPlayerComponent());
|
||||
syncVarManager.initializeComponent(component);
|
||||
|
||||
const fieldUpdates: SyncVarFieldUpdate[] = [
|
||||
{
|
||||
fieldNumber: 3, // isReady字段
|
||||
propertyKey: 'isReady',
|
||||
newValue: true,
|
||||
oldValue: false,
|
||||
timestamp: Date.now(),
|
||||
authorityOnly: true
|
||||
}
|
||||
];
|
||||
|
||||
const message = new SyncVarUpdateMessage(
|
||||
'player_001',
|
||||
'TestPlayerComponent',
|
||||
fieldUpdates
|
||||
);
|
||||
|
||||
// 记录初始值
|
||||
const initialValue = component.isReady;
|
||||
expect(initialValue).toBe(false);
|
||||
|
||||
// 应用消息(客户端应该接受来自服务端的权威字段更新)
|
||||
syncVarManager.applySyncVarUpdateMessage(component, message);
|
||||
|
||||
// 值应该改变
|
||||
expect(component.isReady).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 清理
|
||||
NetworkEnvironment.SetServerMode(); // 重置为服务器模式
|
||||
});
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* SyncVar简单测试
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
|
||||
describe('SyncVar简单测试', () => {
|
||||
test('基础功能测试', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
145
packages/network/tests/TsrpcTransport.test.ts
Normal file
145
packages/network/tests/TsrpcTransport.test.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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,72 +1,28 @@
|
||||
/**
|
||||
* Jest 测试全局设置文件
|
||||
*
|
||||
* 此文件在每个测试文件执行前运行,用于设置全局测试环境
|
||||
* Jest测试设置文件
|
||||
*/
|
||||
|
||||
// 设置测试超时时间(毫秒)
|
||||
import 'reflect-metadata';
|
||||
|
||||
// 全局Jest配置
|
||||
expect.extend({});
|
||||
|
||||
// 设置测试环境变量
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
// 全局错误处理
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('未处理的Promise拒绝:', reason);
|
||||
});
|
||||
|
||||
// 设置全局测试超时
|
||||
jest.setTimeout(10000);
|
||||
|
||||
// 模拟控制台方法以减少测试输出噪音
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
// 在测试环境中可以选择性地静默某些日志
|
||||
beforeAll(() => {
|
||||
// 可以在这里设置全局的模拟或配置
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// 清理全局资源
|
||||
});
|
||||
|
||||
// 每个测试前的清理
|
||||
beforeEach(() => {
|
||||
// 清理定时器
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
// 清理函数
|
||||
afterEach(() => {
|
||||
// 恢复所有模拟
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
// 导出测试工具函数
|
||||
export const TestUtils = {
|
||||
/**
|
||||
* 创建测试用的延迟
|
||||
* @param ms 延迟毫秒数
|
||||
*/
|
||||
delay: (ms: number): Promise<void> => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
},
|
||||
|
||||
/**
|
||||
* 等待条件满足
|
||||
* @param condition 条件函数
|
||||
* @param timeout 超时时间(毫秒)
|
||||
* @param interval 检查间隔(毫秒)
|
||||
*/
|
||||
waitFor: async (
|
||||
condition: () => boolean,
|
||||
timeout: number = 5000,
|
||||
interval: number = 10
|
||||
): Promise<void> => {
|
||||
const start = Date.now();
|
||||
while (!condition() && Date.now() - start < timeout) {
|
||||
await TestUtils.delay(interval);
|
||||
}
|
||||
if (!condition()) {
|
||||
throw new Error(`等待条件超时 (${timeout}ms)`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 模拟时间前进
|
||||
* @param ms 前进的毫秒数
|
||||
*/
|
||||
advanceTime: (ms: number): void => {
|
||||
jest.advanceTimersByTime(ms);
|
||||
}
|
||||
};
|
||||
// 清理所有定时器
|
||||
jest.clearAllTimers();
|
||||
|
||||
// 清理所有模拟
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
Reference in New Issue
Block a user