新增syncvar高级特性,使用protobuf定义
This commit is contained in:
280
packages/network/tests/SyncVar.test.ts
Normal file
280
packages/network/tests/SyncVar.test.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import { SyncVar, getSyncVarMetadata, SyncVarManager } from '../src/SyncVar';
|
||||
import { createNetworkComponent } from '../src/SyncVar/SyncVarFactory';
|
||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
||||
import { NetworkComponent } from '../src/NetworkComponent';
|
||||
|
||||
// 模拟NetworkComponent基类
|
||||
class MockNetworkComponent {
|
||||
private _dirtyFields: Set<number> = new Set();
|
||||
private _fieldTimestamps: Map<number, number> = new Map();
|
||||
|
||||
constructor() {}
|
||||
|
||||
public isClient(): boolean { return true; }
|
||||
public isServer(): boolean { return false; }
|
||||
public getRole(): string { return 'client'; }
|
||||
|
||||
public getSyncVarChanges(): any[] {
|
||||
const syncVarManager = SyncVarManager.Instance;
|
||||
return syncVarManager.getPendingChanges(this);
|
||||
}
|
||||
|
||||
public createSyncVarData(): any {
|
||||
const syncVarManager = SyncVarManager.Instance;
|
||||
return syncVarManager.createSyncData(this);
|
||||
}
|
||||
|
||||
public applySyncVarData(syncData: any): void {
|
||||
const syncVarManager = SyncVarManager.Instance;
|
||||
syncVarManager.applySyncData(this, syncData);
|
||||
}
|
||||
|
||||
public hasSyncVars(): boolean {
|
||||
const metadata = getSyncVarMetadata(this.constructor);
|
||||
return metadata.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用的组件类
|
||||
class TestPlayerComponent extends MockNetworkComponent {
|
||||
@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 MockNetworkComponent {
|
||||
public normalField: number = 42;
|
||||
}
|
||||
|
||||
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 MockNetworkComponent {
|
||||
@SyncVar()
|
||||
public syncField: number = 1;
|
||||
|
||||
public normalField: number = 2;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user