@esengine/network 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NetworkPlugin.d.ts +150 -8
- package/dist/NetworkPlugin.d.ts.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2055 -508
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +125 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/sync/ComponentSync.d.ts +173 -0
- package/dist/sync/ComponentSync.d.ts.map +1 -0
- package/dist/sync/StateDelta.d.ts +177 -0
- package/dist/sync/StateDelta.d.ts.map +1 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/systems/NetworkAOISystem.d.ts +172 -0
- package/dist/systems/NetworkAOISystem.d.ts.map +1 -0
- package/dist/systems/NetworkInputSystem.d.ts +69 -2
- package/dist/systems/NetworkInputSystem.d.ts.map +1 -1
- package/dist/systems/NetworkPredictionSystem.d.ts +113 -0
- package/dist/systems/NetworkPredictionSystem.d.ts.map +1 -0
- package/dist/systems/NetworkSyncSystem.d.ts +88 -9
- package/dist/systems/NetworkSyncSystem.d.ts.map +1 -1
- package/dist/tokens.d.ts +12 -0
- package/dist/tokens.d.ts.map +1 -1
- package/package.json +12 -3
package/dist/index.js
CHANGED
|
@@ -19,7 +19,12 @@ var gameProtocol = rpc.define({
|
|
|
19
19
|
* @zh 离开房间
|
|
20
20
|
* @en Leave room
|
|
21
21
|
*/
|
|
22
|
-
leave: rpc.api()
|
|
22
|
+
leave: rpc.api(),
|
|
23
|
+
/**
|
|
24
|
+
* @zh 重连
|
|
25
|
+
* @en Reconnect
|
|
26
|
+
*/
|
|
27
|
+
reconnect: rpc.api()
|
|
23
28
|
},
|
|
24
29
|
msg: {
|
|
25
30
|
/**
|
|
@@ -41,7 +46,12 @@ var gameProtocol = rpc.define({
|
|
|
41
46
|
* @zh 实体销毁
|
|
42
47
|
* @en Entity despawn
|
|
43
48
|
*/
|
|
44
|
-
despawn: rpc.msg()
|
|
49
|
+
despawn: rpc.msg(),
|
|
50
|
+
/**
|
|
51
|
+
* @zh 完整状态快照
|
|
52
|
+
* @en Full state snapshot
|
|
53
|
+
*/
|
|
54
|
+
fullState: rpc.msg()
|
|
45
55
|
}
|
|
46
56
|
});
|
|
47
57
|
|
|
@@ -51,6 +61,8 @@ var NetworkServiceToken = createServiceToken("networkService");
|
|
|
51
61
|
var NetworkSyncSystemToken = createServiceToken("networkSyncSystem");
|
|
52
62
|
var NetworkSpawnSystemToken = createServiceToken("networkSpawnSystem");
|
|
53
63
|
var NetworkInputSystemToken = createServiceToken("networkInputSystem");
|
|
64
|
+
var NetworkPredictionSystemToken = createServiceToken("networkPredictionSystem");
|
|
65
|
+
var NetworkAOISystemToken = createServiceToken("networkAOISystem");
|
|
54
66
|
|
|
55
67
|
// src/NetworkPlugin.ts
|
|
56
68
|
import { Core } from "@esengine/ecs-framework";
|
|
@@ -218,7 +230,7 @@ function createNetworkService(protocol) {
|
|
|
218
230
|
__name(createNetworkService, "createNetworkService");
|
|
219
231
|
|
|
220
232
|
// src/systems/NetworkSyncSystem.ts
|
|
221
|
-
import { EntitySystem, Matcher, Time } from "@esengine/ecs-framework";
|
|
233
|
+
import { EntitySystem as EntitySystem2, Matcher as Matcher2, Time } from "@esengine/ecs-framework";
|
|
222
234
|
|
|
223
235
|
// src/components/NetworkIdentity.ts
|
|
224
236
|
import { Component, ECSComponent, Serialize, Serializable, Property } from "@esengine/ecs-framework";
|
|
@@ -454,222 +466,1877 @@ NetworkTransform = _ts_decorate2([
|
|
|
454
466
|
})
|
|
455
467
|
], NetworkTransform);
|
|
456
468
|
|
|
457
|
-
// src/
|
|
458
|
-
var
|
|
459
|
-
constructor() {
|
|
460
|
-
|
|
461
|
-
__publicField(this, "
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
* @en Handle sync message
|
|
466
|
-
*/
|
|
467
|
-
handleSync(msg) {
|
|
468
|
-
for (const state of msg.entities) {
|
|
469
|
-
const entityId = this._netIdToEntity.get(state.netId);
|
|
470
|
-
if (entityId === void 0) continue;
|
|
471
|
-
const entity = this.scene?.findEntityById(entityId);
|
|
472
|
-
if (!entity) continue;
|
|
473
|
-
const transform = entity.getComponent(NetworkTransform);
|
|
474
|
-
if (transform && state.pos) {
|
|
475
|
-
transform.setTarget(state.pos.x, state.pos.y, state.rot ?? 0);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
process(entities) {
|
|
480
|
-
const deltaTime = Time.deltaTime;
|
|
481
|
-
for (const entity of entities) {
|
|
482
|
-
const transform = this.requireComponent(entity, NetworkTransform);
|
|
483
|
-
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
484
|
-
if (!identity.bHasAuthority && transform.bInterpolate) {
|
|
485
|
-
this._interpolate(transform, deltaTime);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* @zh 注册网络实体
|
|
491
|
-
* @en Register network entity
|
|
492
|
-
*/
|
|
493
|
-
registerEntity(netId, entityId) {
|
|
494
|
-
this._netIdToEntity.set(netId, entityId);
|
|
469
|
+
// src/sync/SnapshotBuffer.ts
|
|
470
|
+
var _SnapshotBuffer = class _SnapshotBuffer {
|
|
471
|
+
constructor(config) {
|
|
472
|
+
__publicField(this, "_buffer", []);
|
|
473
|
+
__publicField(this, "_maxSize");
|
|
474
|
+
__publicField(this, "_interpolationDelay");
|
|
475
|
+
this._maxSize = config.maxSize;
|
|
476
|
+
this._interpolationDelay = config.interpolationDelay;
|
|
495
477
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* @en Unregister network entity
|
|
499
|
-
*/
|
|
500
|
-
unregisterEntity(netId) {
|
|
501
|
-
this._netIdToEntity.delete(netId);
|
|
478
|
+
get size() {
|
|
479
|
+
return this._buffer.length;
|
|
502
480
|
}
|
|
503
481
|
/**
|
|
504
|
-
* @zh
|
|
505
|
-
* @en Get
|
|
482
|
+
* @zh 获取插值延迟
|
|
483
|
+
* @en Get interpolation delay
|
|
506
484
|
*/
|
|
507
|
-
|
|
508
|
-
return this.
|
|
509
|
-
}
|
|
510
|
-
_interpolate(transform, deltaTime) {
|
|
511
|
-
const t = Math.min(1, transform.lerpSpeed * deltaTime);
|
|
512
|
-
transform.currentX += (transform.targetX - transform.currentX) * t;
|
|
513
|
-
transform.currentY += (transform.targetY - transform.currentY) * t;
|
|
514
|
-
let angleDiff = transform.targetRotation - transform.currentRotation;
|
|
515
|
-
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
|
|
516
|
-
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
|
|
517
|
-
transform.currentRotation += angleDiff * t;
|
|
518
|
-
}
|
|
519
|
-
onDestroy() {
|
|
520
|
-
this._netIdToEntity.clear();
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
__name(_NetworkSyncSystem, "NetworkSyncSystem");
|
|
524
|
-
var NetworkSyncSystem = _NetworkSyncSystem;
|
|
525
|
-
|
|
526
|
-
// src/systems/NetworkSpawnSystem.ts
|
|
527
|
-
import { EntitySystem as EntitySystem2, Matcher as Matcher2 } from "@esengine/ecs-framework";
|
|
528
|
-
var _NetworkSpawnSystem = class _NetworkSpawnSystem extends EntitySystem2 {
|
|
529
|
-
constructor(syncSystem) {
|
|
530
|
-
super(Matcher2.nothing());
|
|
531
|
-
__publicField(this, "_syncSystem");
|
|
532
|
-
__publicField(this, "_prefabFactories", /* @__PURE__ */ new Map());
|
|
533
|
-
__publicField(this, "_localPlayerId", 0);
|
|
534
|
-
this._syncSystem = syncSystem;
|
|
485
|
+
get interpolationDelay() {
|
|
486
|
+
return this._interpolationDelay;
|
|
535
487
|
}
|
|
536
488
|
/**
|
|
537
|
-
* @zh
|
|
538
|
-
* @en
|
|
489
|
+
* @zh 添加快照
|
|
490
|
+
* @en Add snapshot
|
|
539
491
|
*/
|
|
540
|
-
|
|
541
|
-
|
|
492
|
+
push(snapshot) {
|
|
493
|
+
let insertIndex = this._buffer.length;
|
|
494
|
+
for (let i = this._buffer.length - 1; i >= 0; i--) {
|
|
495
|
+
if (this._buffer[i].timestamp <= snapshot.timestamp) {
|
|
496
|
+
insertIndex = i + 1;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (i === 0) {
|
|
500
|
+
insertIndex = 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
this._buffer.splice(insertIndex, 0, snapshot);
|
|
504
|
+
while (this._buffer.length > this._maxSize) {
|
|
505
|
+
this._buffer.shift();
|
|
506
|
+
}
|
|
542
507
|
}
|
|
543
508
|
/**
|
|
544
|
-
* @zh
|
|
545
|
-
* @en
|
|
509
|
+
* @zh 获取用于插值的两个快照
|
|
510
|
+
* @en Get two snapshots for interpolation
|
|
546
511
|
*/
|
|
547
|
-
|
|
548
|
-
if (
|
|
549
|
-
const factory = this._prefabFactories.get(msg.prefab);
|
|
550
|
-
if (!factory) {
|
|
551
|
-
this.logger.warn(`Unknown prefab: ${msg.prefab}`);
|
|
512
|
+
getInterpolationSnapshots(renderTime) {
|
|
513
|
+
if (this._buffer.length < 2) {
|
|
552
514
|
return null;
|
|
553
515
|
}
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
516
|
+
const targetTime = renderTime - this._interpolationDelay;
|
|
517
|
+
for (let i = 0; i < this._buffer.length - 1; i++) {
|
|
518
|
+
const prev = this._buffer[i];
|
|
519
|
+
const next = this._buffer[i + 1];
|
|
520
|
+
if (prev.timestamp <= targetTime && next.timestamp >= targetTime) {
|
|
521
|
+
const duration = next.timestamp - prev.timestamp;
|
|
522
|
+
const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 0;
|
|
523
|
+
return [
|
|
524
|
+
prev,
|
|
525
|
+
next,
|
|
526
|
+
Math.max(0, Math.min(1, t))
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (targetTime > this._buffer[this._buffer.length - 1].timestamp) {
|
|
531
|
+
const prev = this._buffer[this._buffer.length - 2];
|
|
532
|
+
const next = this._buffer[this._buffer.length - 1];
|
|
533
|
+
const duration = next.timestamp - prev.timestamp;
|
|
534
|
+
const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 1;
|
|
535
|
+
return [
|
|
536
|
+
prev,
|
|
537
|
+
next,
|
|
538
|
+
Math.min(t, 2)
|
|
539
|
+
];
|
|
540
|
+
}
|
|
541
|
+
return null;
|
|
566
542
|
}
|
|
567
543
|
/**
|
|
568
|
-
* @zh
|
|
569
|
-
* @en
|
|
544
|
+
* @zh 获取最新快照
|
|
545
|
+
* @en Get latest snapshot
|
|
570
546
|
*/
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (entityId === void 0) return;
|
|
574
|
-
const entity = this.scene?.findEntityById(entityId);
|
|
575
|
-
if (entity) {
|
|
576
|
-
entity.destroy();
|
|
577
|
-
}
|
|
578
|
-
this._syncSystem.unregisterEntity(msg.netId);
|
|
547
|
+
getLatest() {
|
|
548
|
+
return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
|
|
579
549
|
}
|
|
580
550
|
/**
|
|
581
|
-
* @zh
|
|
582
|
-
* @en
|
|
551
|
+
* @zh 获取特定时间之后的所有快照
|
|
552
|
+
* @en Get all snapshots after a specific time
|
|
583
553
|
*/
|
|
584
|
-
|
|
585
|
-
this.
|
|
554
|
+
getSnapshotsAfter(timestamp) {
|
|
555
|
+
return this._buffer.filter((s) => s.timestamp > timestamp);
|
|
586
556
|
}
|
|
587
557
|
/**
|
|
588
|
-
* @zh
|
|
589
|
-
* @en
|
|
558
|
+
* @zh 清空缓冲区
|
|
559
|
+
* @en Clear buffer
|
|
590
560
|
*/
|
|
591
|
-
|
|
592
|
-
this.
|
|
593
|
-
}
|
|
594
|
-
onDestroy() {
|
|
595
|
-
this._prefabFactories.clear();
|
|
561
|
+
clear() {
|
|
562
|
+
this._buffer.length = 0;
|
|
596
563
|
}
|
|
597
564
|
};
|
|
598
|
-
__name(
|
|
599
|
-
var
|
|
565
|
+
__name(_SnapshotBuffer, "SnapshotBuffer");
|
|
566
|
+
var SnapshotBuffer = _SnapshotBuffer;
|
|
567
|
+
function createSnapshotBuffer(maxSize = 30, interpolationDelay = 100) {
|
|
568
|
+
return new SnapshotBuffer({
|
|
569
|
+
maxSize,
|
|
570
|
+
interpolationDelay
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
__name(createSnapshotBuffer, "createSnapshotBuffer");
|
|
600
574
|
|
|
601
|
-
// src/
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
575
|
+
// src/sync/IInterpolator.ts
|
|
576
|
+
function lerp(a, b, t) {
|
|
577
|
+
return a + (b - a) * t;
|
|
578
|
+
}
|
|
579
|
+
__name(lerp, "lerp");
|
|
580
|
+
function lerpAngle(a, b, t) {
|
|
581
|
+
let diff = b - a;
|
|
582
|
+
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
583
|
+
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
584
|
+
return a + diff * t;
|
|
585
|
+
}
|
|
586
|
+
__name(lerpAngle, "lerpAngle");
|
|
587
|
+
function smoothDamp(current, target, velocity, smoothTime, deltaTime, maxSpeed = Infinity) {
|
|
588
|
+
smoothTime = Math.max(1e-4, smoothTime);
|
|
589
|
+
const omega = 2 / smoothTime;
|
|
590
|
+
const x = omega * deltaTime;
|
|
591
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
592
|
+
let change = current - target;
|
|
593
|
+
const maxChange = maxSpeed * smoothTime;
|
|
594
|
+
change = Math.max(-maxChange, Math.min(maxChange, change));
|
|
595
|
+
const temp = (velocity + omega * change) * deltaTime;
|
|
596
|
+
let newVelocity = (velocity - omega * temp) * exp;
|
|
597
|
+
let newValue = target + (change + temp) * exp;
|
|
598
|
+
if (target - current > 0 === newValue > target) {
|
|
599
|
+
newValue = target;
|
|
600
|
+
newVelocity = (newValue - target) / deltaTime;
|
|
610
601
|
}
|
|
602
|
+
return [
|
|
603
|
+
newValue,
|
|
604
|
+
newVelocity
|
|
605
|
+
];
|
|
606
|
+
}
|
|
607
|
+
__name(smoothDamp, "smoothDamp");
|
|
608
|
+
|
|
609
|
+
// src/sync/TransformInterpolator.ts
|
|
610
|
+
var _TransformInterpolator = class _TransformInterpolator {
|
|
611
611
|
/**
|
|
612
|
-
* @zh
|
|
613
|
-
* @en
|
|
612
|
+
* @zh 在两个变换状态之间插值
|
|
613
|
+
* @en Interpolate between two transform states
|
|
614
614
|
*/
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this._networkService.sendInput(input);
|
|
622
|
-
}
|
|
615
|
+
interpolate(from, to, t) {
|
|
616
|
+
return {
|
|
617
|
+
x: lerp(from.x, to.x, t),
|
|
618
|
+
y: lerp(from.y, to.y, t),
|
|
619
|
+
rotation: lerpAngle(from.rotation, to.rotation, t)
|
|
620
|
+
};
|
|
623
621
|
}
|
|
624
622
|
/**
|
|
625
|
-
* @zh
|
|
626
|
-
* @en
|
|
623
|
+
* @zh 基于速度外推变换状态
|
|
624
|
+
* @en Extrapolate transform state based on velocity
|
|
625
|
+
*/
|
|
626
|
+
extrapolate(state, deltaTime) {
|
|
627
|
+
return {
|
|
628
|
+
x: state.x + state.velocityX * deltaTime,
|
|
629
|
+
y: state.y + state.velocityY * deltaTime,
|
|
630
|
+
rotation: state.rotation + state.angularVelocity * deltaTime,
|
|
631
|
+
velocityX: state.velocityX,
|
|
632
|
+
velocityY: state.velocityY,
|
|
633
|
+
angularVelocity: state.angularVelocity
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
__name(_TransformInterpolator, "TransformInterpolator");
|
|
638
|
+
var TransformInterpolator = _TransformInterpolator;
|
|
639
|
+
var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
|
|
640
|
+
/**
|
|
641
|
+
* @zh 使用赫尔米特插值
|
|
642
|
+
* @en Use Hermite interpolation
|
|
643
|
+
*/
|
|
644
|
+
interpolate(from, to, t) {
|
|
645
|
+
const t2 = t * t;
|
|
646
|
+
const t3 = t2 * t;
|
|
647
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
648
|
+
const h10 = t3 - 2 * t2 + t;
|
|
649
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
650
|
+
const h11 = t3 - t2;
|
|
651
|
+
const dt = 0.1;
|
|
652
|
+
const x = h00 * from.x + h10 * from.velocityX * dt + h01 * to.x + h11 * to.velocityX * dt;
|
|
653
|
+
const y = h00 * from.y + h10 * from.velocityY * dt + h01 * to.y + h11 * to.velocityY * dt;
|
|
654
|
+
const dh00 = 6 * t2 - 6 * t;
|
|
655
|
+
const dh10 = 3 * t2 - 4 * t + 1;
|
|
656
|
+
const dh01 = -6 * t2 + 6 * t;
|
|
657
|
+
const dh11 = 3 * t2 - 2 * t;
|
|
658
|
+
const velocityX = (dh00 * from.x + dh10 * from.velocityX * dt + dh01 * to.x + dh11 * to.velocityX * dt) / dt;
|
|
659
|
+
const velocityY = (dh00 * from.y + dh10 * from.velocityY * dt + dh01 * to.y + dh11 * to.velocityY * dt) / dt;
|
|
660
|
+
return {
|
|
661
|
+
x,
|
|
662
|
+
y,
|
|
663
|
+
rotation: lerpAngle(from.rotation, to.rotation, t),
|
|
664
|
+
velocityX,
|
|
665
|
+
velocityY,
|
|
666
|
+
angularVelocity: lerp(from.angularVelocity, to.angularVelocity, t)
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
__name(_HermiteTransformInterpolator, "HermiteTransformInterpolator");
|
|
671
|
+
var HermiteTransformInterpolator = _HermiteTransformInterpolator;
|
|
672
|
+
function createTransformInterpolator() {
|
|
673
|
+
return new TransformInterpolator();
|
|
674
|
+
}
|
|
675
|
+
__name(createTransformInterpolator, "createTransformInterpolator");
|
|
676
|
+
function createHermiteTransformInterpolator() {
|
|
677
|
+
return new HermiteTransformInterpolator();
|
|
678
|
+
}
|
|
679
|
+
__name(createHermiteTransformInterpolator, "createHermiteTransformInterpolator");
|
|
680
|
+
|
|
681
|
+
// src/sync/ClientPrediction.ts
|
|
682
|
+
var _ClientPrediction = class _ClientPrediction {
|
|
683
|
+
constructor(predictor, config) {
|
|
684
|
+
__publicField(this, "_predictor");
|
|
685
|
+
__publicField(this, "_config");
|
|
686
|
+
__publicField(this, "_pendingInputs", []);
|
|
687
|
+
__publicField(this, "_lastAcknowledgedSequence", 0);
|
|
688
|
+
__publicField(this, "_currentSequence", 0);
|
|
689
|
+
__publicField(this, "_lastServerState", null);
|
|
690
|
+
__publicField(this, "_predictedState", null);
|
|
691
|
+
__publicField(this, "_correctionOffset", {
|
|
692
|
+
x: 0,
|
|
693
|
+
y: 0
|
|
694
|
+
});
|
|
695
|
+
this._predictor = predictor;
|
|
696
|
+
this._config = {
|
|
697
|
+
maxUnacknowledgedInputs: 60,
|
|
698
|
+
reconciliationThreshold: 0.1,
|
|
699
|
+
reconciliationSpeed: 10,
|
|
700
|
+
...config
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* @zh 获取当前预测状态
|
|
705
|
+
* @en Get current predicted state
|
|
706
|
+
*/
|
|
707
|
+
get predictedState() {
|
|
708
|
+
return this._predictedState;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* @zh 获取校正偏移
|
|
712
|
+
* @en Get correction offset
|
|
713
|
+
*/
|
|
714
|
+
get correctionOffset() {
|
|
715
|
+
return this._correctionOffset;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* @zh 获取待确认输入数量
|
|
719
|
+
* @en Get pending input count
|
|
720
|
+
*/
|
|
721
|
+
get pendingInputCount() {
|
|
722
|
+
return this._pendingInputs.length;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* @zh 记录并预测输入
|
|
726
|
+
* @en Record and predict input
|
|
727
|
+
*
|
|
728
|
+
* @param input - @zh 输入数据 @en Input data
|
|
729
|
+
* @param currentState - @zh 当前状态 @en Current state
|
|
730
|
+
* @param deltaTime - @zh 时间间隔 @en Delta time
|
|
731
|
+
* @returns @zh 预测的状态 @en Predicted state
|
|
732
|
+
*/
|
|
733
|
+
recordInput(input, currentState, deltaTime) {
|
|
734
|
+
this._currentSequence++;
|
|
735
|
+
const inputSnapshot = {
|
|
736
|
+
sequence: this._currentSequence,
|
|
737
|
+
input,
|
|
738
|
+
timestamp: Date.now()
|
|
739
|
+
};
|
|
740
|
+
this._pendingInputs.push(inputSnapshot);
|
|
741
|
+
while (this._pendingInputs.length > this._config.maxUnacknowledgedInputs) {
|
|
742
|
+
this._pendingInputs.shift();
|
|
743
|
+
}
|
|
744
|
+
this._predictedState = this._predictor.predict(currentState, input, deltaTime);
|
|
745
|
+
return this._predictedState;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* @zh 获取下一个要发送的输入
|
|
749
|
+
* @en Get next input to send
|
|
750
|
+
*/
|
|
751
|
+
getInputToSend() {
|
|
752
|
+
return this._pendingInputs.length > 0 ? this._pendingInputs[this._pendingInputs.length - 1] : null;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* @zh 获取当前序列号
|
|
756
|
+
* @en Get current sequence number
|
|
757
|
+
*/
|
|
758
|
+
get currentSequence() {
|
|
759
|
+
return this._currentSequence;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* @zh 处理服务器状态并进行校正
|
|
763
|
+
* @en Process server state and reconcile
|
|
764
|
+
*
|
|
765
|
+
* @param serverState - @zh 服务器状态 @en Server state
|
|
766
|
+
* @param acknowledgedSequence - @zh 已确认的输入序列号 @en Acknowledged input sequence
|
|
767
|
+
* @param stateGetter - @zh 获取状态位置的函数 @en Function to get state position
|
|
768
|
+
* @param deltaTime - @zh 帧时间 @en Frame delta time
|
|
769
|
+
*/
|
|
770
|
+
reconcile(serverState, acknowledgedSequence, stateGetter, deltaTime) {
|
|
771
|
+
this._lastServerState = serverState;
|
|
772
|
+
this._lastAcknowledgedSequence = acknowledgedSequence;
|
|
773
|
+
while (this._pendingInputs.length > 0 && this._pendingInputs[0].sequence <= acknowledgedSequence) {
|
|
774
|
+
this._pendingInputs.shift();
|
|
775
|
+
}
|
|
776
|
+
let state = serverState;
|
|
777
|
+
for (const inputSnapshot of this._pendingInputs) {
|
|
778
|
+
state = this._predictor.predict(state, inputSnapshot.input, deltaTime);
|
|
779
|
+
}
|
|
780
|
+
const serverPos = stateGetter(serverState);
|
|
781
|
+
const predictedPos = stateGetter(state);
|
|
782
|
+
const errorX = serverPos.x - predictedPos.x;
|
|
783
|
+
const errorY = serverPos.y - predictedPos.y;
|
|
784
|
+
const errorMagnitude = Math.sqrt(errorX * errorX + errorY * errorY);
|
|
785
|
+
if (errorMagnitude > this._config.reconciliationThreshold) {
|
|
786
|
+
const t = Math.min(1, this._config.reconciliationSpeed * deltaTime);
|
|
787
|
+
this._correctionOffset.x += errorX * t;
|
|
788
|
+
this._correctionOffset.y += errorY * t;
|
|
789
|
+
}
|
|
790
|
+
const decayRate = 0.9;
|
|
791
|
+
this._correctionOffset.x *= decayRate;
|
|
792
|
+
this._correctionOffset.y *= decayRate;
|
|
793
|
+
this._predictedState = state;
|
|
794
|
+
return state;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* @zh 清空预测状态
|
|
798
|
+
* @en Clear prediction state
|
|
799
|
+
*/
|
|
800
|
+
clear() {
|
|
801
|
+
this._pendingInputs.length = 0;
|
|
802
|
+
this._lastAcknowledgedSequence = 0;
|
|
803
|
+
this._currentSequence = 0;
|
|
804
|
+
this._lastServerState = null;
|
|
805
|
+
this._predictedState = null;
|
|
806
|
+
this._correctionOffset = {
|
|
807
|
+
x: 0,
|
|
808
|
+
y: 0
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
__name(_ClientPrediction, "ClientPrediction");
|
|
813
|
+
var ClientPrediction = _ClientPrediction;
|
|
814
|
+
function createClientPrediction(predictor, config) {
|
|
815
|
+
return new ClientPrediction(predictor, config);
|
|
816
|
+
}
|
|
817
|
+
__name(createClientPrediction, "createClientPrediction");
|
|
818
|
+
|
|
819
|
+
// src/sync/StateDelta.ts
|
|
820
|
+
var DeltaFlags = {
|
|
821
|
+
NONE: 0,
|
|
822
|
+
POSITION: 1 << 0,
|
|
823
|
+
ROTATION: 1 << 1,
|
|
824
|
+
VELOCITY: 1 << 2,
|
|
825
|
+
ANGULAR_VELOCITY: 1 << 3,
|
|
826
|
+
CUSTOM: 1 << 4
|
|
827
|
+
};
|
|
828
|
+
var DEFAULT_CONFIG = {
|
|
829
|
+
positionThreshold: 0.01,
|
|
830
|
+
rotationThreshold: 1e-3,
|
|
831
|
+
velocityThreshold: 0.1,
|
|
832
|
+
fullSnapshotInterval: 60
|
|
833
|
+
};
|
|
834
|
+
var _StateDeltaCompressor = class _StateDeltaCompressor {
|
|
835
|
+
constructor(config) {
|
|
836
|
+
__publicField(this, "_config");
|
|
837
|
+
__publicField(this, "_lastStates", /* @__PURE__ */ new Map());
|
|
838
|
+
__publicField(this, "_frameCounter", 0);
|
|
839
|
+
this._config = {
|
|
840
|
+
...DEFAULT_CONFIG,
|
|
841
|
+
...config
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* @zh 获取配置
|
|
846
|
+
* @en Get configuration
|
|
847
|
+
*/
|
|
848
|
+
get config() {
|
|
849
|
+
return this._config;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* @zh 压缩同步数据为增量格式
|
|
853
|
+
* @en Compress sync data to delta format
|
|
854
|
+
*/
|
|
855
|
+
compress(data) {
|
|
856
|
+
this._frameCounter++;
|
|
857
|
+
const isFullSnapshot = this._frameCounter % this._config.fullSnapshotInterval === 0;
|
|
858
|
+
const deltaEntities = [];
|
|
859
|
+
for (const entity of data.entities) {
|
|
860
|
+
const lastState = this._lastStates.get(entity.netId);
|
|
861
|
+
if (isFullSnapshot || !lastState) {
|
|
862
|
+
deltaEntities.push(this._createFullDelta(entity));
|
|
863
|
+
} else {
|
|
864
|
+
const delta = this._calculateDelta(lastState, entity);
|
|
865
|
+
if (delta) {
|
|
866
|
+
deltaEntities.push(delta);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
this._lastStates.set(entity.netId, {
|
|
870
|
+
...entity
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
frame: data.frame,
|
|
875
|
+
timestamp: data.timestamp,
|
|
876
|
+
ackSeq: data.ackSeq,
|
|
877
|
+
entities: deltaEntities,
|
|
878
|
+
isFullSnapshot
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* @zh 解压增量数据为完整同步数据
|
|
883
|
+
* @en Decompress delta data to full sync data
|
|
884
|
+
*/
|
|
885
|
+
decompress(data) {
|
|
886
|
+
const entities = [];
|
|
887
|
+
for (const delta of data.entities) {
|
|
888
|
+
const lastState = this._lastStates.get(delta.netId);
|
|
889
|
+
const fullState = this._applyDelta(lastState, delta);
|
|
890
|
+
entities.push(fullState);
|
|
891
|
+
this._lastStates.set(delta.netId, fullState);
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
frame: data.frame,
|
|
895
|
+
timestamp: data.timestamp,
|
|
896
|
+
ackSeq: data.ackSeq,
|
|
897
|
+
entities
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* @zh 移除实体状态
|
|
902
|
+
* @en Remove entity state
|
|
903
|
+
*/
|
|
904
|
+
removeEntity(netId) {
|
|
905
|
+
this._lastStates.delete(netId);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* @zh 清除所有状态
|
|
909
|
+
* @en Clear all states
|
|
910
|
+
*/
|
|
911
|
+
clear() {
|
|
912
|
+
this._lastStates.clear();
|
|
913
|
+
this._frameCounter = 0;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* @zh 强制下一次发送完整快照
|
|
917
|
+
* @en Force next send to be a full snapshot
|
|
918
|
+
*/
|
|
919
|
+
forceFullSnapshot() {
|
|
920
|
+
this._frameCounter = this._config.fullSnapshotInterval - 1;
|
|
921
|
+
}
|
|
922
|
+
// =========================================================================
|
|
923
|
+
// 私有方法 | Private Methods
|
|
924
|
+
// =========================================================================
|
|
925
|
+
_createFullDelta(entity) {
|
|
926
|
+
let flags = 0;
|
|
927
|
+
if (entity.pos) flags |= DeltaFlags.POSITION;
|
|
928
|
+
if (entity.rot !== void 0) flags |= DeltaFlags.ROTATION;
|
|
929
|
+
if (entity.vel) flags |= DeltaFlags.VELOCITY;
|
|
930
|
+
if (entity.angVel !== void 0) flags |= DeltaFlags.ANGULAR_VELOCITY;
|
|
931
|
+
if (entity.custom) flags |= DeltaFlags.CUSTOM;
|
|
932
|
+
return {
|
|
933
|
+
netId: entity.netId,
|
|
934
|
+
flags,
|
|
935
|
+
pos: entity.pos,
|
|
936
|
+
rot: entity.rot,
|
|
937
|
+
vel: entity.vel,
|
|
938
|
+
angVel: entity.angVel,
|
|
939
|
+
custom: entity.custom
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
_calculateDelta(lastState, currentState) {
|
|
943
|
+
let flags = 0;
|
|
944
|
+
const delta = {
|
|
945
|
+
netId: currentState.netId,
|
|
946
|
+
flags: 0
|
|
947
|
+
};
|
|
948
|
+
if (currentState.pos) {
|
|
949
|
+
const posChanged = !lastState.pos || Math.abs(currentState.pos.x - lastState.pos.x) > this._config.positionThreshold || Math.abs(currentState.pos.y - lastState.pos.y) > this._config.positionThreshold;
|
|
950
|
+
if (posChanged) {
|
|
951
|
+
flags |= DeltaFlags.POSITION;
|
|
952
|
+
delta.pos = currentState.pos;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (currentState.rot !== void 0) {
|
|
956
|
+
const rotChanged = lastState.rot === void 0 || Math.abs(currentState.rot - lastState.rot) > this._config.rotationThreshold;
|
|
957
|
+
if (rotChanged) {
|
|
958
|
+
flags |= DeltaFlags.ROTATION;
|
|
959
|
+
delta.rot = currentState.rot;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (currentState.vel) {
|
|
963
|
+
const velChanged = !lastState.vel || Math.abs(currentState.vel.x - lastState.vel.x) > this._config.velocityThreshold || Math.abs(currentState.vel.y - lastState.vel.y) > this._config.velocityThreshold;
|
|
964
|
+
if (velChanged) {
|
|
965
|
+
flags |= DeltaFlags.VELOCITY;
|
|
966
|
+
delta.vel = currentState.vel;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (currentState.angVel !== void 0) {
|
|
970
|
+
const angVelChanged = lastState.angVel === void 0 || Math.abs(currentState.angVel - lastState.angVel) > this._config.velocityThreshold;
|
|
971
|
+
if (angVelChanged) {
|
|
972
|
+
flags |= DeltaFlags.ANGULAR_VELOCITY;
|
|
973
|
+
delta.angVel = currentState.angVel;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
if (currentState.custom) {
|
|
977
|
+
const customChanged = !this._customDataEqual(lastState.custom, currentState.custom);
|
|
978
|
+
if (customChanged) {
|
|
979
|
+
flags |= DeltaFlags.CUSTOM;
|
|
980
|
+
delta.custom = currentState.custom;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (flags === 0) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
delta.flags = flags;
|
|
987
|
+
return delta;
|
|
988
|
+
}
|
|
989
|
+
_applyDelta(lastState, delta) {
|
|
990
|
+
const state = {
|
|
991
|
+
netId: delta.netId
|
|
992
|
+
};
|
|
993
|
+
if (delta.flags & DeltaFlags.POSITION) {
|
|
994
|
+
state.pos = delta.pos;
|
|
995
|
+
} else if (lastState?.pos) {
|
|
996
|
+
state.pos = lastState.pos;
|
|
997
|
+
}
|
|
998
|
+
if (delta.flags & DeltaFlags.ROTATION) {
|
|
999
|
+
state.rot = delta.rot;
|
|
1000
|
+
} else if (lastState?.rot !== void 0) {
|
|
1001
|
+
state.rot = lastState.rot;
|
|
1002
|
+
}
|
|
1003
|
+
if (delta.flags & DeltaFlags.VELOCITY) {
|
|
1004
|
+
state.vel = delta.vel;
|
|
1005
|
+
} else if (lastState?.vel) {
|
|
1006
|
+
state.vel = lastState.vel;
|
|
1007
|
+
}
|
|
1008
|
+
if (delta.flags & DeltaFlags.ANGULAR_VELOCITY) {
|
|
1009
|
+
state.angVel = delta.angVel;
|
|
1010
|
+
} else if (lastState?.angVel !== void 0) {
|
|
1011
|
+
state.angVel = lastState.angVel;
|
|
1012
|
+
}
|
|
1013
|
+
if (delta.flags & DeltaFlags.CUSTOM) {
|
|
1014
|
+
state.custom = delta.custom;
|
|
1015
|
+
} else if (lastState?.custom) {
|
|
1016
|
+
state.custom = lastState.custom;
|
|
1017
|
+
}
|
|
1018
|
+
return state;
|
|
1019
|
+
}
|
|
1020
|
+
_customDataEqual(a, b) {
|
|
1021
|
+
if (a === b) return true;
|
|
1022
|
+
if (!a || !b) return false;
|
|
1023
|
+
const keysA = Object.keys(a);
|
|
1024
|
+
const keysB = Object.keys(b);
|
|
1025
|
+
if (keysA.length !== keysB.length) return false;
|
|
1026
|
+
for (const key of keysA) {
|
|
1027
|
+
if (a[key] !== b[key]) return false;
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
__name(_StateDeltaCompressor, "StateDeltaCompressor");
|
|
1033
|
+
var StateDeltaCompressor = _StateDeltaCompressor;
|
|
1034
|
+
function createStateDeltaCompressor(config) {
|
|
1035
|
+
return new StateDeltaCompressor(config);
|
|
1036
|
+
}
|
|
1037
|
+
__name(createStateDeltaCompressor, "createStateDeltaCompressor");
|
|
1038
|
+
|
|
1039
|
+
// src/sync/ComponentSync.ts
|
|
1040
|
+
import {
|
|
1041
|
+
EntitySystem,
|
|
1042
|
+
Matcher,
|
|
1043
|
+
SyncOperation,
|
|
1044
|
+
SYNC_METADATA,
|
|
1045
|
+
CHANGE_TRACKER,
|
|
1046
|
+
encodeSnapshot,
|
|
1047
|
+
encodeSpawn,
|
|
1048
|
+
encodeDespawn,
|
|
1049
|
+
decodeSnapshot,
|
|
1050
|
+
decodeSpawn,
|
|
1051
|
+
processDespawn,
|
|
1052
|
+
registerSyncComponent
|
|
1053
|
+
} from "@esengine/ecs-framework";
|
|
1054
|
+
var DEFAULT_CONFIG2 = {
|
|
1055
|
+
enableDeltaSync: true,
|
|
1056
|
+
syncInterval: 50
|
|
1057
|
+
};
|
|
1058
|
+
var _ComponentSyncSystem = class _ComponentSyncSystem extends EntitySystem {
|
|
1059
|
+
constructor(config, isServer = false) {
|
|
1060
|
+
super(Matcher.all(NetworkIdentity));
|
|
1061
|
+
__publicField(this, "_config");
|
|
1062
|
+
__publicField(this, "_syncEntityMap", /* @__PURE__ */ new Map());
|
|
1063
|
+
__publicField(this, "_syncListeners", /* @__PURE__ */ new Set());
|
|
1064
|
+
__publicField(this, "_lastSyncTime", 0);
|
|
1065
|
+
__publicField(this, "_isServer", false);
|
|
1066
|
+
this._config = {
|
|
1067
|
+
...DEFAULT_CONFIG2,
|
|
1068
|
+
...config
|
|
1069
|
+
};
|
|
1070
|
+
this._isServer = isServer;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* @zh 设置是否为服务端模式
|
|
1074
|
+
* @en Set whether in server mode
|
|
1075
|
+
*/
|
|
1076
|
+
set isServer(value) {
|
|
1077
|
+
this._isServer = value;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* @zh 获取是否为服务端模式
|
|
1081
|
+
* @en Get whether in server mode
|
|
1082
|
+
*/
|
|
1083
|
+
get isServer() {
|
|
1084
|
+
return this._isServer;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* @zh 获取配置
|
|
1088
|
+
* @en Get configuration
|
|
1089
|
+
*/
|
|
1090
|
+
get config() {
|
|
1091
|
+
return this._config;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* @zh 添加同步事件监听器
|
|
1095
|
+
* @en Add sync event listener
|
|
1096
|
+
*/
|
|
1097
|
+
addSyncListener(listener) {
|
|
1098
|
+
this._syncListeners.add(listener);
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* @zh 移除同步事件监听器
|
|
1102
|
+
* @en Remove sync event listener
|
|
1103
|
+
*/
|
|
1104
|
+
removeSyncListener(listener) {
|
|
1105
|
+
this._syncListeners.delete(listener);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* @zh 注册同步组件类型
|
|
1109
|
+
* @en Register sync component type
|
|
1110
|
+
*
|
|
1111
|
+
* @zh 客户端需要调用此方法注册所有需要同步的组件类型
|
|
1112
|
+
* @en Client needs to call this to register all component types to be synced
|
|
1113
|
+
*/
|
|
1114
|
+
registerComponent(componentClass) {
|
|
1115
|
+
const metadata = componentClass[SYNC_METADATA];
|
|
1116
|
+
if (metadata) {
|
|
1117
|
+
registerSyncComponent(metadata.typeId, componentClass);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
// =========================================================================
|
|
1121
|
+
// Server-side: Encoding | 服务端:编码
|
|
1122
|
+
// =========================================================================
|
|
1123
|
+
/**
|
|
1124
|
+
* @zh 编码所有实体状态
|
|
1125
|
+
* @en Encode all entities state
|
|
1126
|
+
*
|
|
1127
|
+
* @param fullSync - @zh 是否完整同步(首次连接时使用)@en Whether to do full sync (for initial connection)
|
|
1128
|
+
* @returns @zh 编码后的二进制数据 @en Encoded binary data
|
|
1129
|
+
*/
|
|
1130
|
+
encodeAllEntities(fullSync = false) {
|
|
1131
|
+
const entities = this.getMatchingEntities();
|
|
1132
|
+
const operation = fullSync ? SyncOperation.FULL : SyncOperation.DELTA;
|
|
1133
|
+
const data = encodeSnapshot(entities, operation);
|
|
1134
|
+
if (!fullSync) {
|
|
1135
|
+
this._clearChangeTrackers(entities);
|
|
1136
|
+
}
|
|
1137
|
+
return data;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* @zh 编码有变更的实体
|
|
1141
|
+
* @en Encode entities with changes
|
|
1142
|
+
*
|
|
1143
|
+
* @returns @zh 编码后的二进制数据,如果没有变更返回 null @en Encoded binary data, or null if no changes
|
|
1144
|
+
*/
|
|
1145
|
+
encodeDelta() {
|
|
1146
|
+
const entities = this.getMatchingEntities();
|
|
1147
|
+
const changedEntities = entities.filter((entity) => this._hasChanges(entity));
|
|
1148
|
+
if (changedEntities.length === 0) {
|
|
1149
|
+
return null;
|
|
1150
|
+
}
|
|
1151
|
+
const data = encodeSnapshot(changedEntities, SyncOperation.DELTA);
|
|
1152
|
+
this._clearChangeTrackers(changedEntities);
|
|
1153
|
+
return data;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* @zh 编码实体生成消息
|
|
1157
|
+
* @en Encode entity spawn message
|
|
1158
|
+
*/
|
|
1159
|
+
encodeSpawn(entity, prefabType) {
|
|
1160
|
+
return encodeSpawn(entity, prefabType);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* @zh 编码实体销毁消息
|
|
1164
|
+
* @en Encode entity despawn message
|
|
1165
|
+
*/
|
|
1166
|
+
encodeDespawn(entityId) {
|
|
1167
|
+
return encodeDespawn(entityId);
|
|
1168
|
+
}
|
|
1169
|
+
// =========================================================================
|
|
1170
|
+
// Client-side: Decoding | 客户端:解码
|
|
1171
|
+
// =========================================================================
|
|
1172
|
+
/**
|
|
1173
|
+
* @zh 应用状态快照
|
|
1174
|
+
* @en Apply state snapshot
|
|
1175
|
+
*
|
|
1176
|
+
* @param data - @zh 二进制数据 @en Binary data
|
|
1177
|
+
* @returns @zh 解码结果 @en Decode result
|
|
1178
|
+
*/
|
|
1179
|
+
applySnapshot(data) {
|
|
1180
|
+
if (!this.scene) {
|
|
1181
|
+
throw new Error("ComponentSyncSystem not attached to a scene");
|
|
1182
|
+
}
|
|
1183
|
+
const result = decodeSnapshot(this.scene, data, this._syncEntityMap);
|
|
1184
|
+
for (const entityResult of result.entities) {
|
|
1185
|
+
if (entityResult.isNew) {
|
|
1186
|
+
this._emitEvent({
|
|
1187
|
+
type: "entitySpawned",
|
|
1188
|
+
entityId: entityResult.entityId
|
|
1189
|
+
});
|
|
1190
|
+
} else {
|
|
1191
|
+
this._emitEvent({
|
|
1192
|
+
type: "stateUpdated",
|
|
1193
|
+
entityId: entityResult.entityId
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return result;
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* @zh 应用实体生成消息
|
|
1201
|
+
* @en Apply entity spawn message
|
|
1202
|
+
*
|
|
1203
|
+
* @param data - @zh 二进制数据 @en Binary data
|
|
1204
|
+
* @returns @zh 解码结果,如果不是 SPAWN 消息返回 null @en Decode result, or null if not a SPAWN message
|
|
1205
|
+
*/
|
|
1206
|
+
applySpawn(data) {
|
|
1207
|
+
if (!this.scene) {
|
|
1208
|
+
throw new Error("ComponentSyncSystem not attached to a scene");
|
|
1209
|
+
}
|
|
1210
|
+
const result = decodeSpawn(this.scene, data, this._syncEntityMap);
|
|
1211
|
+
if (result) {
|
|
1212
|
+
this._emitEvent({
|
|
1213
|
+
type: "entitySpawned",
|
|
1214
|
+
entityId: result.entity.id,
|
|
1215
|
+
prefabType: result.prefabType
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
return result;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* @zh 应用实体销毁消息
|
|
1222
|
+
* @en Apply entity despawn message
|
|
1223
|
+
*
|
|
1224
|
+
* @param data - @zh 二进制数据 @en Binary data
|
|
1225
|
+
* @returns @zh 销毁的实体 ID 列表 @en List of despawned entity IDs
|
|
1226
|
+
*/
|
|
1227
|
+
applyDespawn(data) {
|
|
1228
|
+
if (!this.scene) {
|
|
1229
|
+
throw new Error("ComponentSyncSystem not attached to a scene");
|
|
1230
|
+
}
|
|
1231
|
+
const entityIds = processDespawn(this.scene, data, this._syncEntityMap);
|
|
1232
|
+
for (const entityId of entityIds) {
|
|
1233
|
+
this._emitEvent({
|
|
1234
|
+
type: "entityDespawned",
|
|
1235
|
+
entityId
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return entityIds;
|
|
1239
|
+
}
|
|
1240
|
+
// =========================================================================
|
|
1241
|
+
// Entity Management | 实体管理
|
|
1242
|
+
// =========================================================================
|
|
1243
|
+
/**
|
|
1244
|
+
* @zh 通过网络 ID 获取实体
|
|
1245
|
+
* @en Get entity by network ID
|
|
1246
|
+
*/
|
|
1247
|
+
getEntityById(entityId) {
|
|
1248
|
+
return this._syncEntityMap.get(entityId);
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* @zh 获取所有匹配的实体
|
|
1252
|
+
* @en Get all matching entities
|
|
1253
|
+
*/
|
|
1254
|
+
getMatchingEntities() {
|
|
1255
|
+
return this.entities.slice();
|
|
1256
|
+
}
|
|
1257
|
+
// =========================================================================
|
|
1258
|
+
// Internal | 内部方法
|
|
1259
|
+
// =========================================================================
|
|
1260
|
+
process(entities) {
|
|
1261
|
+
if (this._isServer && this._config.enableDeltaSync) {
|
|
1262
|
+
const now = Date.now();
|
|
1263
|
+
if (now - this._lastSyncTime >= this._config.syncInterval) {
|
|
1264
|
+
this._lastSyncTime = now;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
for (const entity of entities) {
|
|
1268
|
+
const identity = entity.getComponent(NetworkIdentity);
|
|
1269
|
+
if (identity) {
|
|
1270
|
+
this._syncEntityMap.set(entity.id, entity);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
_hasChanges(entity) {
|
|
1275
|
+
for (const component of entity.components) {
|
|
1276
|
+
const tracker = component[CHANGE_TRACKER];
|
|
1277
|
+
if (tracker?.hasChanges()) {
|
|
1278
|
+
return true;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
_clearChangeTrackers(entities) {
|
|
1284
|
+
for (const entity of entities) {
|
|
1285
|
+
for (const component of entity.components) {
|
|
1286
|
+
const tracker = component[CHANGE_TRACKER];
|
|
1287
|
+
if (tracker) {
|
|
1288
|
+
tracker.clear();
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
_emitEvent(event) {
|
|
1294
|
+
for (const listener of this._syncListeners) {
|
|
1295
|
+
try {
|
|
1296
|
+
listener(event);
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
console.error("ComponentSyncSystem: event listener error:", error);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
onDestroy() {
|
|
1303
|
+
this._syncEntityMap.clear();
|
|
1304
|
+
this._syncListeners.clear();
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
__name(_ComponentSyncSystem, "ComponentSyncSystem");
|
|
1308
|
+
var ComponentSyncSystem = _ComponentSyncSystem;
|
|
1309
|
+
function createComponentSyncSystem(config, isServer = false) {
|
|
1310
|
+
return new ComponentSyncSystem(config, isServer);
|
|
1311
|
+
}
|
|
1312
|
+
__name(createComponentSyncSystem, "createComponentSyncSystem");
|
|
1313
|
+
|
|
1314
|
+
// src/systems/NetworkSyncSystem.ts
|
|
1315
|
+
var DEFAULT_CONFIG3 = {
|
|
1316
|
+
bufferSize: 30,
|
|
1317
|
+
interpolationDelay: 100,
|
|
1318
|
+
enableExtrapolation: true,
|
|
1319
|
+
maxExtrapolationTime: 200,
|
|
1320
|
+
useHermiteInterpolation: false
|
|
1321
|
+
};
|
|
1322
|
+
var _NetworkSyncSystem = class _NetworkSyncSystem extends EntitySystem2 {
|
|
1323
|
+
constructor(config) {
|
|
1324
|
+
super(Matcher2.all(NetworkIdentity, NetworkTransform));
|
|
1325
|
+
__publicField(this, "_netIdToEntity", /* @__PURE__ */ new Map());
|
|
1326
|
+
__publicField(this, "_entitySnapshots", /* @__PURE__ */ new Map());
|
|
1327
|
+
__publicField(this, "_interpolator");
|
|
1328
|
+
__publicField(this, "_config");
|
|
1329
|
+
__publicField(this, "_serverTimeOffset", 0);
|
|
1330
|
+
__publicField(this, "_lastSyncTime", 0);
|
|
1331
|
+
__publicField(this, "_renderTime", 0);
|
|
1332
|
+
this._config = {
|
|
1333
|
+
...DEFAULT_CONFIG3,
|
|
1334
|
+
...config
|
|
1335
|
+
};
|
|
1336
|
+
this._interpolator = createTransformInterpolator();
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* @zh 获取配置
|
|
1340
|
+
* @en Get configuration
|
|
1341
|
+
*/
|
|
1342
|
+
get config() {
|
|
1343
|
+
return this._config;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* @zh 获取服务器时间偏移
|
|
1347
|
+
* @en Get server time offset
|
|
1348
|
+
*/
|
|
1349
|
+
get serverTimeOffset() {
|
|
1350
|
+
return this._serverTimeOffset;
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* @zh 获取当前渲染时间
|
|
1354
|
+
* @en Get current render time
|
|
1355
|
+
*/
|
|
1356
|
+
get renderTime() {
|
|
1357
|
+
return this._renderTime;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* @zh 处理同步消息(新版,带时间戳)
|
|
1361
|
+
* @en Handle sync message (new version with timestamp)
|
|
1362
|
+
*/
|
|
1363
|
+
handleSyncData(data) {
|
|
1364
|
+
const serverTime = data.timestamp;
|
|
1365
|
+
const clientTime = Date.now();
|
|
1366
|
+
this._serverTimeOffset = serverTime - clientTime;
|
|
1367
|
+
this._lastSyncTime = clientTime;
|
|
1368
|
+
for (const state of data.entities) {
|
|
1369
|
+
this._processEntityState(state, serverTime);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* @zh 处理同步消息(兼容旧版)
|
|
1374
|
+
* @en Handle sync message (backwards compatible)
|
|
1375
|
+
*/
|
|
1376
|
+
handleSync(msg) {
|
|
1377
|
+
const now = Date.now();
|
|
1378
|
+
for (const state of msg.entities) {
|
|
1379
|
+
const entityId = this._netIdToEntity.get(state.netId);
|
|
1380
|
+
if (entityId === void 0) continue;
|
|
1381
|
+
const entity = this.scene?.findEntityById(entityId);
|
|
1382
|
+
if (!entity) continue;
|
|
1383
|
+
const transform = entity.getComponent(NetworkTransform);
|
|
1384
|
+
if (transform && state.pos) {
|
|
1385
|
+
transform.setTarget(state.pos.x, state.pos.y, state.rot ?? 0);
|
|
1386
|
+
}
|
|
1387
|
+
this._processEntityState({
|
|
1388
|
+
netId: state.netId,
|
|
1389
|
+
pos: state.pos,
|
|
1390
|
+
rot: state.rot
|
|
1391
|
+
}, now);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
_processEntityState(state, serverTime) {
|
|
1395
|
+
const entityId = this._netIdToEntity.get(state.netId);
|
|
1396
|
+
if (entityId === void 0) return;
|
|
1397
|
+
let snapshotData = this._entitySnapshots.get(state.netId);
|
|
1398
|
+
if (!snapshotData) {
|
|
1399
|
+
snapshotData = {
|
|
1400
|
+
buffer: createSnapshotBuffer(this._config.bufferSize, this._config.interpolationDelay),
|
|
1401
|
+
lastServerTime: 0
|
|
1402
|
+
};
|
|
1403
|
+
this._entitySnapshots.set(state.netId, snapshotData);
|
|
1404
|
+
}
|
|
1405
|
+
const transformState = {
|
|
1406
|
+
x: state.pos?.x ?? 0,
|
|
1407
|
+
y: state.pos?.y ?? 0,
|
|
1408
|
+
rotation: state.rot ?? 0,
|
|
1409
|
+
velocityX: state.vel?.x ?? 0,
|
|
1410
|
+
velocityY: state.vel?.y ?? 0,
|
|
1411
|
+
angularVelocity: state.angVel ?? 0
|
|
1412
|
+
};
|
|
1413
|
+
const snapshot = {
|
|
1414
|
+
timestamp: serverTime,
|
|
1415
|
+
state: transformState
|
|
1416
|
+
};
|
|
1417
|
+
snapshotData.buffer.push(snapshot);
|
|
1418
|
+
snapshotData.lastServerTime = serverTime;
|
|
1419
|
+
}
|
|
1420
|
+
process(entities) {
|
|
1421
|
+
const deltaTime = Time.deltaTime;
|
|
1422
|
+
const clientTime = Date.now();
|
|
1423
|
+
this._renderTime = clientTime + this._serverTimeOffset;
|
|
1424
|
+
for (const entity of entities) {
|
|
1425
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
1426
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
1427
|
+
if (identity.bHasAuthority) continue;
|
|
1428
|
+
if (transform.bInterpolate) {
|
|
1429
|
+
this._interpolateEntity(identity.netId, transform, deltaTime);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
_interpolateEntity(netId, transform, deltaTime) {
|
|
1434
|
+
const snapshotData = this._entitySnapshots.get(netId);
|
|
1435
|
+
if (snapshotData && snapshotData.buffer.size >= 2) {
|
|
1436
|
+
const result = snapshotData.buffer.getInterpolationSnapshots(this._renderTime);
|
|
1437
|
+
if (result) {
|
|
1438
|
+
const [prev, next, t] = result;
|
|
1439
|
+
const interpolated = this._interpolator.interpolate(prev.state, next.state, t);
|
|
1440
|
+
transform.currentX = interpolated.x;
|
|
1441
|
+
transform.currentY = interpolated.y;
|
|
1442
|
+
transform.currentRotation = interpolated.rotation;
|
|
1443
|
+
transform.targetX = next.state.x;
|
|
1444
|
+
transform.targetY = next.state.y;
|
|
1445
|
+
transform.targetRotation = next.state.rotation;
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (this._config.enableExtrapolation) {
|
|
1449
|
+
const latest = snapshotData.buffer.getLatest();
|
|
1450
|
+
if (latest) {
|
|
1451
|
+
const timeSinceLastSnapshot = this._renderTime - latest.timestamp;
|
|
1452
|
+
if (timeSinceLastSnapshot > 0 && timeSinceLastSnapshot < this._config.maxExtrapolationTime) {
|
|
1453
|
+
const extrapolated = this._interpolator.extrapolate(latest.state, timeSinceLastSnapshot / 1e3);
|
|
1454
|
+
transform.currentX = extrapolated.x;
|
|
1455
|
+
transform.currentY = extrapolated.y;
|
|
1456
|
+
transform.currentRotation = extrapolated.rotation;
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
this._simpleLerp(transform, deltaTime);
|
|
1463
|
+
}
|
|
1464
|
+
_simpleLerp(transform, deltaTime) {
|
|
1465
|
+
const t = Math.min(1, transform.lerpSpeed * deltaTime);
|
|
1466
|
+
transform.currentX += (transform.targetX - transform.currentX) * t;
|
|
1467
|
+
transform.currentY += (transform.targetY - transform.currentY) * t;
|
|
1468
|
+
let angleDiff = transform.targetRotation - transform.currentRotation;
|
|
1469
|
+
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
|
|
1470
|
+
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
|
|
1471
|
+
transform.currentRotation += angleDiff * t;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* @zh 注册网络实体
|
|
1475
|
+
* @en Register network entity
|
|
1476
|
+
*/
|
|
1477
|
+
registerEntity(netId, entityId) {
|
|
1478
|
+
this._netIdToEntity.set(netId, entityId);
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* @zh 注销网络实体
|
|
1482
|
+
* @en Unregister network entity
|
|
1483
|
+
*/
|
|
1484
|
+
unregisterEntity(netId) {
|
|
1485
|
+
this._netIdToEntity.delete(netId);
|
|
1486
|
+
this._entitySnapshots.delete(netId);
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* @zh 根据网络 ID 获取实体 ID
|
|
1490
|
+
* @en Get entity ID by network ID
|
|
1491
|
+
*/
|
|
1492
|
+
getEntityId(netId) {
|
|
1493
|
+
return this._netIdToEntity.get(netId);
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* @zh 获取实体的快照缓冲区
|
|
1497
|
+
* @en Get entity's snapshot buffer
|
|
1498
|
+
*/
|
|
1499
|
+
getSnapshotBuffer(netId) {
|
|
1500
|
+
return this._entitySnapshots.get(netId)?.buffer;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* @zh 清空所有快照缓冲
|
|
1504
|
+
* @en Clear all snapshot buffers
|
|
1505
|
+
*/
|
|
1506
|
+
clearSnapshots() {
|
|
1507
|
+
for (const data of this._entitySnapshots.values()) {
|
|
1508
|
+
data.buffer.clear();
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
onDestroy() {
|
|
1512
|
+
this._netIdToEntity.clear();
|
|
1513
|
+
this._entitySnapshots.clear();
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
__name(_NetworkSyncSystem, "NetworkSyncSystem");
|
|
1517
|
+
var NetworkSyncSystem = _NetworkSyncSystem;
|
|
1518
|
+
|
|
1519
|
+
// src/systems/NetworkSpawnSystem.ts
|
|
1520
|
+
import { EntitySystem as EntitySystem3, Matcher as Matcher3 } from "@esengine/ecs-framework";
|
|
1521
|
+
var _NetworkSpawnSystem = class _NetworkSpawnSystem extends EntitySystem3 {
|
|
1522
|
+
constructor(syncSystem) {
|
|
1523
|
+
super(Matcher3.nothing());
|
|
1524
|
+
__publicField(this, "_syncSystem");
|
|
1525
|
+
__publicField(this, "_prefabFactories", /* @__PURE__ */ new Map());
|
|
1526
|
+
__publicField(this, "_localPlayerId", 0);
|
|
1527
|
+
this._syncSystem = syncSystem;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* @zh 设置本地玩家 ID
|
|
1531
|
+
* @en Set local player ID
|
|
1532
|
+
*/
|
|
1533
|
+
setLocalPlayerId(id) {
|
|
1534
|
+
this._localPlayerId = id;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* @zh 处理生成消息
|
|
1538
|
+
* @en Handle spawn message
|
|
1539
|
+
*/
|
|
1540
|
+
handleSpawn(msg) {
|
|
1541
|
+
if (!this.scene) return null;
|
|
1542
|
+
const factory = this._prefabFactories.get(msg.prefab);
|
|
1543
|
+
if (!factory) {
|
|
1544
|
+
this.logger.warn(`Unknown prefab: ${msg.prefab}`);
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
const entity = factory(this.scene, msg);
|
|
1548
|
+
const identity = entity.addComponent(new NetworkIdentity());
|
|
1549
|
+
identity.netId = msg.netId;
|
|
1550
|
+
identity.ownerId = msg.ownerId;
|
|
1551
|
+
identity.prefabType = msg.prefab;
|
|
1552
|
+
identity.bHasAuthority = msg.ownerId === this._localPlayerId;
|
|
1553
|
+
identity.bIsLocalPlayer = identity.bHasAuthority;
|
|
1554
|
+
const transform = entity.addComponent(new NetworkTransform());
|
|
1555
|
+
transform.setTarget(msg.pos.x, msg.pos.y, msg.rot ?? 0);
|
|
1556
|
+
transform.snap();
|
|
1557
|
+
this._syncSystem.registerEntity(msg.netId, entity.id);
|
|
1558
|
+
return entity;
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* @zh 处理销毁消息
|
|
1562
|
+
* @en Handle despawn message
|
|
1563
|
+
*/
|
|
1564
|
+
handleDespawn(msg) {
|
|
1565
|
+
const entityId = this._syncSystem.getEntityId(msg.netId);
|
|
1566
|
+
if (entityId === void 0) return;
|
|
1567
|
+
const entity = this.scene?.findEntityById(entityId);
|
|
1568
|
+
if (entity) {
|
|
1569
|
+
entity.destroy();
|
|
1570
|
+
}
|
|
1571
|
+
this._syncSystem.unregisterEntity(msg.netId);
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* @zh 注册预制体工厂
|
|
1575
|
+
* @en Register prefab factory
|
|
1576
|
+
*/
|
|
1577
|
+
registerPrefab(prefabType, factory) {
|
|
1578
|
+
this._prefabFactories.set(prefabType, factory);
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* @zh 注销预制体工厂
|
|
1582
|
+
* @en Unregister prefab factory
|
|
1583
|
+
*/
|
|
1584
|
+
unregisterPrefab(prefabType) {
|
|
1585
|
+
this._prefabFactories.delete(prefabType);
|
|
1586
|
+
}
|
|
1587
|
+
onDestroy() {
|
|
1588
|
+
this._prefabFactories.clear();
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
__name(_NetworkSpawnSystem, "NetworkSpawnSystem");
|
|
1592
|
+
var NetworkSpawnSystem = _NetworkSpawnSystem;
|
|
1593
|
+
|
|
1594
|
+
// src/systems/NetworkInputSystem.ts
|
|
1595
|
+
import { EntitySystem as EntitySystem4, Matcher as Matcher4 } from "@esengine/ecs-framework";
|
|
1596
|
+
var DEFAULT_CONFIG4 = {
|
|
1597
|
+
sendInterval: 16,
|
|
1598
|
+
mergeIdenticalInputs: true,
|
|
1599
|
+
maxQueueLength: 10
|
|
1600
|
+
};
|
|
1601
|
+
var _NetworkInputSystem = class _NetworkInputSystem extends EntitySystem4 {
|
|
1602
|
+
constructor(networkService, config) {
|
|
1603
|
+
super(Matcher4.nothing());
|
|
1604
|
+
__publicField(this, "_networkService");
|
|
1605
|
+
__publicField(this, "_config");
|
|
1606
|
+
__publicField(this, "_predictionSystem", null);
|
|
1607
|
+
__publicField(this, "_frame", 0);
|
|
1608
|
+
__publicField(this, "_inputSequence", 0);
|
|
1609
|
+
__publicField(this, "_inputQueue", []);
|
|
1610
|
+
__publicField(this, "_lastSendTime", 0);
|
|
1611
|
+
__publicField(this, "_lastMoveDir", {
|
|
1612
|
+
x: 0,
|
|
1613
|
+
y: 0
|
|
1614
|
+
});
|
|
1615
|
+
this._networkService = networkService;
|
|
1616
|
+
this._config = {
|
|
1617
|
+
...DEFAULT_CONFIG4,
|
|
1618
|
+
...config
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* @zh 获取配置
|
|
1623
|
+
* @en Get configuration
|
|
1624
|
+
*/
|
|
1625
|
+
get config() {
|
|
1626
|
+
return this._config;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* @zh 获取当前帧号
|
|
1630
|
+
* @en Get current frame number
|
|
1631
|
+
*/
|
|
1632
|
+
get frame() {
|
|
1633
|
+
return this._frame;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* @zh 获取当前输入序列号
|
|
1637
|
+
* @en Get current input sequence
|
|
1638
|
+
*/
|
|
1639
|
+
get inputSequence() {
|
|
1640
|
+
return this._inputSequence;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* @zh 设置预测系统引用
|
|
1644
|
+
* @en Set prediction system reference
|
|
1645
|
+
*/
|
|
1646
|
+
setPredictionSystem(system) {
|
|
1647
|
+
this._predictionSystem = system;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* @zh 处理输入队列
|
|
1651
|
+
* @en Process input queue
|
|
1652
|
+
*/
|
|
1653
|
+
process() {
|
|
1654
|
+
if (!this._networkService.isConnected) return;
|
|
1655
|
+
this._frame++;
|
|
1656
|
+
const now = Date.now();
|
|
1657
|
+
if (now - this._lastSendTime < this._config.sendInterval) return;
|
|
1658
|
+
if (this._predictionSystem) {
|
|
1659
|
+
const predictedInput = this._predictionSystem.getInputToSend();
|
|
1660
|
+
if (predictedInput) {
|
|
1661
|
+
this._networkService.sendInput(predictedInput);
|
|
1662
|
+
this._lastSendTime = now;
|
|
1663
|
+
}
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (this._inputQueue.length === 0) return;
|
|
1667
|
+
let mergedInput;
|
|
1668
|
+
if (this._config.mergeIdenticalInputs && this._inputQueue.length > 1) {
|
|
1669
|
+
mergedInput = this._mergeInputs(this._inputQueue);
|
|
1670
|
+
this._inputQueue.length = 0;
|
|
1671
|
+
} else {
|
|
1672
|
+
mergedInput = this._inputQueue.shift();
|
|
1673
|
+
}
|
|
1674
|
+
this._inputSequence++;
|
|
1675
|
+
const input = {
|
|
1676
|
+
seq: this._inputSequence,
|
|
1677
|
+
frame: this._frame,
|
|
1678
|
+
timestamp: mergedInput.timestamp,
|
|
1679
|
+
moveDir: mergedInput.moveDir,
|
|
1680
|
+
actions: mergedInput.actions
|
|
1681
|
+
};
|
|
1682
|
+
this._networkService.sendInput(input);
|
|
1683
|
+
this._lastSendTime = now;
|
|
1684
|
+
}
|
|
1685
|
+
_mergeInputs(inputs) {
|
|
1686
|
+
const allActions = [];
|
|
1687
|
+
let lastMoveDir;
|
|
1688
|
+
for (const input of inputs) {
|
|
1689
|
+
if (input.moveDir) {
|
|
1690
|
+
lastMoveDir = input.moveDir;
|
|
1691
|
+
}
|
|
1692
|
+
if (input.actions) {
|
|
1693
|
+
allActions.push(...input.actions);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
return {
|
|
1697
|
+
moveDir: lastMoveDir,
|
|
1698
|
+
actions: allActions.length > 0 ? allActions : void 0,
|
|
1699
|
+
timestamp: inputs[inputs.length - 1].timestamp
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* @zh 添加移动输入
|
|
1704
|
+
* @en Add move input
|
|
627
1705
|
*/
|
|
628
1706
|
addMoveInput(x, y) {
|
|
629
|
-
this._inputQueue.
|
|
1707
|
+
if (this._config.mergeIdenticalInputs && this._lastMoveDir.x === x && this._lastMoveDir.y === y && this._inputQueue.length > 0) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
this._lastMoveDir = {
|
|
1711
|
+
x,
|
|
1712
|
+
y
|
|
1713
|
+
};
|
|
1714
|
+
if (this._predictionSystem) {
|
|
1715
|
+
this._predictionSystem.setInput(x, y);
|
|
1716
|
+
}
|
|
1717
|
+
this._addToQueue({
|
|
1718
|
+
moveDir: {
|
|
1719
|
+
x,
|
|
1720
|
+
y
|
|
1721
|
+
},
|
|
1722
|
+
timestamp: Date.now()
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* @zh 添加动作输入
|
|
1727
|
+
* @en Add action input
|
|
1728
|
+
*/
|
|
1729
|
+
addActionInput(action) {
|
|
1730
|
+
const lastInput = this._inputQueue[this._inputQueue.length - 1];
|
|
1731
|
+
if (lastInput) {
|
|
1732
|
+
lastInput.actions = lastInput.actions || [];
|
|
1733
|
+
lastInput.actions.push(action);
|
|
1734
|
+
} else {
|
|
1735
|
+
this._addToQueue({
|
|
1736
|
+
actions: [
|
|
1737
|
+
action
|
|
1738
|
+
],
|
|
1739
|
+
timestamp: Date.now()
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
if (this._predictionSystem) {
|
|
1743
|
+
this._predictionSystem.setInput(this._lastMoveDir.x, this._lastMoveDir.y, [
|
|
1744
|
+
action
|
|
1745
|
+
]);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
_addToQueue(input) {
|
|
1749
|
+
this._inputQueue.push(input);
|
|
1750
|
+
while (this._inputQueue.length > this._config.maxQueueLength) {
|
|
1751
|
+
this._inputQueue.shift();
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* @zh 清空输入队列
|
|
1756
|
+
* @en Clear input queue
|
|
1757
|
+
*/
|
|
1758
|
+
clearQueue() {
|
|
1759
|
+
this._inputQueue.length = 0;
|
|
1760
|
+
this._lastMoveDir = {
|
|
1761
|
+
x: 0,
|
|
1762
|
+
y: 0
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* @zh 重置状态
|
|
1767
|
+
* @en Reset state
|
|
1768
|
+
*/
|
|
1769
|
+
reset() {
|
|
1770
|
+
this._frame = 0;
|
|
1771
|
+
this._inputSequence = 0;
|
|
1772
|
+
this.clearQueue();
|
|
1773
|
+
}
|
|
1774
|
+
onDestroy() {
|
|
1775
|
+
this._inputQueue.length = 0;
|
|
1776
|
+
this._predictionSystem = null;
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
__name(_NetworkInputSystem, "NetworkInputSystem");
|
|
1780
|
+
var NetworkInputSystem = _NetworkInputSystem;
|
|
1781
|
+
function createNetworkInputSystem(networkService, config) {
|
|
1782
|
+
return new NetworkInputSystem(networkService, config);
|
|
1783
|
+
}
|
|
1784
|
+
__name(createNetworkInputSystem, "createNetworkInputSystem");
|
|
1785
|
+
|
|
1786
|
+
// src/systems/NetworkPredictionSystem.ts
|
|
1787
|
+
import { EntitySystem as EntitySystem5, Matcher as Matcher5, Time as Time2 } from "@esengine/ecs-framework";
|
|
1788
|
+
var DEFAULT_CONFIG5 = {
|
|
1789
|
+
moveSpeed: 200,
|
|
1790
|
+
enabled: true,
|
|
1791
|
+
maxUnacknowledgedInputs: 60,
|
|
1792
|
+
reconciliationThreshold: 0.5,
|
|
1793
|
+
reconciliationSpeed: 10
|
|
1794
|
+
};
|
|
1795
|
+
var _a;
|
|
1796
|
+
var SimpleMovementPredictor = (_a = class {
|
|
1797
|
+
constructor(_moveSpeed) {
|
|
1798
|
+
__publicField(this, "_moveSpeed");
|
|
1799
|
+
this._moveSpeed = _moveSpeed;
|
|
1800
|
+
}
|
|
1801
|
+
predict(state, input, deltaTime) {
|
|
1802
|
+
const velocityX = input.x * this._moveSpeed;
|
|
1803
|
+
const velocityY = input.y * this._moveSpeed;
|
|
1804
|
+
return {
|
|
1805
|
+
x: state.x + velocityX * deltaTime,
|
|
1806
|
+
y: state.y + velocityY * deltaTime,
|
|
1807
|
+
rotation: state.rotation,
|
|
1808
|
+
velocityX,
|
|
1809
|
+
velocityY
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
}, __name(_a, "SimpleMovementPredictor"), _a);
|
|
1813
|
+
var _NetworkPredictionSystem = class _NetworkPredictionSystem extends EntitySystem5 {
|
|
1814
|
+
constructor(config) {
|
|
1815
|
+
super(Matcher5.all(NetworkIdentity, NetworkTransform));
|
|
1816
|
+
__publicField(this, "_config");
|
|
1817
|
+
__publicField(this, "_predictor");
|
|
1818
|
+
__publicField(this, "_prediction", null);
|
|
1819
|
+
__publicField(this, "_localPlayerNetId", -1);
|
|
1820
|
+
__publicField(this, "_currentInput", {
|
|
1821
|
+
x: 0,
|
|
1822
|
+
y: 0
|
|
1823
|
+
});
|
|
1824
|
+
__publicField(this, "_inputSequence", 0);
|
|
1825
|
+
this._config = {
|
|
1826
|
+
...DEFAULT_CONFIG5,
|
|
1827
|
+
...config
|
|
1828
|
+
};
|
|
1829
|
+
this._predictor = new SimpleMovementPredictor(this._config.moveSpeed);
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* @zh 获取配置
|
|
1833
|
+
* @en Get configuration
|
|
1834
|
+
*/
|
|
1835
|
+
get config() {
|
|
1836
|
+
return this._config;
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* @zh 获取当前输入序列号
|
|
1840
|
+
* @en Get current input sequence number
|
|
1841
|
+
*/
|
|
1842
|
+
get inputSequence() {
|
|
1843
|
+
return this._inputSequence;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* @zh 获取待确认输入数量
|
|
1847
|
+
* @en Get pending input count
|
|
1848
|
+
*/
|
|
1849
|
+
get pendingInputCount() {
|
|
1850
|
+
return this._prediction?.pendingInputCount ?? 0;
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* @zh 是否启用预测
|
|
1854
|
+
* @en Whether prediction is enabled
|
|
1855
|
+
*/
|
|
1856
|
+
get enabled() {
|
|
1857
|
+
return this._config.enabled;
|
|
1858
|
+
}
|
|
1859
|
+
set enabled(value) {
|
|
1860
|
+
this._config.enabled = value;
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* @zh 设置本地玩家网络 ID
|
|
1864
|
+
* @en Set local player network ID
|
|
1865
|
+
*/
|
|
1866
|
+
setLocalPlayerNetId(netId) {
|
|
1867
|
+
this._localPlayerNetId = netId;
|
|
1868
|
+
this._prediction = createClientPrediction(this._predictor, {
|
|
1869
|
+
maxUnacknowledgedInputs: this._config.maxUnacknowledgedInputs,
|
|
1870
|
+
reconciliationThreshold: this._config.reconciliationThreshold,
|
|
1871
|
+
reconciliationSpeed: this._config.reconciliationSpeed
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* @zh 设置移动输入
|
|
1876
|
+
* @en Set movement input
|
|
1877
|
+
*/
|
|
1878
|
+
setInput(x, y, actions) {
|
|
1879
|
+
this._currentInput = {
|
|
1880
|
+
x,
|
|
1881
|
+
y,
|
|
1882
|
+
actions
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* @zh 获取下一个要发送的输入(带序列号)
|
|
1887
|
+
* @en Get next input to send (with sequence number)
|
|
1888
|
+
*/
|
|
1889
|
+
getInputToSend() {
|
|
1890
|
+
if (!this._prediction) return null;
|
|
1891
|
+
const input = this._prediction.getInputToSend();
|
|
1892
|
+
if (!input) return null;
|
|
1893
|
+
return {
|
|
1894
|
+
seq: input.sequence,
|
|
630
1895
|
frame: 0,
|
|
1896
|
+
timestamp: input.timestamp,
|
|
631
1897
|
moveDir: {
|
|
1898
|
+
x: input.input.x,
|
|
1899
|
+
y: input.input.y
|
|
1900
|
+
},
|
|
1901
|
+
actions: input.input.actions
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* @zh 处理服务器同步数据进行校正
|
|
1906
|
+
* @en Process server sync data for reconciliation
|
|
1907
|
+
*/
|
|
1908
|
+
reconcileWithServer(data) {
|
|
1909
|
+
if (!this._prediction || this._localPlayerNetId < 0) return;
|
|
1910
|
+
const localState = data.entities.find((e) => e.netId === this._localPlayerNetId);
|
|
1911
|
+
if (!localState || !localState.pos) return;
|
|
1912
|
+
const serverState = {
|
|
1913
|
+
x: localState.pos.x,
|
|
1914
|
+
y: localState.pos.y,
|
|
1915
|
+
rotation: localState.rot ?? 0,
|
|
1916
|
+
velocityX: localState.vel?.x ?? 0,
|
|
1917
|
+
velocityY: localState.vel?.y ?? 0
|
|
1918
|
+
};
|
|
1919
|
+
if (data.ackSeq !== void 0) {
|
|
1920
|
+
this._prediction.reconcile(serverState, data.ackSeq, (state) => ({
|
|
1921
|
+
x: state.x,
|
|
1922
|
+
y: state.y
|
|
1923
|
+
}), Time2.deltaTime);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
process(entities) {
|
|
1927
|
+
if (!this._config.enabled || !this._prediction) return;
|
|
1928
|
+
const deltaTime = Time2.deltaTime;
|
|
1929
|
+
for (const entity of entities) {
|
|
1930
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
1931
|
+
if (!identity.bHasAuthority || identity.netId !== this._localPlayerNetId) continue;
|
|
1932
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
1933
|
+
const currentState = {
|
|
1934
|
+
x: transform.currentX,
|
|
1935
|
+
y: transform.currentY,
|
|
1936
|
+
rotation: transform.currentRotation,
|
|
1937
|
+
velocityX: 0,
|
|
1938
|
+
velocityY: 0
|
|
1939
|
+
};
|
|
1940
|
+
if (this._currentInput.x !== 0 || this._currentInput.y !== 0) {
|
|
1941
|
+
const predicted = this._prediction.recordInput(this._currentInput, currentState, deltaTime);
|
|
1942
|
+
transform.currentX = predicted.x;
|
|
1943
|
+
transform.currentY = predicted.y;
|
|
1944
|
+
transform.currentRotation = predicted.rotation;
|
|
1945
|
+
transform.targetX = predicted.x;
|
|
1946
|
+
transform.targetY = predicted.y;
|
|
1947
|
+
transform.targetRotation = predicted.rotation;
|
|
1948
|
+
this._inputSequence = this._prediction.currentSequence;
|
|
1949
|
+
}
|
|
1950
|
+
const offset = this._prediction.correctionOffset;
|
|
1951
|
+
if (Math.abs(offset.x) > 0.01 || Math.abs(offset.y) > 0.01) {
|
|
1952
|
+
transform.currentX += offset.x * deltaTime * 5;
|
|
1953
|
+
transform.currentY += offset.y * deltaTime * 5;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* @zh 重置预测状态
|
|
1959
|
+
* @en Reset prediction state
|
|
1960
|
+
*/
|
|
1961
|
+
reset() {
|
|
1962
|
+
this._prediction?.clear();
|
|
1963
|
+
this._inputSequence = 0;
|
|
1964
|
+
this._currentInput = {
|
|
1965
|
+
x: 0,
|
|
1966
|
+
y: 0
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
onDestroy() {
|
|
1970
|
+
this._prediction?.clear();
|
|
1971
|
+
this._prediction = null;
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
__name(_NetworkPredictionSystem, "NetworkPredictionSystem");
|
|
1975
|
+
var NetworkPredictionSystem = _NetworkPredictionSystem;
|
|
1976
|
+
function createNetworkPredictionSystem(config) {
|
|
1977
|
+
return new NetworkPredictionSystem(config);
|
|
1978
|
+
}
|
|
1979
|
+
__name(createNetworkPredictionSystem, "createNetworkPredictionSystem");
|
|
1980
|
+
|
|
1981
|
+
// src/systems/NetworkAOISystem.ts
|
|
1982
|
+
import { EntitySystem as EntitySystem6, Matcher as Matcher6 } from "@esengine/ecs-framework";
|
|
1983
|
+
var DEFAULT_CONFIG6 = {
|
|
1984
|
+
cellSize: 100,
|
|
1985
|
+
defaultViewRange: 500,
|
|
1986
|
+
enabled: true
|
|
1987
|
+
};
|
|
1988
|
+
var _NetworkAOISystem = class _NetworkAOISystem extends EntitySystem6 {
|
|
1989
|
+
constructor(config) {
|
|
1990
|
+
super(Matcher6.all(NetworkIdentity, NetworkTransform));
|
|
1991
|
+
__publicField(this, "_config");
|
|
1992
|
+
__publicField(this, "_observers", /* @__PURE__ */ new Map());
|
|
1993
|
+
__publicField(this, "_cells", /* @__PURE__ */ new Map());
|
|
1994
|
+
__publicField(this, "_listeners", /* @__PURE__ */ new Set());
|
|
1995
|
+
__publicField(this, "_entityNetIdMap", /* @__PURE__ */ new Map());
|
|
1996
|
+
__publicField(this, "_netIdEntityMap", /* @__PURE__ */ new Map());
|
|
1997
|
+
this._config = {
|
|
1998
|
+
...DEFAULT_CONFIG6,
|
|
1999
|
+
...config
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* @zh 获取配置
|
|
2004
|
+
* @en Get configuration
|
|
2005
|
+
*/
|
|
2006
|
+
get config() {
|
|
2007
|
+
return this._config;
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* @zh 是否启用
|
|
2011
|
+
* @en Is enabled
|
|
2012
|
+
*/
|
|
2013
|
+
get enabled() {
|
|
2014
|
+
return this._config.enabled;
|
|
2015
|
+
}
|
|
2016
|
+
set enabled(value) {
|
|
2017
|
+
this._config.enabled = value;
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* @zh 观察者数量
|
|
2021
|
+
* @en Observer count
|
|
2022
|
+
*/
|
|
2023
|
+
get observerCount() {
|
|
2024
|
+
return this._observers.size;
|
|
2025
|
+
}
|
|
2026
|
+
// =========================================================================
|
|
2027
|
+
// 观察者管理 | Observer Management
|
|
2028
|
+
// =========================================================================
|
|
2029
|
+
/**
|
|
2030
|
+
* @zh 添加观察者(通常是玩家实体)
|
|
2031
|
+
* @en Add observer (usually player entity)
|
|
2032
|
+
*/
|
|
2033
|
+
addObserver(netId, x, y, viewRange) {
|
|
2034
|
+
if (this._observers.has(netId)) {
|
|
2035
|
+
this.updateObserverPosition(netId, x, y);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
const range = viewRange ?? this._config.defaultViewRange;
|
|
2039
|
+
const cellKey = this._getCellKey(x, y);
|
|
2040
|
+
const data = {
|
|
2041
|
+
netId,
|
|
2042
|
+
position: {
|
|
632
2043
|
x,
|
|
633
2044
|
y
|
|
2045
|
+
},
|
|
2046
|
+
viewRange: range,
|
|
2047
|
+
viewRangeSq: range * range,
|
|
2048
|
+
cellKey,
|
|
2049
|
+
visibleEntities: /* @__PURE__ */ new Set()
|
|
2050
|
+
};
|
|
2051
|
+
this._observers.set(netId, data);
|
|
2052
|
+
this._addToCell(cellKey, netId);
|
|
2053
|
+
this._updateVisibility(data);
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* @zh 移除观察者
|
|
2057
|
+
* @en Remove observer
|
|
2058
|
+
*/
|
|
2059
|
+
removeObserver(netId) {
|
|
2060
|
+
const data = this._observers.get(netId);
|
|
2061
|
+
if (!data) return false;
|
|
2062
|
+
for (const visibleNetId of data.visibleEntities) {
|
|
2063
|
+
this._emitEvent({
|
|
2064
|
+
type: "exit",
|
|
2065
|
+
observerNetId: netId,
|
|
2066
|
+
targetNetId: visibleNetId
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
this._removeFromCell(data.cellKey, netId);
|
|
2070
|
+
this._observers.delete(netId);
|
|
2071
|
+
return true;
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* @zh 更新观察者位置
|
|
2075
|
+
* @en Update observer position
|
|
2076
|
+
*/
|
|
2077
|
+
updateObserverPosition(netId, x, y) {
|
|
2078
|
+
const data = this._observers.get(netId);
|
|
2079
|
+
if (!data) return;
|
|
2080
|
+
const newCellKey = this._getCellKey(x, y);
|
|
2081
|
+
if (newCellKey !== data.cellKey) {
|
|
2082
|
+
this._removeFromCell(data.cellKey, netId);
|
|
2083
|
+
data.cellKey = newCellKey;
|
|
2084
|
+
this._addToCell(newCellKey, netId);
|
|
2085
|
+
}
|
|
2086
|
+
data.position.x = x;
|
|
2087
|
+
data.position.y = y;
|
|
2088
|
+
this._updateVisibility(data);
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* @zh 更新观察者视野范围
|
|
2092
|
+
* @en Update observer view range
|
|
2093
|
+
*/
|
|
2094
|
+
updateObserverViewRange(netId, viewRange) {
|
|
2095
|
+
const data = this._observers.get(netId);
|
|
2096
|
+
if (!data) return;
|
|
2097
|
+
data.viewRange = viewRange;
|
|
2098
|
+
data.viewRangeSq = viewRange * viewRange;
|
|
2099
|
+
this._updateVisibility(data);
|
|
2100
|
+
}
|
|
2101
|
+
// =========================================================================
|
|
2102
|
+
// 实体管理 | Entity Management
|
|
2103
|
+
// =========================================================================
|
|
2104
|
+
/**
|
|
2105
|
+
* @zh 注册网络实体
|
|
2106
|
+
* @en Register network entity
|
|
2107
|
+
*/
|
|
2108
|
+
registerEntity(entity, netId) {
|
|
2109
|
+
this._entityNetIdMap.set(entity, netId);
|
|
2110
|
+
this._netIdEntityMap.set(netId, entity);
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* @zh 注销网络实体
|
|
2114
|
+
* @en Unregister network entity
|
|
2115
|
+
*/
|
|
2116
|
+
unregisterEntity(entity) {
|
|
2117
|
+
const netId = this._entityNetIdMap.get(entity);
|
|
2118
|
+
if (netId !== void 0) {
|
|
2119
|
+
for (const [, data] of this._observers) {
|
|
2120
|
+
if (data.visibleEntities.has(netId)) {
|
|
2121
|
+
data.visibleEntities.delete(netId);
|
|
2122
|
+
this._emitEvent({
|
|
2123
|
+
type: "exit",
|
|
2124
|
+
observerNetId: data.netId,
|
|
2125
|
+
targetNetId: netId
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
this._netIdEntityMap.delete(netId);
|
|
2130
|
+
}
|
|
2131
|
+
this._entityNetIdMap.delete(entity);
|
|
2132
|
+
}
|
|
2133
|
+
// =========================================================================
|
|
2134
|
+
// 查询接口 | Query Interface
|
|
2135
|
+
// =========================================================================
|
|
2136
|
+
/**
|
|
2137
|
+
* @zh 获取观察者能看到的实体网络 ID 列表
|
|
2138
|
+
* @en Get list of entity network IDs visible to observer
|
|
2139
|
+
*/
|
|
2140
|
+
getVisibleEntities(observerNetId) {
|
|
2141
|
+
const data = this._observers.get(observerNetId);
|
|
2142
|
+
return data ? Array.from(data.visibleEntities) : [];
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* @zh 获取能看到指定实体的观察者网络 ID 列表
|
|
2146
|
+
* @en Get list of observer network IDs that can see the entity
|
|
2147
|
+
*/
|
|
2148
|
+
getObserversOf(entityNetId) {
|
|
2149
|
+
const observers = [];
|
|
2150
|
+
for (const [, data] of this._observers) {
|
|
2151
|
+
if (data.visibleEntities.has(entityNetId)) {
|
|
2152
|
+
observers.push(data.netId);
|
|
634
2153
|
}
|
|
2154
|
+
}
|
|
2155
|
+
return observers;
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* @zh 检查观察者是否能看到目标
|
|
2159
|
+
* @en Check if observer can see target
|
|
2160
|
+
*/
|
|
2161
|
+
canSee(observerNetId, targetNetId) {
|
|
2162
|
+
const data = this._observers.get(observerNetId);
|
|
2163
|
+
return data?.visibleEntities.has(targetNetId) ?? false;
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* @zh 过滤同步数据,只保留观察者能看到的实体
|
|
2167
|
+
* @en Filter sync data to only include entities visible to observer
|
|
2168
|
+
*/
|
|
2169
|
+
filterSyncData(observerNetId, entities) {
|
|
2170
|
+
if (!this._config.enabled) {
|
|
2171
|
+
return entities;
|
|
2172
|
+
}
|
|
2173
|
+
const data = this._observers.get(observerNetId);
|
|
2174
|
+
if (!data) {
|
|
2175
|
+
return entities;
|
|
2176
|
+
}
|
|
2177
|
+
return entities.filter((entity) => {
|
|
2178
|
+
if (entity.netId === observerNetId) return true;
|
|
2179
|
+
return data.visibleEntities.has(entity.netId);
|
|
635
2180
|
});
|
|
636
2181
|
}
|
|
2182
|
+
// =========================================================================
|
|
2183
|
+
// 事件系统 | Event System
|
|
2184
|
+
// =========================================================================
|
|
637
2185
|
/**
|
|
638
|
-
* @zh
|
|
639
|
-
* @en Add
|
|
2186
|
+
* @zh 添加事件监听器
|
|
2187
|
+
* @en Add event listener
|
|
640
2188
|
*/
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
2189
|
+
addListener(listener) {
|
|
2190
|
+
this._listeners.add(listener);
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* @zh 移除事件监听器
|
|
2194
|
+
* @en Remove event listener
|
|
2195
|
+
*/
|
|
2196
|
+
removeListener(listener) {
|
|
2197
|
+
this._listeners.delete(listener);
|
|
2198
|
+
}
|
|
2199
|
+
// =========================================================================
|
|
2200
|
+
// 系统生命周期 | System Lifecycle
|
|
2201
|
+
// =========================================================================
|
|
2202
|
+
process(entities) {
|
|
2203
|
+
if (!this._config.enabled) return;
|
|
2204
|
+
for (const entity of entities) {
|
|
2205
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
2206
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
2207
|
+
if (!this._entityNetIdMap.has(entity)) {
|
|
2208
|
+
this.registerEntity(entity, identity.netId);
|
|
2209
|
+
}
|
|
2210
|
+
if (identity.bHasAuthority && this._observers.has(identity.netId)) {
|
|
2211
|
+
this.updateObserverPosition(identity.netId, transform.currentX, transform.currentY);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
this._updateAllObserversVisibility(entities);
|
|
2215
|
+
}
|
|
2216
|
+
_updateAllObserversVisibility(entities) {
|
|
2217
|
+
for (const [, data] of this._observers) {
|
|
2218
|
+
const newVisible = /* @__PURE__ */ new Set();
|
|
2219
|
+
for (const entity of entities) {
|
|
2220
|
+
const identity = this.requireComponent(entity, NetworkIdentity);
|
|
2221
|
+
const transform = this.requireComponent(entity, NetworkTransform);
|
|
2222
|
+
if (identity.netId === data.netId) continue;
|
|
2223
|
+
const dx = transform.currentX - data.position.x;
|
|
2224
|
+
const dy = transform.currentY - data.position.y;
|
|
2225
|
+
const distSq = dx * dx + dy * dy;
|
|
2226
|
+
if (distSq <= data.viewRangeSq) {
|
|
2227
|
+
newVisible.add(identity.netId);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
for (const netId of newVisible) {
|
|
2231
|
+
if (!data.visibleEntities.has(netId)) {
|
|
2232
|
+
this._emitEvent({
|
|
2233
|
+
type: "enter",
|
|
2234
|
+
observerNetId: data.netId,
|
|
2235
|
+
targetNetId: netId
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
for (const netId of data.visibleEntities) {
|
|
2240
|
+
if (!newVisible.has(netId)) {
|
|
2241
|
+
this._emitEvent({
|
|
2242
|
+
type: "exit",
|
|
2243
|
+
observerNetId: data.netId,
|
|
2244
|
+
targetNetId: netId
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
data.visibleEntities = newVisible;
|
|
653
2249
|
}
|
|
654
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* @zh 清除所有数据
|
|
2253
|
+
* @en Clear all data
|
|
2254
|
+
*/
|
|
2255
|
+
clear() {
|
|
2256
|
+
this._observers.clear();
|
|
2257
|
+
this._cells.clear();
|
|
2258
|
+
this._entityNetIdMap.clear();
|
|
2259
|
+
this._netIdEntityMap.clear();
|
|
2260
|
+
}
|
|
655
2261
|
onDestroy() {
|
|
656
|
-
this.
|
|
2262
|
+
this.clear();
|
|
2263
|
+
this._listeners.clear();
|
|
2264
|
+
}
|
|
2265
|
+
// =========================================================================
|
|
2266
|
+
// 私有方法 | Private Methods
|
|
2267
|
+
// =========================================================================
|
|
2268
|
+
_getCellKey(x, y) {
|
|
2269
|
+
const cellX = Math.floor(x / this._config.cellSize);
|
|
2270
|
+
const cellY = Math.floor(y / this._config.cellSize);
|
|
2271
|
+
return `${cellX},${cellY}`;
|
|
2272
|
+
}
|
|
2273
|
+
_addToCell(cellKey, netId) {
|
|
2274
|
+
let cell = this._cells.get(cellKey);
|
|
2275
|
+
if (!cell) {
|
|
2276
|
+
cell = /* @__PURE__ */ new Set();
|
|
2277
|
+
this._cells.set(cellKey, cell);
|
|
2278
|
+
}
|
|
2279
|
+
cell.add(netId);
|
|
2280
|
+
}
|
|
2281
|
+
_removeFromCell(cellKey, netId) {
|
|
2282
|
+
const cell = this._cells.get(cellKey);
|
|
2283
|
+
if (cell) {
|
|
2284
|
+
cell.delete(netId);
|
|
2285
|
+
if (cell.size === 0) {
|
|
2286
|
+
this._cells.delete(cellKey);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
_updateVisibility(data) {
|
|
2291
|
+
}
|
|
2292
|
+
_emitEvent(event) {
|
|
2293
|
+
for (const listener of this._listeners) {
|
|
2294
|
+
try {
|
|
2295
|
+
listener(event);
|
|
2296
|
+
} catch (e) {
|
|
2297
|
+
console.error("[NetworkAOISystem] Listener error:", e);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
657
2300
|
}
|
|
658
2301
|
};
|
|
659
|
-
__name(
|
|
660
|
-
var
|
|
2302
|
+
__name(_NetworkAOISystem, "NetworkAOISystem");
|
|
2303
|
+
var NetworkAOISystem = _NetworkAOISystem;
|
|
2304
|
+
function createNetworkAOISystem(config) {
|
|
2305
|
+
return new NetworkAOISystem(config);
|
|
2306
|
+
}
|
|
2307
|
+
__name(createNetworkAOISystem, "createNetworkAOISystem");
|
|
661
2308
|
|
|
662
2309
|
// src/NetworkPlugin.ts
|
|
2310
|
+
var DEFAULT_CONFIG7 = {
|
|
2311
|
+
enablePrediction: true,
|
|
2312
|
+
enableAutoReconnect: true,
|
|
2313
|
+
maxReconnectAttempts: 5,
|
|
2314
|
+
reconnectInterval: 2e3,
|
|
2315
|
+
enableAOI: false
|
|
2316
|
+
};
|
|
663
2317
|
var _NetworkPlugin = class _NetworkPlugin {
|
|
664
|
-
constructor() {
|
|
2318
|
+
constructor(config) {
|
|
665
2319
|
__publicField(this, "name", "@esengine/network");
|
|
666
|
-
__publicField(this, "version", "2.
|
|
2320
|
+
__publicField(this, "version", "2.1.0");
|
|
2321
|
+
__publicField(this, "_config");
|
|
667
2322
|
__publicField(this, "_networkService");
|
|
668
2323
|
__publicField(this, "_syncSystem");
|
|
669
2324
|
__publicField(this, "_spawnSystem");
|
|
670
2325
|
__publicField(this, "_inputSystem");
|
|
2326
|
+
__publicField(this, "_predictionSystem", null);
|
|
2327
|
+
__publicField(this, "_aoiSystem", null);
|
|
671
2328
|
__publicField(this, "_localPlayerId", 0);
|
|
2329
|
+
__publicField(this, "_reconnectState", null);
|
|
2330
|
+
__publicField(this, "_reconnectTimer", null);
|
|
2331
|
+
__publicField(this, "_lastConnectOptions", null);
|
|
2332
|
+
this._config = {
|
|
2333
|
+
...DEFAULT_CONFIG7,
|
|
2334
|
+
...config
|
|
2335
|
+
};
|
|
672
2336
|
}
|
|
2337
|
+
// =========================================================================
|
|
2338
|
+
// Getters | 属性访问器
|
|
2339
|
+
// =========================================================================
|
|
673
2340
|
/**
|
|
674
2341
|
* @zh 网络服务
|
|
675
2342
|
* @en Network service
|
|
@@ -698,6 +2365,20 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
698
2365
|
get inputSystem() {
|
|
699
2366
|
return this._inputSystem;
|
|
700
2367
|
}
|
|
2368
|
+
/**
|
|
2369
|
+
* @zh 预测系统
|
|
2370
|
+
* @en Prediction system
|
|
2371
|
+
*/
|
|
2372
|
+
get predictionSystem() {
|
|
2373
|
+
return this._predictionSystem;
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* @zh AOI 系统
|
|
2377
|
+
* @en AOI system
|
|
2378
|
+
*/
|
|
2379
|
+
get aoiSystem() {
|
|
2380
|
+
return this._aoiSystem;
|
|
2381
|
+
}
|
|
701
2382
|
/**
|
|
702
2383
|
* @zh 本地玩家 ID
|
|
703
2384
|
* @en Local player ID
|
|
@@ -712,6 +2393,30 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
712
2393
|
get isConnected() {
|
|
713
2394
|
return this._networkService?.isConnected ?? false;
|
|
714
2395
|
}
|
|
2396
|
+
/**
|
|
2397
|
+
* @zh 是否正在重连
|
|
2398
|
+
* @en Is reconnecting
|
|
2399
|
+
*/
|
|
2400
|
+
get isReconnecting() {
|
|
2401
|
+
return this._reconnectState?.isReconnecting ?? false;
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* @zh 是否启用预测
|
|
2405
|
+
* @en Is prediction enabled
|
|
2406
|
+
*/
|
|
2407
|
+
get isPredictionEnabled() {
|
|
2408
|
+
return this._config.enablePrediction && this._predictionSystem !== null;
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* @zh 是否启用 AOI
|
|
2412
|
+
* @en Is AOI enabled
|
|
2413
|
+
*/
|
|
2414
|
+
get isAOIEnabled() {
|
|
2415
|
+
return this._config.enableAOI && this._aoiSystem !== null;
|
|
2416
|
+
}
|
|
2417
|
+
// =========================================================================
|
|
2418
|
+
// Plugin Lifecycle | 插件生命周期
|
|
2419
|
+
// =========================================================================
|
|
715
2420
|
/**
|
|
716
2421
|
* @zh 安装插件
|
|
717
2422
|
* @en Install plugin
|
|
@@ -728,12 +2433,22 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
728
2433
|
* @en Uninstall plugin
|
|
729
2434
|
*/
|
|
730
2435
|
uninstall() {
|
|
2436
|
+
this._clearReconnectTimer();
|
|
731
2437
|
this._networkService?.disconnect();
|
|
732
2438
|
}
|
|
733
2439
|
_setupSystems(scene) {
|
|
734
|
-
this._syncSystem = new NetworkSyncSystem();
|
|
2440
|
+
this._syncSystem = new NetworkSyncSystem(this._config.syncConfig);
|
|
735
2441
|
this._spawnSystem = new NetworkSpawnSystem(this._syncSystem);
|
|
736
|
-
this._inputSystem = new NetworkInputSystem(this._networkService);
|
|
2442
|
+
this._inputSystem = new NetworkInputSystem(this._networkService, this._config.inputConfig);
|
|
2443
|
+
if (this._config.enablePrediction) {
|
|
2444
|
+
this._predictionSystem = new NetworkPredictionSystem(this._config.predictionConfig);
|
|
2445
|
+
this._inputSystem.setPredictionSystem(this._predictionSystem);
|
|
2446
|
+
scene.addSystem(this._predictionSystem);
|
|
2447
|
+
}
|
|
2448
|
+
if (this._config.enableAOI) {
|
|
2449
|
+
this._aoiSystem = new NetworkAOISystem(this._config.aoiConfig);
|
|
2450
|
+
scene.addSystem(this._aoiSystem);
|
|
2451
|
+
}
|
|
737
2452
|
scene.addSystem(this._syncSystem);
|
|
738
2453
|
scene.addSystem(this._spawnSystem);
|
|
739
2454
|
scene.addSystem(this._inputSystem);
|
|
@@ -741,21 +2456,34 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
741
2456
|
}
|
|
742
2457
|
_setupMessageHandlers() {
|
|
743
2458
|
this._networkService.onSync((data) => {
|
|
744
|
-
this._syncSystem.
|
|
745
|
-
|
|
746
|
-
|
|
2459
|
+
this._syncSystem.handleSyncData(data);
|
|
2460
|
+
if (this._predictionSystem) {
|
|
2461
|
+
this._predictionSystem.reconcileWithServer(data);
|
|
2462
|
+
}
|
|
747
2463
|
}).onSpawn((data) => {
|
|
748
2464
|
this._spawnSystem.handleSpawn(data);
|
|
749
2465
|
}).onDespawn((data) => {
|
|
750
2466
|
this._spawnSystem.handleDespawn(data);
|
|
751
2467
|
});
|
|
2468
|
+
this._networkService.on("fullState", (data) => {
|
|
2469
|
+
this._handleFullState(data);
|
|
2470
|
+
});
|
|
752
2471
|
}
|
|
2472
|
+
// =========================================================================
|
|
2473
|
+
// Connection | 连接管理
|
|
2474
|
+
// =========================================================================
|
|
753
2475
|
/**
|
|
754
2476
|
* @zh 连接到服务器
|
|
755
2477
|
* @en Connect to server
|
|
756
2478
|
*/
|
|
757
2479
|
async connect(options) {
|
|
2480
|
+
this._lastConnectOptions = options;
|
|
758
2481
|
try {
|
|
2482
|
+
const originalOnDisconnect = options.onDisconnect;
|
|
2483
|
+
options.onDisconnect = (reason) => {
|
|
2484
|
+
originalOnDisconnect?.(reason);
|
|
2485
|
+
this._handleDisconnect(reason);
|
|
2486
|
+
};
|
|
759
2487
|
await this._networkService.connect(options);
|
|
760
2488
|
const result = await this._networkService.call("join", {
|
|
761
2489
|
playerName: options.playerName,
|
|
@@ -763,8 +2491,20 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
763
2491
|
});
|
|
764
2492
|
this._localPlayerId = result.playerId;
|
|
765
2493
|
this._spawnSystem.setLocalPlayerId(this._localPlayerId);
|
|
2494
|
+
if (this._predictionSystem) {
|
|
2495
|
+
}
|
|
2496
|
+
if (this._config.enableAutoReconnect) {
|
|
2497
|
+
this._reconnectState = {
|
|
2498
|
+
token: this._generateReconnectToken(),
|
|
2499
|
+
playerId: result.playerId,
|
|
2500
|
+
roomId: result.roomId,
|
|
2501
|
+
attempts: 0,
|
|
2502
|
+
isReconnecting: false
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
766
2505
|
return true;
|
|
767
2506
|
} catch (err) {
|
|
2507
|
+
console.error("[NetworkPlugin] Connection failed:", err);
|
|
768
2508
|
return false;
|
|
769
2509
|
}
|
|
770
2510
|
}
|
|
@@ -773,386 +2513,181 @@ var _NetworkPlugin = class _NetworkPlugin {
|
|
|
773
2513
|
* @en Disconnect
|
|
774
2514
|
*/
|
|
775
2515
|
async disconnect() {
|
|
2516
|
+
this._clearReconnectTimer();
|
|
2517
|
+
this._reconnectState = null;
|
|
776
2518
|
try {
|
|
777
2519
|
await this._networkService.call("leave", void 0);
|
|
778
2520
|
} catch {
|
|
779
2521
|
}
|
|
780
2522
|
this._networkService.disconnect();
|
|
2523
|
+
this._cleanup();
|
|
781
2524
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
registerPrefab(prefabType, factory) {
|
|
787
|
-
this._spawnSystem?.registerPrefab(prefabType, factory);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* @zh 发送移动输入
|
|
791
|
-
* @en Send move input
|
|
792
|
-
*/
|
|
793
|
-
sendMoveInput(x, y) {
|
|
794
|
-
this._inputSystem?.addMoveInput(x, y);
|
|
795
|
-
}
|
|
796
|
-
/**
|
|
797
|
-
* @zh 发送动作输入
|
|
798
|
-
* @en Send action input
|
|
799
|
-
*/
|
|
800
|
-
sendActionInput(action) {
|
|
801
|
-
this._inputSystem?.addActionInput(action);
|
|
802
|
-
}
|
|
803
|
-
};
|
|
804
|
-
__name(_NetworkPlugin, "NetworkPlugin");
|
|
805
|
-
var NetworkPlugin = _NetworkPlugin;
|
|
806
|
-
|
|
807
|
-
// src/sync/SnapshotBuffer.ts
|
|
808
|
-
var _SnapshotBuffer = class _SnapshotBuffer {
|
|
809
|
-
constructor(config) {
|
|
810
|
-
__publicField(this, "_buffer", []);
|
|
811
|
-
__publicField(this, "_maxSize");
|
|
812
|
-
__publicField(this, "_interpolationDelay");
|
|
813
|
-
this._maxSize = config.maxSize;
|
|
814
|
-
this._interpolationDelay = config.interpolationDelay;
|
|
815
|
-
}
|
|
816
|
-
get size() {
|
|
817
|
-
return this._buffer.length;
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* @zh 获取插值延迟
|
|
821
|
-
* @en Get interpolation delay
|
|
822
|
-
*/
|
|
823
|
-
get interpolationDelay() {
|
|
824
|
-
return this._interpolationDelay;
|
|
825
|
-
}
|
|
826
|
-
/**
|
|
827
|
-
* @zh 添加快照
|
|
828
|
-
* @en Add snapshot
|
|
829
|
-
*/
|
|
830
|
-
push(snapshot) {
|
|
831
|
-
let insertIndex = this._buffer.length;
|
|
832
|
-
for (let i = this._buffer.length - 1; i >= 0; i--) {
|
|
833
|
-
if (this._buffer[i].timestamp <= snapshot.timestamp) {
|
|
834
|
-
insertIndex = i + 1;
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
if (i === 0) {
|
|
838
|
-
insertIndex = 0;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
this._buffer.splice(insertIndex, 0, snapshot);
|
|
842
|
-
while (this._buffer.length > this._maxSize) {
|
|
843
|
-
this._buffer.shift();
|
|
2525
|
+
_handleDisconnect(reason) {
|
|
2526
|
+
console.log("[NetworkPlugin] Disconnected:", reason);
|
|
2527
|
+
if (this._config.enableAutoReconnect && this._reconnectState && !this._reconnectState.isReconnecting) {
|
|
2528
|
+
this._attemptReconnect();
|
|
844
2529
|
}
|
|
845
2530
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return null;
|
|
2531
|
+
_attemptReconnect() {
|
|
2532
|
+
if (!this._reconnectState || !this._lastConnectOptions) return;
|
|
2533
|
+
if (this._reconnectState.attempts >= this._config.maxReconnectAttempts) {
|
|
2534
|
+
console.error("[NetworkPlugin] Max reconnection attempts reached");
|
|
2535
|
+
this._reconnectState = null;
|
|
2536
|
+
return;
|
|
853
2537
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
2538
|
+
this._reconnectState.isReconnecting = true;
|
|
2539
|
+
this._reconnectState.attempts++;
|
|
2540
|
+
console.log(`[NetworkPlugin] Attempting reconnection (${this._reconnectState.attempts}/${this._config.maxReconnectAttempts})`);
|
|
2541
|
+
this._reconnectTimer = setTimeout(async () => {
|
|
2542
|
+
try {
|
|
2543
|
+
await this._networkService.connect(this._lastConnectOptions);
|
|
2544
|
+
const result = await this._networkService.call("reconnect", {
|
|
2545
|
+
playerId: this._reconnectState.playerId,
|
|
2546
|
+
roomId: this._reconnectState.roomId,
|
|
2547
|
+
token: this._reconnectState.token
|
|
2548
|
+
});
|
|
2549
|
+
if (result.success) {
|
|
2550
|
+
console.log("[NetworkPlugin] Reconnection successful");
|
|
2551
|
+
this._reconnectState.isReconnecting = false;
|
|
2552
|
+
this._reconnectState.attempts = 0;
|
|
2553
|
+
if (result.state) {
|
|
2554
|
+
this._handleFullState(result.state);
|
|
2555
|
+
}
|
|
2556
|
+
} else {
|
|
2557
|
+
console.error("[NetworkPlugin] Reconnection rejected:", result.error);
|
|
2558
|
+
this._attemptReconnect();
|
|
2559
|
+
}
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
console.error("[NetworkPlugin] Reconnection failed:", err);
|
|
2562
|
+
if (this._reconnectState) {
|
|
2563
|
+
this._reconnectState.isReconnecting = false;
|
|
2564
|
+
}
|
|
2565
|
+
this._attemptReconnect();
|
|
2566
|
+
}
|
|
2567
|
+
}, this._config.reconnectInterval);
|
|
2568
|
+
}
|
|
2569
|
+
_handleFullState(data) {
|
|
2570
|
+
this._syncSystem.clearSnapshots();
|
|
2571
|
+
for (const entityData of data.entities) {
|
|
2572
|
+
this._spawnSystem.handleSpawn(entityData);
|
|
2573
|
+
if (entityData.state) {
|
|
2574
|
+
this._syncSystem.handleSyncData({
|
|
2575
|
+
frame: data.frame,
|
|
2576
|
+
timestamp: data.timestamp,
|
|
2577
|
+
entities: [
|
|
2578
|
+
entityData.state
|
|
2579
|
+
]
|
|
2580
|
+
});
|
|
866
2581
|
}
|
|
867
2582
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
return [
|
|
874
|
-
prev,
|
|
875
|
-
next,
|
|
876
|
-
Math.min(t, 2)
|
|
877
|
-
];
|
|
2583
|
+
}
|
|
2584
|
+
_clearReconnectTimer() {
|
|
2585
|
+
if (this._reconnectTimer) {
|
|
2586
|
+
clearTimeout(this._reconnectTimer);
|
|
2587
|
+
this._reconnectTimer = null;
|
|
878
2588
|
}
|
|
879
|
-
return null;
|
|
880
2589
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
* @en Get latest snapshot
|
|
884
|
-
*/
|
|
885
|
-
getLatest() {
|
|
886
|
-
return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
|
|
2590
|
+
_generateReconnectToken() {
|
|
2591
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
887
2592
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
return this._buffer.filter((s) => s.timestamp > timestamp);
|
|
2593
|
+
_cleanup() {
|
|
2594
|
+
this._localPlayerId = 0;
|
|
2595
|
+
this._syncSystem?.clearSnapshots();
|
|
2596
|
+
this._predictionSystem?.reset();
|
|
2597
|
+
this._inputSystem?.reset();
|
|
894
2598
|
}
|
|
2599
|
+
// =========================================================================
|
|
2600
|
+
// Game API | 游戏接口
|
|
2601
|
+
// =========================================================================
|
|
895
2602
|
/**
|
|
896
|
-
* @zh
|
|
897
|
-
* @en
|
|
2603
|
+
* @zh 注册预制体工厂
|
|
2604
|
+
* @en Register prefab factory
|
|
898
2605
|
*/
|
|
899
|
-
|
|
900
|
-
this.
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
__name(_SnapshotBuffer, "SnapshotBuffer");
|
|
904
|
-
var SnapshotBuffer = _SnapshotBuffer;
|
|
905
|
-
function createSnapshotBuffer(maxSize = 30, interpolationDelay = 100) {
|
|
906
|
-
return new SnapshotBuffer({
|
|
907
|
-
maxSize,
|
|
908
|
-
interpolationDelay
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
__name(createSnapshotBuffer, "createSnapshotBuffer");
|
|
912
|
-
|
|
913
|
-
// src/sync/IInterpolator.ts
|
|
914
|
-
function lerp(a, b, t) {
|
|
915
|
-
return a + (b - a) * t;
|
|
916
|
-
}
|
|
917
|
-
__name(lerp, "lerp");
|
|
918
|
-
function lerpAngle(a, b, t) {
|
|
919
|
-
let diff = b - a;
|
|
920
|
-
while (diff > Math.PI) diff -= Math.PI * 2;
|
|
921
|
-
while (diff < -Math.PI) diff += Math.PI * 2;
|
|
922
|
-
return a + diff * t;
|
|
923
|
-
}
|
|
924
|
-
__name(lerpAngle, "lerpAngle");
|
|
925
|
-
function smoothDamp(current, target, velocity, smoothTime, deltaTime, maxSpeed = Infinity) {
|
|
926
|
-
smoothTime = Math.max(1e-4, smoothTime);
|
|
927
|
-
const omega = 2 / smoothTime;
|
|
928
|
-
const x = omega * deltaTime;
|
|
929
|
-
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
|
|
930
|
-
let change = current - target;
|
|
931
|
-
const maxChange = maxSpeed * smoothTime;
|
|
932
|
-
change = Math.max(-maxChange, Math.min(maxChange, change));
|
|
933
|
-
const temp = (velocity + omega * change) * deltaTime;
|
|
934
|
-
let newVelocity = (velocity - omega * temp) * exp;
|
|
935
|
-
let newValue = target + (change + temp) * exp;
|
|
936
|
-
if (target - current > 0 === newValue > target) {
|
|
937
|
-
newValue = target;
|
|
938
|
-
newVelocity = (newValue - target) / deltaTime;
|
|
2606
|
+
registerPrefab(prefabType, factory) {
|
|
2607
|
+
this._spawnSystem?.registerPrefab(prefabType, factory);
|
|
939
2608
|
}
|
|
940
|
-
return [
|
|
941
|
-
newValue,
|
|
942
|
-
newVelocity
|
|
943
|
-
];
|
|
944
|
-
}
|
|
945
|
-
__name(smoothDamp, "smoothDamp");
|
|
946
|
-
|
|
947
|
-
// src/sync/TransformInterpolator.ts
|
|
948
|
-
var _TransformInterpolator = class _TransformInterpolator {
|
|
949
2609
|
/**
|
|
950
|
-
* @zh
|
|
951
|
-
* @en
|
|
2610
|
+
* @zh 发送移动输入
|
|
2611
|
+
* @en Send move input
|
|
952
2612
|
*/
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
x: lerp(from.x, to.x, t),
|
|
956
|
-
y: lerp(from.y, to.y, t),
|
|
957
|
-
rotation: lerpAngle(from.rotation, to.rotation, t)
|
|
958
|
-
};
|
|
2613
|
+
sendMoveInput(x, y) {
|
|
2614
|
+
this._inputSystem?.addMoveInput(x, y);
|
|
959
2615
|
}
|
|
960
2616
|
/**
|
|
961
|
-
* @zh
|
|
962
|
-
* @en
|
|
2617
|
+
* @zh 发送动作输入
|
|
2618
|
+
* @en Send action input
|
|
963
2619
|
*/
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
x: state.x + state.velocityX * deltaTime,
|
|
967
|
-
y: state.y + state.velocityY * deltaTime,
|
|
968
|
-
rotation: state.rotation + state.angularVelocity * deltaTime,
|
|
969
|
-
velocityX: state.velocityX,
|
|
970
|
-
velocityY: state.velocityY,
|
|
971
|
-
angularVelocity: state.angularVelocity
|
|
972
|
-
};
|
|
2620
|
+
sendActionInput(action) {
|
|
2621
|
+
this._inputSystem?.addActionInput(action);
|
|
973
2622
|
}
|
|
974
|
-
};
|
|
975
|
-
__name(_TransformInterpolator, "TransformInterpolator");
|
|
976
|
-
var TransformInterpolator = _TransformInterpolator;
|
|
977
|
-
var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
|
|
978
2623
|
/**
|
|
979
|
-
* @zh
|
|
980
|
-
* @en
|
|
2624
|
+
* @zh 设置本地玩家网络 ID(用于预测)
|
|
2625
|
+
* @en Set local player network ID (for prediction)
|
|
981
2626
|
*/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const h10 = t3 - 2 * t2 + t;
|
|
987
|
-
const h01 = -2 * t3 + 3 * t2;
|
|
988
|
-
const h11 = t3 - t2;
|
|
989
|
-
const dt = 0.1;
|
|
990
|
-
const x = h00 * from.x + h10 * from.velocityX * dt + h01 * to.x + h11 * to.velocityX * dt;
|
|
991
|
-
const y = h00 * from.y + h10 * from.velocityY * dt + h01 * to.y + h11 * to.velocityY * dt;
|
|
992
|
-
const dh00 = 6 * t2 - 6 * t;
|
|
993
|
-
const dh10 = 3 * t2 - 4 * t + 1;
|
|
994
|
-
const dh01 = -6 * t2 + 6 * t;
|
|
995
|
-
const dh11 = 3 * t2 - 2 * t;
|
|
996
|
-
const velocityX = (dh00 * from.x + dh10 * from.velocityX * dt + dh01 * to.x + dh11 * to.velocityX * dt) / dt;
|
|
997
|
-
const velocityY = (dh00 * from.y + dh10 * from.velocityY * dt + dh01 * to.y + dh11 * to.velocityY * dt) / dt;
|
|
998
|
-
return {
|
|
999
|
-
x,
|
|
1000
|
-
y,
|
|
1001
|
-
rotation: lerpAngle(from.rotation, to.rotation, t),
|
|
1002
|
-
velocityX,
|
|
1003
|
-
velocityY,
|
|
1004
|
-
angularVelocity: lerp(from.angularVelocity, to.angularVelocity, t)
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
__name(_HermiteTransformInterpolator, "HermiteTransformInterpolator");
|
|
1009
|
-
var HermiteTransformInterpolator = _HermiteTransformInterpolator;
|
|
1010
|
-
function createTransformInterpolator() {
|
|
1011
|
-
return new TransformInterpolator();
|
|
1012
|
-
}
|
|
1013
|
-
__name(createTransformInterpolator, "createTransformInterpolator");
|
|
1014
|
-
function createHermiteTransformInterpolator() {
|
|
1015
|
-
return new HermiteTransformInterpolator();
|
|
1016
|
-
}
|
|
1017
|
-
__name(createHermiteTransformInterpolator, "createHermiteTransformInterpolator");
|
|
1018
|
-
|
|
1019
|
-
// src/sync/ClientPrediction.ts
|
|
1020
|
-
var _ClientPrediction = class _ClientPrediction {
|
|
1021
|
-
constructor(predictor, config) {
|
|
1022
|
-
__publicField(this, "_predictor");
|
|
1023
|
-
__publicField(this, "_config");
|
|
1024
|
-
__publicField(this, "_pendingInputs", []);
|
|
1025
|
-
__publicField(this, "_lastAcknowledgedSequence", 0);
|
|
1026
|
-
__publicField(this, "_currentSequence", 0);
|
|
1027
|
-
__publicField(this, "_lastServerState", null);
|
|
1028
|
-
__publicField(this, "_predictedState", null);
|
|
1029
|
-
__publicField(this, "_correctionOffset", {
|
|
1030
|
-
x: 0,
|
|
1031
|
-
y: 0
|
|
1032
|
-
});
|
|
1033
|
-
this._predictor = predictor;
|
|
1034
|
-
this._config = {
|
|
1035
|
-
maxUnacknowledgedInputs: 60,
|
|
1036
|
-
reconciliationThreshold: 0.1,
|
|
1037
|
-
reconciliationSpeed: 10,
|
|
1038
|
-
...config
|
|
1039
|
-
};
|
|
2627
|
+
setLocalPlayerNetId(netId) {
|
|
2628
|
+
if (this._predictionSystem) {
|
|
2629
|
+
this._predictionSystem.setLocalPlayerNetId(netId);
|
|
2630
|
+
}
|
|
1040
2631
|
}
|
|
1041
2632
|
/**
|
|
1042
|
-
* @zh
|
|
1043
|
-
* @en
|
|
2633
|
+
* @zh 启用/禁用预测
|
|
2634
|
+
* @en Enable/disable prediction
|
|
1044
2635
|
*/
|
|
1045
|
-
|
|
1046
|
-
|
|
2636
|
+
setPredictionEnabled(enabled) {
|
|
2637
|
+
if (this._predictionSystem) {
|
|
2638
|
+
this._predictionSystem.enabled = enabled;
|
|
2639
|
+
}
|
|
1047
2640
|
}
|
|
2641
|
+
// =========================================================================
|
|
2642
|
+
// AOI API | AOI 接口
|
|
2643
|
+
// =========================================================================
|
|
1048
2644
|
/**
|
|
1049
|
-
* @zh
|
|
1050
|
-
* @en
|
|
2645
|
+
* @zh 添加 AOI 观察者(玩家)
|
|
2646
|
+
* @en Add AOI observer (player)
|
|
1051
2647
|
*/
|
|
1052
|
-
|
|
1053
|
-
|
|
2648
|
+
addAOIObserver(netId, x, y, viewRange) {
|
|
2649
|
+
this._aoiSystem?.addObserver(netId, x, y, viewRange);
|
|
1054
2650
|
}
|
|
1055
2651
|
/**
|
|
1056
|
-
* @zh
|
|
1057
|
-
* @en
|
|
2652
|
+
* @zh 移除 AOI 观察者
|
|
2653
|
+
* @en Remove AOI observer
|
|
1058
2654
|
*/
|
|
1059
|
-
|
|
1060
|
-
|
|
2655
|
+
removeAOIObserver(netId) {
|
|
2656
|
+
this._aoiSystem?.removeObserver(netId);
|
|
1061
2657
|
}
|
|
1062
2658
|
/**
|
|
1063
|
-
* @zh
|
|
1064
|
-
* @en
|
|
1065
|
-
*
|
|
1066
|
-
* @param input - @zh 输入数据 @en Input data
|
|
1067
|
-
* @param currentState - @zh 当前状态 @en Current state
|
|
1068
|
-
* @param deltaTime - @zh 时间间隔 @en Delta time
|
|
1069
|
-
* @returns @zh 预测的状态 @en Predicted state
|
|
2659
|
+
* @zh 更新 AOI 观察者位置
|
|
2660
|
+
* @en Update AOI observer position
|
|
1070
2661
|
*/
|
|
1071
|
-
|
|
1072
|
-
this.
|
|
1073
|
-
const inputSnapshot = {
|
|
1074
|
-
sequence: this._currentSequence,
|
|
1075
|
-
input,
|
|
1076
|
-
timestamp: Date.now()
|
|
1077
|
-
};
|
|
1078
|
-
this._pendingInputs.push(inputSnapshot);
|
|
1079
|
-
while (this._pendingInputs.length > this._config.maxUnacknowledgedInputs) {
|
|
1080
|
-
this._pendingInputs.shift();
|
|
1081
|
-
}
|
|
1082
|
-
this._predictedState = this._predictor.predict(currentState, input, deltaTime);
|
|
1083
|
-
return this._predictedState;
|
|
2662
|
+
updateAOIObserverPosition(netId, x, y) {
|
|
2663
|
+
this._aoiSystem?.updateObserverPosition(netId, x, y);
|
|
1084
2664
|
}
|
|
1085
2665
|
/**
|
|
1086
|
-
* @zh
|
|
1087
|
-
* @en Get
|
|
2666
|
+
* @zh 获取观察者可见的实体
|
|
2667
|
+
* @en Get entities visible to observer
|
|
1088
2668
|
*/
|
|
1089
|
-
|
|
1090
|
-
return this.
|
|
2669
|
+
getVisibleEntities(observerNetId) {
|
|
2670
|
+
return this._aoiSystem?.getVisibleEntities(observerNetId) ?? [];
|
|
1091
2671
|
}
|
|
1092
2672
|
/**
|
|
1093
|
-
* @zh
|
|
1094
|
-
* @en
|
|
2673
|
+
* @zh 检查是否可见
|
|
2674
|
+
* @en Check if visible
|
|
1095
2675
|
*/
|
|
1096
|
-
|
|
1097
|
-
return this.
|
|
2676
|
+
canSee(observerNetId, targetNetId) {
|
|
2677
|
+
return this._aoiSystem?.canSee(observerNetId, targetNetId) ?? true;
|
|
1098
2678
|
}
|
|
1099
2679
|
/**
|
|
1100
|
-
* @zh
|
|
1101
|
-
* @en
|
|
1102
|
-
*
|
|
1103
|
-
* @param serverState - @zh 服务器状态 @en Server state
|
|
1104
|
-
* @param acknowledgedSequence - @zh 已确认的输入序列号 @en Acknowledged input sequence
|
|
1105
|
-
* @param stateGetter - @zh 获取状态位置的函数 @en Function to get state position
|
|
1106
|
-
* @param deltaTime - @zh 帧时间 @en Frame delta time
|
|
2680
|
+
* @zh 启用/禁用 AOI
|
|
2681
|
+
* @en Enable/disable AOI
|
|
1107
2682
|
*/
|
|
1108
|
-
|
|
1109
|
-
this.
|
|
1110
|
-
|
|
1111
|
-
while (this._pendingInputs.length > 0 && this._pendingInputs[0].sequence <= acknowledgedSequence) {
|
|
1112
|
-
this._pendingInputs.shift();
|
|
1113
|
-
}
|
|
1114
|
-
let state = serverState;
|
|
1115
|
-
for (const inputSnapshot of this._pendingInputs) {
|
|
1116
|
-
state = this._predictor.predict(state, inputSnapshot.input, deltaTime);
|
|
1117
|
-
}
|
|
1118
|
-
const serverPos = stateGetter(serverState);
|
|
1119
|
-
const predictedPos = stateGetter(state);
|
|
1120
|
-
const errorX = serverPos.x - predictedPos.x;
|
|
1121
|
-
const errorY = serverPos.y - predictedPos.y;
|
|
1122
|
-
const errorMagnitude = Math.sqrt(errorX * errorX + errorY * errorY);
|
|
1123
|
-
if (errorMagnitude > this._config.reconciliationThreshold) {
|
|
1124
|
-
const t = Math.min(1, this._config.reconciliationSpeed * deltaTime);
|
|
1125
|
-
this._correctionOffset.x += errorX * t;
|
|
1126
|
-
this._correctionOffset.y += errorY * t;
|
|
2683
|
+
setAOIEnabled(enabled) {
|
|
2684
|
+
if (this._aoiSystem) {
|
|
2685
|
+
this._aoiSystem.enabled = enabled;
|
|
1127
2686
|
}
|
|
1128
|
-
const decayRate = 0.9;
|
|
1129
|
-
this._correctionOffset.x *= decayRate;
|
|
1130
|
-
this._correctionOffset.y *= decayRate;
|
|
1131
|
-
this._predictedState = state;
|
|
1132
|
-
return state;
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* @zh 清空预测状态
|
|
1136
|
-
* @en Clear prediction state
|
|
1137
|
-
*/
|
|
1138
|
-
clear() {
|
|
1139
|
-
this._pendingInputs.length = 0;
|
|
1140
|
-
this._lastAcknowledgedSequence = 0;
|
|
1141
|
-
this._currentSequence = 0;
|
|
1142
|
-
this._lastServerState = null;
|
|
1143
|
-
this._predictedState = null;
|
|
1144
|
-
this._correctionOffset = {
|
|
1145
|
-
x: 0,
|
|
1146
|
-
y: 0
|
|
1147
|
-
};
|
|
1148
2687
|
}
|
|
1149
2688
|
};
|
|
1150
|
-
__name(
|
|
1151
|
-
var
|
|
1152
|
-
function createClientPrediction(predictor, config) {
|
|
1153
|
-
return new ClientPrediction(predictor, config);
|
|
1154
|
-
}
|
|
1155
|
-
__name(createClientPrediction, "createClientPrediction");
|
|
2689
|
+
__name(_NetworkPlugin, "NetworkPlugin");
|
|
2690
|
+
var NetworkPlugin = _NetworkPlugin;
|
|
1156
2691
|
|
|
1157
2692
|
// src/nodes/NetworkNodes.ts
|
|
1158
2693
|
var IsLocalPlayerTemplate = {
|
|
@@ -1184,15 +2719,15 @@ var IsLocalPlayerTemplate = {
|
|
|
1184
2719
|
};
|
|
1185
2720
|
var _IsLocalPlayerExecutor = class _IsLocalPlayerExecutor {
|
|
1186
2721
|
execute(node, context) {
|
|
1187
|
-
var
|
|
2722
|
+
var _a2;
|
|
1188
2723
|
const ctx = context;
|
|
1189
2724
|
let isLocal = false;
|
|
1190
2725
|
if (ctx.entity) {
|
|
1191
|
-
const identity = ctx.entity.getComponent((
|
|
2726
|
+
const identity = ctx.entity.getComponent((_a2 = class {
|
|
1192
2727
|
constructor() {
|
|
1193
2728
|
__publicField(this, "bIsLocalPlayer", false);
|
|
1194
2729
|
}
|
|
1195
|
-
}, __name(
|
|
2730
|
+
}, __name(_a2, "NetworkIdentity"), _a2));
|
|
1196
2731
|
if (identity) {
|
|
1197
2732
|
isLocal = identity.bIsLocalPlayer;
|
|
1198
2733
|
}
|
|
@@ -1272,15 +2807,15 @@ var HasAuthorityTemplate = {
|
|
|
1272
2807
|
};
|
|
1273
2808
|
var _HasAuthorityExecutor = class _HasAuthorityExecutor {
|
|
1274
2809
|
execute(node, context) {
|
|
1275
|
-
var
|
|
2810
|
+
var _a2;
|
|
1276
2811
|
const ctx = context;
|
|
1277
2812
|
let hasAuthority = false;
|
|
1278
2813
|
if (ctx.entity) {
|
|
1279
|
-
const identity = ctx.entity.getComponent((
|
|
2814
|
+
const identity = ctx.entity.getComponent((_a2 = class {
|
|
1280
2815
|
constructor() {
|
|
1281
2816
|
__publicField(this, "bHasAuthority", false);
|
|
1282
2817
|
}
|
|
1283
|
-
}, __name(
|
|
2818
|
+
}, __name(_a2, "NetworkIdentity"), _a2));
|
|
1284
2819
|
if (identity) {
|
|
1285
2820
|
hasAuthority = identity.bHasAuthority;
|
|
1286
2821
|
}
|
|
@@ -1327,17 +2862,17 @@ var GetNetworkIdTemplate = {
|
|
|
1327
2862
|
};
|
|
1328
2863
|
var _GetNetworkIdExecutor = class _GetNetworkIdExecutor {
|
|
1329
2864
|
execute(node, context) {
|
|
1330
|
-
var
|
|
2865
|
+
var _a2;
|
|
1331
2866
|
const ctx = context;
|
|
1332
2867
|
let netId = 0;
|
|
1333
2868
|
let ownerId = 0;
|
|
1334
2869
|
if (ctx.entity) {
|
|
1335
|
-
const identity = ctx.entity.getComponent((
|
|
2870
|
+
const identity = ctx.entity.getComponent((_a2 = class {
|
|
1336
2871
|
constructor() {
|
|
1337
2872
|
__publicField(this, "netId", 0);
|
|
1338
2873
|
__publicField(this, "ownerId", 0);
|
|
1339
2874
|
}
|
|
1340
|
-
}, __name(
|
|
2875
|
+
}, __name(_a2, "NetworkIdentity"), _a2));
|
|
1341
2876
|
if (identity) {
|
|
1342
2877
|
netId = identity.netId;
|
|
1343
2878
|
ownerId = identity.ownerId;
|
|
@@ -1424,6 +2959,8 @@ var NetworkNodeDefinitions = {
|
|
|
1424
2959
|
};
|
|
1425
2960
|
export {
|
|
1426
2961
|
ClientPrediction,
|
|
2962
|
+
ComponentSyncSystem,
|
|
2963
|
+
DeltaFlags,
|
|
1427
2964
|
GameNetworkService,
|
|
1428
2965
|
GetLocalPlayerIdExecutor,
|
|
1429
2966
|
GetLocalPlayerIdTemplate,
|
|
@@ -1436,11 +2973,15 @@ export {
|
|
|
1436
2973
|
IsLocalPlayerTemplate,
|
|
1437
2974
|
IsServerExecutor,
|
|
1438
2975
|
IsServerTemplate,
|
|
2976
|
+
NetworkAOISystem,
|
|
2977
|
+
NetworkAOISystemToken,
|
|
1439
2978
|
NetworkIdentity,
|
|
1440
2979
|
NetworkInputSystem,
|
|
1441
2980
|
NetworkInputSystemToken,
|
|
1442
2981
|
NetworkNodeDefinitions,
|
|
1443
2982
|
NetworkPlugin,
|
|
2983
|
+
NetworkPredictionSystem,
|
|
2984
|
+
NetworkPredictionSystemToken,
|
|
1444
2985
|
GameNetworkService as NetworkService,
|
|
1445
2986
|
NetworkServiceToken,
|
|
1446
2987
|
NetworkSpawnSystem,
|
|
@@ -1451,11 +2992,17 @@ export {
|
|
|
1451
2992
|
NetworkTransform,
|
|
1452
2993
|
RpcService,
|
|
1453
2994
|
SnapshotBuffer,
|
|
2995
|
+
StateDeltaCompressor,
|
|
1454
2996
|
TransformInterpolator,
|
|
1455
2997
|
createClientPrediction,
|
|
2998
|
+
createComponentSyncSystem,
|
|
1456
2999
|
createHermiteTransformInterpolator,
|
|
3000
|
+
createNetworkAOISystem,
|
|
3001
|
+
createNetworkInputSystem,
|
|
3002
|
+
createNetworkPredictionSystem,
|
|
1457
3003
|
createNetworkService,
|
|
1458
3004
|
createSnapshotBuffer,
|
|
3005
|
+
createStateDeltaCompressor,
|
|
1459
3006
|
createTransformInterpolator,
|
|
1460
3007
|
gameProtocol,
|
|
1461
3008
|
lerp,
|