@esengine/network 2.1.0 → 2.2.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/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";
@@ -454,222 +466,1602 @@ NetworkTransform = _ts_decorate2([
454
466
  })
455
467
  ], NetworkTransform);
456
468
 
457
- // src/systems/NetworkSyncSystem.ts
458
- var _NetworkSyncSystem = class _NetworkSyncSystem extends EntitySystem {
459
- constructor() {
460
- super(Matcher.all(NetworkIdentity, NetworkTransform));
461
- __publicField(this, "_netIdToEntity", /* @__PURE__ */ new Map());
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;
477
+ }
478
+ get size() {
479
+ return this._buffer.length;
462
480
  }
463
481
  /**
464
- * @zh 处理同步消息
465
- * @en Handle sync message
482
+ * @zh 获取插值延迟
483
+ * @en Get interpolation delay
466
484
  */
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);
485
+ get interpolationDelay() {
486
+ return this._interpolationDelay;
487
+ }
488
+ /**
489
+ * @zh 添加快照
490
+ * @en Add snapshot
491
+ */
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;
476
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();
477
506
  }
478
507
  }
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);
508
+ /**
509
+ * @zh 获取用于插值的两个快照
510
+ * @en Get two snapshots for interpolation
511
+ */
512
+ getInterpolationSnapshots(renderTime) {
513
+ if (this._buffer.length < 2) {
514
+ return null;
515
+ }
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
+ ];
486
528
  }
487
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;
488
542
  }
489
543
  /**
490
- * @zh 注册网络实体
491
- * @en Register network entity
544
+ * @zh 获取最新快照
545
+ * @en Get latest snapshot
492
546
  */
493
- registerEntity(netId, entityId) {
494
- this._netIdToEntity.set(netId, entityId);
547
+ getLatest() {
548
+ return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
495
549
  }
496
550
  /**
497
- * @zh 注销网络实体
498
- * @en Unregister network entity
551
+ * @zh 获取特定时间之后的所有快照
552
+ * @en Get all snapshots after a specific time
499
553
  */
500
- unregisterEntity(netId) {
501
- this._netIdToEntity.delete(netId);
554
+ getSnapshotsAfter(timestamp) {
555
+ return this._buffer.filter((s) => s.timestamp > timestamp);
502
556
  }
503
557
  /**
504
- * @zh 根据网络 ID 获取实体 ID
505
- * @en Get entity ID by network ID
558
+ * @zh 清空缓冲区
559
+ * @en Clear buffer
506
560
  */
507
- getEntityId(netId) {
508
- return this._netIdToEntity.get(netId);
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();
561
+ clear() {
562
+ this._buffer.length = 0;
521
563
  }
522
564
  };
523
- __name(_NetworkSyncSystem, "NetworkSyncSystem");
524
- var NetworkSyncSystem = _NetworkSyncSystem;
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");
525
574
 
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;
535
- }
536
- /**
537
- * @zh 设置本地玩家 ID
538
- * @en Set local player ID
539
- */
540
- setLocalPlayerId(id) {
541
- this._localPlayerId = id;
542
- }
543
- /**
544
- * @zh 处理生成消息
545
- * @en Handle spawn message
546
- */
547
- handleSpawn(msg) {
548
- if (!this.scene) return null;
549
- const factory = this._prefabFactories.get(msg.prefab);
550
- if (!factory) {
551
- this.logger.warn(`Unknown prefab: ${msg.prefab}`);
552
- return null;
553
- }
554
- const entity = factory(this.scene, msg);
555
- const identity = entity.addComponent(new NetworkIdentity());
556
- identity.netId = msg.netId;
557
- identity.ownerId = msg.ownerId;
558
- identity.prefabType = msg.prefab;
559
- identity.bHasAuthority = msg.ownerId === this._localPlayerId;
560
- identity.bIsLocalPlayer = identity.bHasAuthority;
561
- const transform = entity.addComponent(new NetworkTransform());
562
- transform.setTarget(msg.pos.x, msg.pos.y, msg.rot ?? 0);
563
- transform.snap();
564
- this._syncSystem.registerEntity(msg.netId, entity.id);
565
- return entity;
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;
566
601
  }
602
+ return [
603
+ newValue,
604
+ newVelocity
605
+ ];
606
+ }
607
+ __name(smoothDamp, "smoothDamp");
608
+
609
+ // src/sync/TransformInterpolator.ts
610
+ var _TransformInterpolator = class _TransformInterpolator {
567
611
  /**
568
- * @zh 处理销毁消息
569
- * @en Handle despawn message
612
+ * @zh 在两个变换状态之间插值
613
+ * @en Interpolate between two transform states
570
614
  */
571
- handleDespawn(msg) {
572
- const entityId = this._syncSystem.getEntityId(msg.netId);
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);
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
+ };
579
621
  }
580
622
  /**
581
- * @zh 注册预制体工厂
582
- * @en Register prefab factory
623
+ * @zh 基于速度外推变换状态
624
+ * @en Extrapolate transform state based on velocity
583
625
  */
584
- registerPrefab(prefabType, factory) {
585
- this._prefabFactories.set(prefabType, factory);
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
+ };
586
635
  }
636
+ };
637
+ __name(_TransformInterpolator, "TransformInterpolator");
638
+ var TransformInterpolator = _TransformInterpolator;
639
+ var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
587
640
  /**
588
- * @zh 注销预制体工厂
589
- * @en Unregister prefab factory
641
+ * @zh 使用赫尔米特插值
642
+ * @en Use Hermite interpolation
590
643
  */
591
- unregisterPrefab(prefabType) {
592
- this._prefabFactories.delete(prefabType);
593
- }
594
- onDestroy() {
595
- this._prefabFactories.clear();
596
- }
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/systems/NetworkSyncSystem.ts
1040
+ var DEFAULT_CONFIG2 = {
1041
+ bufferSize: 30,
1042
+ interpolationDelay: 100,
1043
+ enableExtrapolation: true,
1044
+ maxExtrapolationTime: 200,
1045
+ useHermiteInterpolation: false
1046
+ };
1047
+ var _NetworkSyncSystem = class _NetworkSyncSystem extends EntitySystem {
1048
+ constructor(config) {
1049
+ super(Matcher.all(NetworkIdentity, NetworkTransform));
1050
+ __publicField(this, "_netIdToEntity", /* @__PURE__ */ new Map());
1051
+ __publicField(this, "_entitySnapshots", /* @__PURE__ */ new Map());
1052
+ __publicField(this, "_interpolator");
1053
+ __publicField(this, "_config");
1054
+ __publicField(this, "_serverTimeOffset", 0);
1055
+ __publicField(this, "_lastSyncTime", 0);
1056
+ __publicField(this, "_renderTime", 0);
1057
+ this._config = {
1058
+ ...DEFAULT_CONFIG2,
1059
+ ...config
1060
+ };
1061
+ this._interpolator = createTransformInterpolator();
1062
+ }
1063
+ /**
1064
+ * @zh 获取配置
1065
+ * @en Get configuration
1066
+ */
1067
+ get config() {
1068
+ return this._config;
1069
+ }
1070
+ /**
1071
+ * @zh 获取服务器时间偏移
1072
+ * @en Get server time offset
1073
+ */
1074
+ get serverTimeOffset() {
1075
+ return this._serverTimeOffset;
1076
+ }
1077
+ /**
1078
+ * @zh 获取当前渲染时间
1079
+ * @en Get current render time
1080
+ */
1081
+ get renderTime() {
1082
+ return this._renderTime;
1083
+ }
1084
+ /**
1085
+ * @zh 处理同步消息(新版,带时间戳)
1086
+ * @en Handle sync message (new version with timestamp)
1087
+ */
1088
+ handleSyncData(data) {
1089
+ const serverTime = data.timestamp;
1090
+ const clientTime = Date.now();
1091
+ this._serverTimeOffset = serverTime - clientTime;
1092
+ this._lastSyncTime = clientTime;
1093
+ for (const state of data.entities) {
1094
+ this._processEntityState(state, serverTime);
1095
+ }
1096
+ }
1097
+ /**
1098
+ * @zh 处理同步消息(兼容旧版)
1099
+ * @en Handle sync message (backwards compatible)
1100
+ */
1101
+ handleSync(msg) {
1102
+ const now = Date.now();
1103
+ for (const state of msg.entities) {
1104
+ const entityId = this._netIdToEntity.get(state.netId);
1105
+ if (entityId === void 0) continue;
1106
+ const entity = this.scene?.findEntityById(entityId);
1107
+ if (!entity) continue;
1108
+ const transform = entity.getComponent(NetworkTransform);
1109
+ if (transform && state.pos) {
1110
+ transform.setTarget(state.pos.x, state.pos.y, state.rot ?? 0);
1111
+ }
1112
+ this._processEntityState({
1113
+ netId: state.netId,
1114
+ pos: state.pos,
1115
+ rot: state.rot
1116
+ }, now);
1117
+ }
1118
+ }
1119
+ _processEntityState(state, serverTime) {
1120
+ const entityId = this._netIdToEntity.get(state.netId);
1121
+ if (entityId === void 0) return;
1122
+ let snapshotData = this._entitySnapshots.get(state.netId);
1123
+ if (!snapshotData) {
1124
+ snapshotData = {
1125
+ buffer: createSnapshotBuffer(this._config.bufferSize, this._config.interpolationDelay),
1126
+ lastServerTime: 0
1127
+ };
1128
+ this._entitySnapshots.set(state.netId, snapshotData);
1129
+ }
1130
+ const transformState = {
1131
+ x: state.pos?.x ?? 0,
1132
+ y: state.pos?.y ?? 0,
1133
+ rotation: state.rot ?? 0,
1134
+ velocityX: state.vel?.x ?? 0,
1135
+ velocityY: state.vel?.y ?? 0,
1136
+ angularVelocity: state.angVel ?? 0
1137
+ };
1138
+ const snapshot = {
1139
+ timestamp: serverTime,
1140
+ state: transformState
1141
+ };
1142
+ snapshotData.buffer.push(snapshot);
1143
+ snapshotData.lastServerTime = serverTime;
1144
+ }
1145
+ process(entities) {
1146
+ const deltaTime = Time.deltaTime;
1147
+ const clientTime = Date.now();
1148
+ this._renderTime = clientTime + this._serverTimeOffset;
1149
+ for (const entity of entities) {
1150
+ const transform = this.requireComponent(entity, NetworkTransform);
1151
+ const identity = this.requireComponent(entity, NetworkIdentity);
1152
+ if (identity.bHasAuthority) continue;
1153
+ if (transform.bInterpolate) {
1154
+ this._interpolateEntity(identity.netId, transform, deltaTime);
1155
+ }
1156
+ }
1157
+ }
1158
+ _interpolateEntity(netId, transform, deltaTime) {
1159
+ const snapshotData = this._entitySnapshots.get(netId);
1160
+ if (snapshotData && snapshotData.buffer.size >= 2) {
1161
+ const result = snapshotData.buffer.getInterpolationSnapshots(this._renderTime);
1162
+ if (result) {
1163
+ const [prev, next, t] = result;
1164
+ const interpolated = this._interpolator.interpolate(prev.state, next.state, t);
1165
+ transform.currentX = interpolated.x;
1166
+ transform.currentY = interpolated.y;
1167
+ transform.currentRotation = interpolated.rotation;
1168
+ transform.targetX = next.state.x;
1169
+ transform.targetY = next.state.y;
1170
+ transform.targetRotation = next.state.rotation;
1171
+ return;
1172
+ }
1173
+ if (this._config.enableExtrapolation) {
1174
+ const latest = snapshotData.buffer.getLatest();
1175
+ if (latest) {
1176
+ const timeSinceLastSnapshot = this._renderTime - latest.timestamp;
1177
+ if (timeSinceLastSnapshot > 0 && timeSinceLastSnapshot < this._config.maxExtrapolationTime) {
1178
+ const extrapolated = this._interpolator.extrapolate(latest.state, timeSinceLastSnapshot / 1e3);
1179
+ transform.currentX = extrapolated.x;
1180
+ transform.currentY = extrapolated.y;
1181
+ transform.currentRotation = extrapolated.rotation;
1182
+ return;
1183
+ }
1184
+ }
1185
+ }
1186
+ }
1187
+ this._simpleLerp(transform, deltaTime);
1188
+ }
1189
+ _simpleLerp(transform, deltaTime) {
1190
+ const t = Math.min(1, transform.lerpSpeed * deltaTime);
1191
+ transform.currentX += (transform.targetX - transform.currentX) * t;
1192
+ transform.currentY += (transform.targetY - transform.currentY) * t;
1193
+ let angleDiff = transform.targetRotation - transform.currentRotation;
1194
+ while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
1195
+ while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
1196
+ transform.currentRotation += angleDiff * t;
1197
+ }
1198
+ /**
1199
+ * @zh 注册网络实体
1200
+ * @en Register network entity
1201
+ */
1202
+ registerEntity(netId, entityId) {
1203
+ this._netIdToEntity.set(netId, entityId);
1204
+ }
1205
+ /**
1206
+ * @zh 注销网络实体
1207
+ * @en Unregister network entity
1208
+ */
1209
+ unregisterEntity(netId) {
1210
+ this._netIdToEntity.delete(netId);
1211
+ this._entitySnapshots.delete(netId);
1212
+ }
1213
+ /**
1214
+ * @zh 根据网络 ID 获取实体 ID
1215
+ * @en Get entity ID by network ID
1216
+ */
1217
+ getEntityId(netId) {
1218
+ return this._netIdToEntity.get(netId);
1219
+ }
1220
+ /**
1221
+ * @zh 获取实体的快照缓冲区
1222
+ * @en Get entity's snapshot buffer
1223
+ */
1224
+ getSnapshotBuffer(netId) {
1225
+ return this._entitySnapshots.get(netId)?.buffer;
1226
+ }
1227
+ /**
1228
+ * @zh 清空所有快照缓冲
1229
+ * @en Clear all snapshot buffers
1230
+ */
1231
+ clearSnapshots() {
1232
+ for (const data of this._entitySnapshots.values()) {
1233
+ data.buffer.clear();
1234
+ }
1235
+ }
1236
+ onDestroy() {
1237
+ this._netIdToEntity.clear();
1238
+ this._entitySnapshots.clear();
1239
+ }
1240
+ };
1241
+ __name(_NetworkSyncSystem, "NetworkSyncSystem");
1242
+ var NetworkSyncSystem = _NetworkSyncSystem;
1243
+
1244
+ // src/systems/NetworkSpawnSystem.ts
1245
+ import { EntitySystem as EntitySystem2, Matcher as Matcher2 } from "@esengine/ecs-framework";
1246
+ var _NetworkSpawnSystem = class _NetworkSpawnSystem extends EntitySystem2 {
1247
+ constructor(syncSystem) {
1248
+ super(Matcher2.nothing());
1249
+ __publicField(this, "_syncSystem");
1250
+ __publicField(this, "_prefabFactories", /* @__PURE__ */ new Map());
1251
+ __publicField(this, "_localPlayerId", 0);
1252
+ this._syncSystem = syncSystem;
1253
+ }
1254
+ /**
1255
+ * @zh 设置本地玩家 ID
1256
+ * @en Set local player ID
1257
+ */
1258
+ setLocalPlayerId(id) {
1259
+ this._localPlayerId = id;
1260
+ }
1261
+ /**
1262
+ * @zh 处理生成消息
1263
+ * @en Handle spawn message
1264
+ */
1265
+ handleSpawn(msg) {
1266
+ if (!this.scene) return null;
1267
+ const factory = this._prefabFactories.get(msg.prefab);
1268
+ if (!factory) {
1269
+ this.logger.warn(`Unknown prefab: ${msg.prefab}`);
1270
+ return null;
1271
+ }
1272
+ const entity = factory(this.scene, msg);
1273
+ const identity = entity.addComponent(new NetworkIdentity());
1274
+ identity.netId = msg.netId;
1275
+ identity.ownerId = msg.ownerId;
1276
+ identity.prefabType = msg.prefab;
1277
+ identity.bHasAuthority = msg.ownerId === this._localPlayerId;
1278
+ identity.bIsLocalPlayer = identity.bHasAuthority;
1279
+ const transform = entity.addComponent(new NetworkTransform());
1280
+ transform.setTarget(msg.pos.x, msg.pos.y, msg.rot ?? 0);
1281
+ transform.snap();
1282
+ this._syncSystem.registerEntity(msg.netId, entity.id);
1283
+ return entity;
1284
+ }
1285
+ /**
1286
+ * @zh 处理销毁消息
1287
+ * @en Handle despawn message
1288
+ */
1289
+ handleDespawn(msg) {
1290
+ const entityId = this._syncSystem.getEntityId(msg.netId);
1291
+ if (entityId === void 0) return;
1292
+ const entity = this.scene?.findEntityById(entityId);
1293
+ if (entity) {
1294
+ entity.destroy();
1295
+ }
1296
+ this._syncSystem.unregisterEntity(msg.netId);
1297
+ }
1298
+ /**
1299
+ * @zh 注册预制体工厂
1300
+ * @en Register prefab factory
1301
+ */
1302
+ registerPrefab(prefabType, factory) {
1303
+ this._prefabFactories.set(prefabType, factory);
1304
+ }
1305
+ /**
1306
+ * @zh 注销预制体工厂
1307
+ * @en Unregister prefab factory
1308
+ */
1309
+ unregisterPrefab(prefabType) {
1310
+ this._prefabFactories.delete(prefabType);
1311
+ }
1312
+ onDestroy() {
1313
+ this._prefabFactories.clear();
1314
+ }
1315
+ };
1316
+ __name(_NetworkSpawnSystem, "NetworkSpawnSystem");
1317
+ var NetworkSpawnSystem = _NetworkSpawnSystem;
1318
+
1319
+ // src/systems/NetworkInputSystem.ts
1320
+ import { EntitySystem as EntitySystem3, Matcher as Matcher3 } from "@esengine/ecs-framework";
1321
+ var DEFAULT_CONFIG3 = {
1322
+ sendInterval: 16,
1323
+ mergeIdenticalInputs: true,
1324
+ maxQueueLength: 10
1325
+ };
1326
+ var _NetworkInputSystem = class _NetworkInputSystem extends EntitySystem3 {
1327
+ constructor(networkService, config) {
1328
+ super(Matcher3.nothing());
1329
+ __publicField(this, "_networkService");
1330
+ __publicField(this, "_config");
1331
+ __publicField(this, "_predictionSystem", null);
1332
+ __publicField(this, "_frame", 0);
1333
+ __publicField(this, "_inputSequence", 0);
1334
+ __publicField(this, "_inputQueue", []);
1335
+ __publicField(this, "_lastSendTime", 0);
1336
+ __publicField(this, "_lastMoveDir", {
1337
+ x: 0,
1338
+ y: 0
1339
+ });
1340
+ this._networkService = networkService;
1341
+ this._config = {
1342
+ ...DEFAULT_CONFIG3,
1343
+ ...config
1344
+ };
1345
+ }
1346
+ /**
1347
+ * @zh 获取配置
1348
+ * @en Get configuration
1349
+ */
1350
+ get config() {
1351
+ return this._config;
1352
+ }
1353
+ /**
1354
+ * @zh 获取当前帧号
1355
+ * @en Get current frame number
1356
+ */
1357
+ get frame() {
1358
+ return this._frame;
1359
+ }
1360
+ /**
1361
+ * @zh 获取当前输入序列号
1362
+ * @en Get current input sequence
1363
+ */
1364
+ get inputSequence() {
1365
+ return this._inputSequence;
1366
+ }
1367
+ /**
1368
+ * @zh 设置预测系统引用
1369
+ * @en Set prediction system reference
1370
+ */
1371
+ setPredictionSystem(system) {
1372
+ this._predictionSystem = system;
1373
+ }
1374
+ /**
1375
+ * @zh 处理输入队列
1376
+ * @en Process input queue
1377
+ */
1378
+ process() {
1379
+ if (!this._networkService.isConnected) return;
1380
+ this._frame++;
1381
+ const now = Date.now();
1382
+ if (now - this._lastSendTime < this._config.sendInterval) return;
1383
+ if (this._predictionSystem) {
1384
+ const predictedInput = this._predictionSystem.getInputToSend();
1385
+ if (predictedInput) {
1386
+ this._networkService.sendInput(predictedInput);
1387
+ this._lastSendTime = now;
1388
+ }
1389
+ return;
1390
+ }
1391
+ if (this._inputQueue.length === 0) return;
1392
+ let mergedInput;
1393
+ if (this._config.mergeIdenticalInputs && this._inputQueue.length > 1) {
1394
+ mergedInput = this._mergeInputs(this._inputQueue);
1395
+ this._inputQueue.length = 0;
1396
+ } else {
1397
+ mergedInput = this._inputQueue.shift();
1398
+ }
1399
+ this._inputSequence++;
1400
+ const input = {
1401
+ seq: this._inputSequence,
1402
+ frame: this._frame,
1403
+ timestamp: mergedInput.timestamp,
1404
+ moveDir: mergedInput.moveDir,
1405
+ actions: mergedInput.actions
1406
+ };
1407
+ this._networkService.sendInput(input);
1408
+ this._lastSendTime = now;
1409
+ }
1410
+ _mergeInputs(inputs) {
1411
+ const allActions = [];
1412
+ let lastMoveDir;
1413
+ for (const input of inputs) {
1414
+ if (input.moveDir) {
1415
+ lastMoveDir = input.moveDir;
1416
+ }
1417
+ if (input.actions) {
1418
+ allActions.push(...input.actions);
1419
+ }
1420
+ }
1421
+ return {
1422
+ moveDir: lastMoveDir,
1423
+ actions: allActions.length > 0 ? allActions : void 0,
1424
+ timestamp: inputs[inputs.length - 1].timestamp
1425
+ };
1426
+ }
1427
+ /**
1428
+ * @zh 添加移动输入
1429
+ * @en Add move input
1430
+ */
1431
+ addMoveInput(x, y) {
1432
+ if (this._config.mergeIdenticalInputs && this._lastMoveDir.x === x && this._lastMoveDir.y === y && this._inputQueue.length > 0) {
1433
+ return;
1434
+ }
1435
+ this._lastMoveDir = {
1436
+ x,
1437
+ y
1438
+ };
1439
+ if (this._predictionSystem) {
1440
+ this._predictionSystem.setInput(x, y);
1441
+ }
1442
+ this._addToQueue({
1443
+ moveDir: {
1444
+ x,
1445
+ y
1446
+ },
1447
+ timestamp: Date.now()
1448
+ });
1449
+ }
1450
+ /**
1451
+ * @zh 添加动作输入
1452
+ * @en Add action input
1453
+ */
1454
+ addActionInput(action) {
1455
+ const lastInput = this._inputQueue[this._inputQueue.length - 1];
1456
+ if (lastInput) {
1457
+ lastInput.actions = lastInput.actions || [];
1458
+ lastInput.actions.push(action);
1459
+ } else {
1460
+ this._addToQueue({
1461
+ actions: [
1462
+ action
1463
+ ],
1464
+ timestamp: Date.now()
1465
+ });
1466
+ }
1467
+ if (this._predictionSystem) {
1468
+ this._predictionSystem.setInput(this._lastMoveDir.x, this._lastMoveDir.y, [
1469
+ action
1470
+ ]);
1471
+ }
1472
+ }
1473
+ _addToQueue(input) {
1474
+ this._inputQueue.push(input);
1475
+ while (this._inputQueue.length > this._config.maxQueueLength) {
1476
+ this._inputQueue.shift();
1477
+ }
1478
+ }
1479
+ /**
1480
+ * @zh 清空输入队列
1481
+ * @en Clear input queue
1482
+ */
1483
+ clearQueue() {
1484
+ this._inputQueue.length = 0;
1485
+ this._lastMoveDir = {
1486
+ x: 0,
1487
+ y: 0
1488
+ };
1489
+ }
1490
+ /**
1491
+ * @zh 重置状态
1492
+ * @en Reset state
1493
+ */
1494
+ reset() {
1495
+ this._frame = 0;
1496
+ this._inputSequence = 0;
1497
+ this.clearQueue();
1498
+ }
1499
+ onDestroy() {
1500
+ this._inputQueue.length = 0;
1501
+ this._predictionSystem = null;
1502
+ }
1503
+ };
1504
+ __name(_NetworkInputSystem, "NetworkInputSystem");
1505
+ var NetworkInputSystem = _NetworkInputSystem;
1506
+ function createNetworkInputSystem(networkService, config) {
1507
+ return new NetworkInputSystem(networkService, config);
1508
+ }
1509
+ __name(createNetworkInputSystem, "createNetworkInputSystem");
1510
+
1511
+ // src/systems/NetworkPredictionSystem.ts
1512
+ import { EntitySystem as EntitySystem4, Matcher as Matcher4, Time as Time2 } from "@esengine/ecs-framework";
1513
+ var DEFAULT_CONFIG4 = {
1514
+ moveSpeed: 200,
1515
+ enabled: true,
1516
+ maxUnacknowledgedInputs: 60,
1517
+ reconciliationThreshold: 0.5,
1518
+ reconciliationSpeed: 10
1519
+ };
1520
+ var _a;
1521
+ var SimpleMovementPredictor = (_a = class {
1522
+ constructor(_moveSpeed) {
1523
+ __publicField(this, "_moveSpeed");
1524
+ this._moveSpeed = _moveSpeed;
1525
+ }
1526
+ predict(state, input, deltaTime) {
1527
+ const velocityX = input.x * this._moveSpeed;
1528
+ const velocityY = input.y * this._moveSpeed;
1529
+ return {
1530
+ x: state.x + velocityX * deltaTime,
1531
+ y: state.y + velocityY * deltaTime,
1532
+ rotation: state.rotation,
1533
+ velocityX,
1534
+ velocityY
1535
+ };
1536
+ }
1537
+ }, __name(_a, "SimpleMovementPredictor"), _a);
1538
+ var _NetworkPredictionSystem = class _NetworkPredictionSystem extends EntitySystem4 {
1539
+ constructor(config) {
1540
+ super(Matcher4.all(NetworkIdentity, NetworkTransform));
1541
+ __publicField(this, "_config");
1542
+ __publicField(this, "_predictor");
1543
+ __publicField(this, "_prediction", null);
1544
+ __publicField(this, "_localPlayerNetId", -1);
1545
+ __publicField(this, "_currentInput", {
1546
+ x: 0,
1547
+ y: 0
1548
+ });
1549
+ __publicField(this, "_inputSequence", 0);
1550
+ this._config = {
1551
+ ...DEFAULT_CONFIG4,
1552
+ ...config
1553
+ };
1554
+ this._predictor = new SimpleMovementPredictor(this._config.moveSpeed);
1555
+ }
1556
+ /**
1557
+ * @zh 获取配置
1558
+ * @en Get configuration
1559
+ */
1560
+ get config() {
1561
+ return this._config;
1562
+ }
1563
+ /**
1564
+ * @zh 获取当前输入序列号
1565
+ * @en Get current input sequence number
1566
+ */
1567
+ get inputSequence() {
1568
+ return this._inputSequence;
1569
+ }
1570
+ /**
1571
+ * @zh 获取待确认输入数量
1572
+ * @en Get pending input count
1573
+ */
1574
+ get pendingInputCount() {
1575
+ return this._prediction?.pendingInputCount ?? 0;
1576
+ }
1577
+ /**
1578
+ * @zh 是否启用预测
1579
+ * @en Whether prediction is enabled
1580
+ */
1581
+ get enabled() {
1582
+ return this._config.enabled;
1583
+ }
1584
+ set enabled(value) {
1585
+ this._config.enabled = value;
1586
+ }
1587
+ /**
1588
+ * @zh 设置本地玩家网络 ID
1589
+ * @en Set local player network ID
1590
+ */
1591
+ setLocalPlayerNetId(netId) {
1592
+ this._localPlayerNetId = netId;
1593
+ this._prediction = createClientPrediction(this._predictor, {
1594
+ maxUnacknowledgedInputs: this._config.maxUnacknowledgedInputs,
1595
+ reconciliationThreshold: this._config.reconciliationThreshold,
1596
+ reconciliationSpeed: this._config.reconciliationSpeed
1597
+ });
1598
+ }
1599
+ /**
1600
+ * @zh 设置移动输入
1601
+ * @en Set movement input
1602
+ */
1603
+ setInput(x, y, actions) {
1604
+ this._currentInput = {
1605
+ x,
1606
+ y,
1607
+ actions
1608
+ };
1609
+ }
1610
+ /**
1611
+ * @zh 获取下一个要发送的输入(带序列号)
1612
+ * @en Get next input to send (with sequence number)
1613
+ */
1614
+ getInputToSend() {
1615
+ if (!this._prediction) return null;
1616
+ const input = this._prediction.getInputToSend();
1617
+ if (!input) return null;
1618
+ return {
1619
+ seq: input.sequence,
1620
+ frame: 0,
1621
+ timestamp: input.timestamp,
1622
+ moveDir: {
1623
+ x: input.input.x,
1624
+ y: input.input.y
1625
+ },
1626
+ actions: input.input.actions
1627
+ };
1628
+ }
1629
+ /**
1630
+ * @zh 处理服务器同步数据进行校正
1631
+ * @en Process server sync data for reconciliation
1632
+ */
1633
+ reconcileWithServer(data) {
1634
+ if (!this._prediction || this._localPlayerNetId < 0) return;
1635
+ const localState = data.entities.find((e) => e.netId === this._localPlayerNetId);
1636
+ if (!localState || !localState.pos) return;
1637
+ const serverState = {
1638
+ x: localState.pos.x,
1639
+ y: localState.pos.y,
1640
+ rotation: localState.rot ?? 0,
1641
+ velocityX: localState.vel?.x ?? 0,
1642
+ velocityY: localState.vel?.y ?? 0
1643
+ };
1644
+ if (data.ackSeq !== void 0) {
1645
+ this._prediction.reconcile(serverState, data.ackSeq, (state) => ({
1646
+ x: state.x,
1647
+ y: state.y
1648
+ }), Time2.deltaTime);
1649
+ }
1650
+ }
1651
+ process(entities) {
1652
+ if (!this._config.enabled || !this._prediction) return;
1653
+ const deltaTime = Time2.deltaTime;
1654
+ for (const entity of entities) {
1655
+ const identity = this.requireComponent(entity, NetworkIdentity);
1656
+ if (!identity.bHasAuthority || identity.netId !== this._localPlayerNetId) continue;
1657
+ const transform = this.requireComponent(entity, NetworkTransform);
1658
+ const currentState = {
1659
+ x: transform.currentX,
1660
+ y: transform.currentY,
1661
+ rotation: transform.currentRotation,
1662
+ velocityX: 0,
1663
+ velocityY: 0
1664
+ };
1665
+ if (this._currentInput.x !== 0 || this._currentInput.y !== 0) {
1666
+ const predicted = this._prediction.recordInput(this._currentInput, currentState, deltaTime);
1667
+ transform.currentX = predicted.x;
1668
+ transform.currentY = predicted.y;
1669
+ transform.currentRotation = predicted.rotation;
1670
+ transform.targetX = predicted.x;
1671
+ transform.targetY = predicted.y;
1672
+ transform.targetRotation = predicted.rotation;
1673
+ this._inputSequence = this._prediction.currentSequence;
1674
+ }
1675
+ const offset = this._prediction.correctionOffset;
1676
+ if (Math.abs(offset.x) > 0.01 || Math.abs(offset.y) > 0.01) {
1677
+ transform.currentX += offset.x * deltaTime * 5;
1678
+ transform.currentY += offset.y * deltaTime * 5;
1679
+ }
1680
+ }
1681
+ }
1682
+ /**
1683
+ * @zh 重置预测状态
1684
+ * @en Reset prediction state
1685
+ */
1686
+ reset() {
1687
+ this._prediction?.clear();
1688
+ this._inputSequence = 0;
1689
+ this._currentInput = {
1690
+ x: 0,
1691
+ y: 0
1692
+ };
1693
+ }
1694
+ onDestroy() {
1695
+ this._prediction?.clear();
1696
+ this._prediction = null;
1697
+ }
597
1698
  };
598
- __name(_NetworkSpawnSystem, "NetworkSpawnSystem");
599
- var NetworkSpawnSystem = _NetworkSpawnSystem;
1699
+ __name(_NetworkPredictionSystem, "NetworkPredictionSystem");
1700
+ var NetworkPredictionSystem = _NetworkPredictionSystem;
1701
+ function createNetworkPredictionSystem(config) {
1702
+ return new NetworkPredictionSystem(config);
1703
+ }
1704
+ __name(createNetworkPredictionSystem, "createNetworkPredictionSystem");
600
1705
 
601
- // src/systems/NetworkInputSystem.ts
602
- import { EntitySystem as EntitySystem3, Matcher as Matcher3 } from "@esengine/ecs-framework";
603
- var _NetworkInputSystem = class _NetworkInputSystem extends EntitySystem3 {
604
- constructor(networkService) {
605
- super(Matcher3.nothing());
606
- __publicField(this, "_networkService");
607
- __publicField(this, "_frame", 0);
608
- __publicField(this, "_inputQueue", []);
609
- this._networkService = networkService;
1706
+ // src/systems/NetworkAOISystem.ts
1707
+ import { EntitySystem as EntitySystem5, Matcher as Matcher5 } from "@esengine/ecs-framework";
1708
+ var DEFAULT_CONFIG5 = {
1709
+ cellSize: 100,
1710
+ defaultViewRange: 500,
1711
+ enabled: true
1712
+ };
1713
+ var _NetworkAOISystem = class _NetworkAOISystem extends EntitySystem5 {
1714
+ constructor(config) {
1715
+ super(Matcher5.all(NetworkIdentity, NetworkTransform));
1716
+ __publicField(this, "_config");
1717
+ __publicField(this, "_observers", /* @__PURE__ */ new Map());
1718
+ __publicField(this, "_cells", /* @__PURE__ */ new Map());
1719
+ __publicField(this, "_listeners", /* @__PURE__ */ new Set());
1720
+ __publicField(this, "_entityNetIdMap", /* @__PURE__ */ new Map());
1721
+ __publicField(this, "_netIdEntityMap", /* @__PURE__ */ new Map());
1722
+ this._config = {
1723
+ ...DEFAULT_CONFIG5,
1724
+ ...config
1725
+ };
610
1726
  }
611
1727
  /**
612
- * @zh 处理输入队列
613
- * @en Process input queue
1728
+ * @zh 获取配置
1729
+ * @en Get configuration
614
1730
  */
615
- process() {
616
- if (!this._networkService.isConnected) return;
617
- this._frame++;
618
- while (this._inputQueue.length > 0) {
619
- const input = this._inputQueue.shift();
620
- input.frame = this._frame;
621
- this._networkService.sendInput(input);
622
- }
1731
+ get config() {
1732
+ return this._config;
623
1733
  }
624
1734
  /**
625
- * @zh 添加移动输入
626
- * @en Add move input
1735
+ * @zh 是否启用
1736
+ * @en Is enabled
627
1737
  */
628
- addMoveInput(x, y) {
629
- this._inputQueue.push({
630
- frame: 0,
631
- moveDir: {
1738
+ get enabled() {
1739
+ return this._config.enabled;
1740
+ }
1741
+ set enabled(value) {
1742
+ this._config.enabled = value;
1743
+ }
1744
+ /**
1745
+ * @zh 观察者数量
1746
+ * @en Observer count
1747
+ */
1748
+ get observerCount() {
1749
+ return this._observers.size;
1750
+ }
1751
+ // =========================================================================
1752
+ // 观察者管理 | Observer Management
1753
+ // =========================================================================
1754
+ /**
1755
+ * @zh 添加观察者(通常是玩家实体)
1756
+ * @en Add observer (usually player entity)
1757
+ */
1758
+ addObserver(netId, x, y, viewRange) {
1759
+ if (this._observers.has(netId)) {
1760
+ this.updateObserverPosition(netId, x, y);
1761
+ return;
1762
+ }
1763
+ const range = viewRange ?? this._config.defaultViewRange;
1764
+ const cellKey = this._getCellKey(x, y);
1765
+ const data = {
1766
+ netId,
1767
+ position: {
632
1768
  x,
633
1769
  y
1770
+ },
1771
+ viewRange: range,
1772
+ viewRangeSq: range * range,
1773
+ cellKey,
1774
+ visibleEntities: /* @__PURE__ */ new Set()
1775
+ };
1776
+ this._observers.set(netId, data);
1777
+ this._addToCell(cellKey, netId);
1778
+ this._updateVisibility(data);
1779
+ }
1780
+ /**
1781
+ * @zh 移除观察者
1782
+ * @en Remove observer
1783
+ */
1784
+ removeObserver(netId) {
1785
+ const data = this._observers.get(netId);
1786
+ if (!data) return false;
1787
+ for (const visibleNetId of data.visibleEntities) {
1788
+ this._emitEvent({
1789
+ type: "exit",
1790
+ observerNetId: netId,
1791
+ targetNetId: visibleNetId
1792
+ });
1793
+ }
1794
+ this._removeFromCell(data.cellKey, netId);
1795
+ this._observers.delete(netId);
1796
+ return true;
1797
+ }
1798
+ /**
1799
+ * @zh 更新观察者位置
1800
+ * @en Update observer position
1801
+ */
1802
+ updateObserverPosition(netId, x, y) {
1803
+ const data = this._observers.get(netId);
1804
+ if (!data) return;
1805
+ const newCellKey = this._getCellKey(x, y);
1806
+ if (newCellKey !== data.cellKey) {
1807
+ this._removeFromCell(data.cellKey, netId);
1808
+ data.cellKey = newCellKey;
1809
+ this._addToCell(newCellKey, netId);
1810
+ }
1811
+ data.position.x = x;
1812
+ data.position.y = y;
1813
+ this._updateVisibility(data);
1814
+ }
1815
+ /**
1816
+ * @zh 更新观察者视野范围
1817
+ * @en Update observer view range
1818
+ */
1819
+ updateObserverViewRange(netId, viewRange) {
1820
+ const data = this._observers.get(netId);
1821
+ if (!data) return;
1822
+ data.viewRange = viewRange;
1823
+ data.viewRangeSq = viewRange * viewRange;
1824
+ this._updateVisibility(data);
1825
+ }
1826
+ // =========================================================================
1827
+ // 实体管理 | Entity Management
1828
+ // =========================================================================
1829
+ /**
1830
+ * @zh 注册网络实体
1831
+ * @en Register network entity
1832
+ */
1833
+ registerEntity(entity, netId) {
1834
+ this._entityNetIdMap.set(entity, netId);
1835
+ this._netIdEntityMap.set(netId, entity);
1836
+ }
1837
+ /**
1838
+ * @zh 注销网络实体
1839
+ * @en Unregister network entity
1840
+ */
1841
+ unregisterEntity(entity) {
1842
+ const netId = this._entityNetIdMap.get(entity);
1843
+ if (netId !== void 0) {
1844
+ for (const [, data] of this._observers) {
1845
+ if (data.visibleEntities.has(netId)) {
1846
+ data.visibleEntities.delete(netId);
1847
+ this._emitEvent({
1848
+ type: "exit",
1849
+ observerNetId: data.netId,
1850
+ targetNetId: netId
1851
+ });
1852
+ }
1853
+ }
1854
+ this._netIdEntityMap.delete(netId);
1855
+ }
1856
+ this._entityNetIdMap.delete(entity);
1857
+ }
1858
+ // =========================================================================
1859
+ // 查询接口 | Query Interface
1860
+ // =========================================================================
1861
+ /**
1862
+ * @zh 获取观察者能看到的实体网络 ID 列表
1863
+ * @en Get list of entity network IDs visible to observer
1864
+ */
1865
+ getVisibleEntities(observerNetId) {
1866
+ const data = this._observers.get(observerNetId);
1867
+ return data ? Array.from(data.visibleEntities) : [];
1868
+ }
1869
+ /**
1870
+ * @zh 获取能看到指定实体的观察者网络 ID 列表
1871
+ * @en Get list of observer network IDs that can see the entity
1872
+ */
1873
+ getObserversOf(entityNetId) {
1874
+ const observers = [];
1875
+ for (const [, data] of this._observers) {
1876
+ if (data.visibleEntities.has(entityNetId)) {
1877
+ observers.push(data.netId);
634
1878
  }
1879
+ }
1880
+ return observers;
1881
+ }
1882
+ /**
1883
+ * @zh 检查观察者是否能看到目标
1884
+ * @en Check if observer can see target
1885
+ */
1886
+ canSee(observerNetId, targetNetId) {
1887
+ const data = this._observers.get(observerNetId);
1888
+ return data?.visibleEntities.has(targetNetId) ?? false;
1889
+ }
1890
+ /**
1891
+ * @zh 过滤同步数据,只保留观察者能看到的实体
1892
+ * @en Filter sync data to only include entities visible to observer
1893
+ */
1894
+ filterSyncData(observerNetId, entities) {
1895
+ if (!this._config.enabled) {
1896
+ return entities;
1897
+ }
1898
+ const data = this._observers.get(observerNetId);
1899
+ if (!data) {
1900
+ return entities;
1901
+ }
1902
+ return entities.filter((entity) => {
1903
+ if (entity.netId === observerNetId) return true;
1904
+ return data.visibleEntities.has(entity.netId);
635
1905
  });
636
1906
  }
1907
+ // =========================================================================
1908
+ // 事件系统 | Event System
1909
+ // =========================================================================
637
1910
  /**
638
- * @zh 添加动作输入
639
- * @en Add action input
1911
+ * @zh 添加事件监听器
1912
+ * @en Add event listener
640
1913
  */
641
- addActionInput(action) {
642
- const lastInput = this._inputQueue[this._inputQueue.length - 1];
643
- if (lastInput) {
644
- lastInput.actions = lastInput.actions || [];
645
- lastInput.actions.push(action);
646
- } else {
647
- this._inputQueue.push({
648
- frame: 0,
649
- actions: [
650
- action
651
- ]
652
- });
1914
+ addListener(listener) {
1915
+ this._listeners.add(listener);
1916
+ }
1917
+ /**
1918
+ * @zh 移除事件监听器
1919
+ * @en Remove event listener
1920
+ */
1921
+ removeListener(listener) {
1922
+ this._listeners.delete(listener);
1923
+ }
1924
+ // =========================================================================
1925
+ // 系统生命周期 | System Lifecycle
1926
+ // =========================================================================
1927
+ process(entities) {
1928
+ if (!this._config.enabled) return;
1929
+ for (const entity of entities) {
1930
+ const identity = this.requireComponent(entity, NetworkIdentity);
1931
+ const transform = this.requireComponent(entity, NetworkTransform);
1932
+ if (!this._entityNetIdMap.has(entity)) {
1933
+ this.registerEntity(entity, identity.netId);
1934
+ }
1935
+ if (identity.bHasAuthority && this._observers.has(identity.netId)) {
1936
+ this.updateObserverPosition(identity.netId, transform.currentX, transform.currentY);
1937
+ }
1938
+ }
1939
+ this._updateAllObserversVisibility(entities);
1940
+ }
1941
+ _updateAllObserversVisibility(entities) {
1942
+ for (const [, data] of this._observers) {
1943
+ const newVisible = /* @__PURE__ */ new Set();
1944
+ for (const entity of entities) {
1945
+ const identity = this.requireComponent(entity, NetworkIdentity);
1946
+ const transform = this.requireComponent(entity, NetworkTransform);
1947
+ if (identity.netId === data.netId) continue;
1948
+ const dx = transform.currentX - data.position.x;
1949
+ const dy = transform.currentY - data.position.y;
1950
+ const distSq = dx * dx + dy * dy;
1951
+ if (distSq <= data.viewRangeSq) {
1952
+ newVisible.add(identity.netId);
1953
+ }
1954
+ }
1955
+ for (const netId of newVisible) {
1956
+ if (!data.visibleEntities.has(netId)) {
1957
+ this._emitEvent({
1958
+ type: "enter",
1959
+ observerNetId: data.netId,
1960
+ targetNetId: netId
1961
+ });
1962
+ }
1963
+ }
1964
+ for (const netId of data.visibleEntities) {
1965
+ if (!newVisible.has(netId)) {
1966
+ this._emitEvent({
1967
+ type: "exit",
1968
+ observerNetId: data.netId,
1969
+ targetNetId: netId
1970
+ });
1971
+ }
1972
+ }
1973
+ data.visibleEntities = newVisible;
653
1974
  }
654
1975
  }
1976
+ /**
1977
+ * @zh 清除所有数据
1978
+ * @en Clear all data
1979
+ */
1980
+ clear() {
1981
+ this._observers.clear();
1982
+ this._cells.clear();
1983
+ this._entityNetIdMap.clear();
1984
+ this._netIdEntityMap.clear();
1985
+ }
655
1986
  onDestroy() {
656
- this._inputQueue.length = 0;
1987
+ this.clear();
1988
+ this._listeners.clear();
1989
+ }
1990
+ // =========================================================================
1991
+ // 私有方法 | Private Methods
1992
+ // =========================================================================
1993
+ _getCellKey(x, y) {
1994
+ const cellX = Math.floor(x / this._config.cellSize);
1995
+ const cellY = Math.floor(y / this._config.cellSize);
1996
+ return `${cellX},${cellY}`;
1997
+ }
1998
+ _addToCell(cellKey, netId) {
1999
+ let cell = this._cells.get(cellKey);
2000
+ if (!cell) {
2001
+ cell = /* @__PURE__ */ new Set();
2002
+ this._cells.set(cellKey, cell);
2003
+ }
2004
+ cell.add(netId);
2005
+ }
2006
+ _removeFromCell(cellKey, netId) {
2007
+ const cell = this._cells.get(cellKey);
2008
+ if (cell) {
2009
+ cell.delete(netId);
2010
+ if (cell.size === 0) {
2011
+ this._cells.delete(cellKey);
2012
+ }
2013
+ }
2014
+ }
2015
+ _updateVisibility(data) {
2016
+ }
2017
+ _emitEvent(event) {
2018
+ for (const listener of this._listeners) {
2019
+ try {
2020
+ listener(event);
2021
+ } catch (e) {
2022
+ console.error("[NetworkAOISystem] Listener error:", e);
2023
+ }
2024
+ }
657
2025
  }
658
2026
  };
659
- __name(_NetworkInputSystem, "NetworkInputSystem");
660
- var NetworkInputSystem = _NetworkInputSystem;
2027
+ __name(_NetworkAOISystem, "NetworkAOISystem");
2028
+ var NetworkAOISystem = _NetworkAOISystem;
2029
+ function createNetworkAOISystem(config) {
2030
+ return new NetworkAOISystem(config);
2031
+ }
2032
+ __name(createNetworkAOISystem, "createNetworkAOISystem");
661
2033
 
662
2034
  // src/NetworkPlugin.ts
2035
+ var DEFAULT_CONFIG6 = {
2036
+ enablePrediction: true,
2037
+ enableAutoReconnect: true,
2038
+ maxReconnectAttempts: 5,
2039
+ reconnectInterval: 2e3,
2040
+ enableAOI: false
2041
+ };
663
2042
  var _NetworkPlugin = class _NetworkPlugin {
664
- constructor() {
2043
+ constructor(config) {
665
2044
  __publicField(this, "name", "@esengine/network");
666
- __publicField(this, "version", "2.0.0");
2045
+ __publicField(this, "version", "2.1.0");
2046
+ __publicField(this, "_config");
667
2047
  __publicField(this, "_networkService");
668
2048
  __publicField(this, "_syncSystem");
669
2049
  __publicField(this, "_spawnSystem");
670
2050
  __publicField(this, "_inputSystem");
2051
+ __publicField(this, "_predictionSystem", null);
2052
+ __publicField(this, "_aoiSystem", null);
671
2053
  __publicField(this, "_localPlayerId", 0);
2054
+ __publicField(this, "_reconnectState", null);
2055
+ __publicField(this, "_reconnectTimer", null);
2056
+ __publicField(this, "_lastConnectOptions", null);
2057
+ this._config = {
2058
+ ...DEFAULT_CONFIG6,
2059
+ ...config
2060
+ };
672
2061
  }
2062
+ // =========================================================================
2063
+ // Getters | 属性访问器
2064
+ // =========================================================================
673
2065
  /**
674
2066
  * @zh 网络服务
675
2067
  * @en Network service
@@ -698,6 +2090,20 @@ var _NetworkPlugin = class _NetworkPlugin {
698
2090
  get inputSystem() {
699
2091
  return this._inputSystem;
700
2092
  }
2093
+ /**
2094
+ * @zh 预测系统
2095
+ * @en Prediction system
2096
+ */
2097
+ get predictionSystem() {
2098
+ return this._predictionSystem;
2099
+ }
2100
+ /**
2101
+ * @zh AOI 系统
2102
+ * @en AOI system
2103
+ */
2104
+ get aoiSystem() {
2105
+ return this._aoiSystem;
2106
+ }
701
2107
  /**
702
2108
  * @zh 本地玩家 ID
703
2109
  * @en Local player ID
@@ -712,6 +2118,30 @@ var _NetworkPlugin = class _NetworkPlugin {
712
2118
  get isConnected() {
713
2119
  return this._networkService?.isConnected ?? false;
714
2120
  }
2121
+ /**
2122
+ * @zh 是否正在重连
2123
+ * @en Is reconnecting
2124
+ */
2125
+ get isReconnecting() {
2126
+ return this._reconnectState?.isReconnecting ?? false;
2127
+ }
2128
+ /**
2129
+ * @zh 是否启用预测
2130
+ * @en Is prediction enabled
2131
+ */
2132
+ get isPredictionEnabled() {
2133
+ return this._config.enablePrediction && this._predictionSystem !== null;
2134
+ }
2135
+ /**
2136
+ * @zh 是否启用 AOI
2137
+ * @en Is AOI enabled
2138
+ */
2139
+ get isAOIEnabled() {
2140
+ return this._config.enableAOI && this._aoiSystem !== null;
2141
+ }
2142
+ // =========================================================================
2143
+ // Plugin Lifecycle | 插件生命周期
2144
+ // =========================================================================
715
2145
  /**
716
2146
  * @zh 安装插件
717
2147
  * @en Install plugin
@@ -728,12 +2158,22 @@ var _NetworkPlugin = class _NetworkPlugin {
728
2158
  * @en Uninstall plugin
729
2159
  */
730
2160
  uninstall() {
2161
+ this._clearReconnectTimer();
731
2162
  this._networkService?.disconnect();
732
2163
  }
733
2164
  _setupSystems(scene) {
734
- this._syncSystem = new NetworkSyncSystem();
2165
+ this._syncSystem = new NetworkSyncSystem(this._config.syncConfig);
735
2166
  this._spawnSystem = new NetworkSpawnSystem(this._syncSystem);
736
- this._inputSystem = new NetworkInputSystem(this._networkService);
2167
+ this._inputSystem = new NetworkInputSystem(this._networkService, this._config.inputConfig);
2168
+ if (this._config.enablePrediction) {
2169
+ this._predictionSystem = new NetworkPredictionSystem(this._config.predictionConfig);
2170
+ this._inputSystem.setPredictionSystem(this._predictionSystem);
2171
+ scene.addSystem(this._predictionSystem);
2172
+ }
2173
+ if (this._config.enableAOI) {
2174
+ this._aoiSystem = new NetworkAOISystem(this._config.aoiConfig);
2175
+ scene.addSystem(this._aoiSystem);
2176
+ }
737
2177
  scene.addSystem(this._syncSystem);
738
2178
  scene.addSystem(this._spawnSystem);
739
2179
  scene.addSystem(this._inputSystem);
@@ -741,21 +2181,34 @@ var _NetworkPlugin = class _NetworkPlugin {
741
2181
  }
742
2182
  _setupMessageHandlers() {
743
2183
  this._networkService.onSync((data) => {
744
- this._syncSystem.handleSync({
745
- entities: data.entities
746
- });
2184
+ this._syncSystem.handleSyncData(data);
2185
+ if (this._predictionSystem) {
2186
+ this._predictionSystem.reconcileWithServer(data);
2187
+ }
747
2188
  }).onSpawn((data) => {
748
2189
  this._spawnSystem.handleSpawn(data);
749
2190
  }).onDespawn((data) => {
750
2191
  this._spawnSystem.handleDespawn(data);
751
2192
  });
2193
+ this._networkService.on("fullState", (data) => {
2194
+ this._handleFullState(data);
2195
+ });
752
2196
  }
2197
+ // =========================================================================
2198
+ // Connection | 连接管理
2199
+ // =========================================================================
753
2200
  /**
754
2201
  * @zh 连接到服务器
755
2202
  * @en Connect to server
756
2203
  */
757
2204
  async connect(options) {
2205
+ this._lastConnectOptions = options;
758
2206
  try {
2207
+ const originalOnDisconnect = options.onDisconnect;
2208
+ options.onDisconnect = (reason) => {
2209
+ originalOnDisconnect?.(reason);
2210
+ this._handleDisconnect(reason);
2211
+ };
759
2212
  await this._networkService.connect(options);
760
2213
  const result = await this._networkService.call("join", {
761
2214
  playerName: options.playerName,
@@ -763,8 +2216,20 @@ var _NetworkPlugin = class _NetworkPlugin {
763
2216
  });
764
2217
  this._localPlayerId = result.playerId;
765
2218
  this._spawnSystem.setLocalPlayerId(this._localPlayerId);
2219
+ if (this._predictionSystem) {
2220
+ }
2221
+ if (this._config.enableAutoReconnect) {
2222
+ this._reconnectState = {
2223
+ token: this._generateReconnectToken(),
2224
+ playerId: result.playerId,
2225
+ roomId: result.roomId,
2226
+ attempts: 0,
2227
+ isReconnecting: false
2228
+ };
2229
+ }
766
2230
  return true;
767
2231
  } catch (err) {
2232
+ console.error("[NetworkPlugin] Connection failed:", err);
768
2233
  return false;
769
2234
  }
770
2235
  }
@@ -773,386 +2238,181 @@ var _NetworkPlugin = class _NetworkPlugin {
773
2238
  * @en Disconnect
774
2239
  */
775
2240
  async disconnect() {
2241
+ this._clearReconnectTimer();
2242
+ this._reconnectState = null;
776
2243
  try {
777
2244
  await this._networkService.call("leave", void 0);
778
2245
  } catch {
779
2246
  }
780
2247
  this._networkService.disconnect();
2248
+ this._cleanup();
781
2249
  }
782
- /**
783
- * @zh 注册预制体工厂
784
- * @en Register prefab factory
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();
2250
+ _handleDisconnect(reason) {
2251
+ console.log("[NetworkPlugin] Disconnected:", reason);
2252
+ if (this._config.enableAutoReconnect && this._reconnectState && !this._reconnectState.isReconnecting) {
2253
+ this._attemptReconnect();
844
2254
  }
845
2255
  }
846
- /**
847
- * @zh 获取用于插值的两个快照
848
- * @en Get two snapshots for interpolation
849
- */
850
- getInterpolationSnapshots(renderTime) {
851
- if (this._buffer.length < 2) {
852
- return null;
2256
+ _attemptReconnect() {
2257
+ if (!this._reconnectState || !this._lastConnectOptions) return;
2258
+ if (this._reconnectState.attempts >= this._config.maxReconnectAttempts) {
2259
+ console.error("[NetworkPlugin] Max reconnection attempts reached");
2260
+ this._reconnectState = null;
2261
+ return;
853
2262
  }
854
- const targetTime = renderTime - this._interpolationDelay;
855
- for (let i = 0; i < this._buffer.length - 1; i++) {
856
- const prev = this._buffer[i];
857
- const next = this._buffer[i + 1];
858
- if (prev.timestamp <= targetTime && next.timestamp >= targetTime) {
859
- const duration = next.timestamp - prev.timestamp;
860
- const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 0;
861
- return [
862
- prev,
863
- next,
864
- Math.max(0, Math.min(1, t))
865
- ];
2263
+ this._reconnectState.isReconnecting = true;
2264
+ this._reconnectState.attempts++;
2265
+ console.log(`[NetworkPlugin] Attempting reconnection (${this._reconnectState.attempts}/${this._config.maxReconnectAttempts})`);
2266
+ this._reconnectTimer = setTimeout(async () => {
2267
+ try {
2268
+ await this._networkService.connect(this._lastConnectOptions);
2269
+ const result = await this._networkService.call("reconnect", {
2270
+ playerId: this._reconnectState.playerId,
2271
+ roomId: this._reconnectState.roomId,
2272
+ token: this._reconnectState.token
2273
+ });
2274
+ if (result.success) {
2275
+ console.log("[NetworkPlugin] Reconnection successful");
2276
+ this._reconnectState.isReconnecting = false;
2277
+ this._reconnectState.attempts = 0;
2278
+ if (result.state) {
2279
+ this._handleFullState(result.state);
2280
+ }
2281
+ } else {
2282
+ console.error("[NetworkPlugin] Reconnection rejected:", result.error);
2283
+ this._attemptReconnect();
2284
+ }
2285
+ } catch (err) {
2286
+ console.error("[NetworkPlugin] Reconnection failed:", err);
2287
+ if (this._reconnectState) {
2288
+ this._reconnectState.isReconnecting = false;
2289
+ }
2290
+ this._attemptReconnect();
2291
+ }
2292
+ }, this._config.reconnectInterval);
2293
+ }
2294
+ _handleFullState(data) {
2295
+ this._syncSystem.clearSnapshots();
2296
+ for (const entityData of data.entities) {
2297
+ this._spawnSystem.handleSpawn(entityData);
2298
+ if (entityData.state) {
2299
+ this._syncSystem.handleSyncData({
2300
+ frame: data.frame,
2301
+ timestamp: data.timestamp,
2302
+ entities: [
2303
+ entityData.state
2304
+ ]
2305
+ });
866
2306
  }
867
2307
  }
868
- if (targetTime > this._buffer[this._buffer.length - 1].timestamp) {
869
- const prev = this._buffer[this._buffer.length - 2];
870
- const next = this._buffer[this._buffer.length - 1];
871
- const duration = next.timestamp - prev.timestamp;
872
- const t = duration > 0 ? (targetTime - prev.timestamp) / duration : 1;
873
- return [
874
- prev,
875
- next,
876
- Math.min(t, 2)
877
- ];
2308
+ }
2309
+ _clearReconnectTimer() {
2310
+ if (this._reconnectTimer) {
2311
+ clearTimeout(this._reconnectTimer);
2312
+ this._reconnectTimer = null;
878
2313
  }
879
- return null;
880
2314
  }
881
- /**
882
- * @zh 获取最新快照
883
- * @en Get latest snapshot
884
- */
885
- getLatest() {
886
- return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
2315
+ _generateReconnectToken() {
2316
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
887
2317
  }
888
- /**
889
- * @zh 获取特定时间之后的所有快照
890
- * @en Get all snapshots after a specific time
891
- */
892
- getSnapshotsAfter(timestamp) {
893
- return this._buffer.filter((s) => s.timestamp > timestamp);
2318
+ _cleanup() {
2319
+ this._localPlayerId = 0;
2320
+ this._syncSystem?.clearSnapshots();
2321
+ this._predictionSystem?.reset();
2322
+ this._inputSystem?.reset();
894
2323
  }
2324
+ // =========================================================================
2325
+ // Game API | 游戏接口
2326
+ // =========================================================================
895
2327
  /**
896
- * @zh 清空缓冲区
897
- * @en Clear buffer
2328
+ * @zh 注册预制体工厂
2329
+ * @en Register prefab factory
898
2330
  */
899
- clear() {
900
- this._buffer.length = 0;
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;
2331
+ registerPrefab(prefabType, factory) {
2332
+ this._spawnSystem?.registerPrefab(prefabType, factory);
939
2333
  }
940
- return [
941
- newValue,
942
- newVelocity
943
- ];
944
- }
945
- __name(smoothDamp, "smoothDamp");
946
-
947
- // src/sync/TransformInterpolator.ts
948
- var _TransformInterpolator = class _TransformInterpolator {
949
2334
  /**
950
- * @zh 在两个变换状态之间插值
951
- * @en Interpolate between two transform states
2335
+ * @zh 发送移动输入
2336
+ * @en Send move input
952
2337
  */
953
- interpolate(from, to, t) {
954
- return {
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
- };
2338
+ sendMoveInput(x, y) {
2339
+ this._inputSystem?.addMoveInput(x, y);
959
2340
  }
960
2341
  /**
961
- * @zh 基于速度外推变换状态
962
- * @en Extrapolate transform state based on velocity
2342
+ * @zh 发送动作输入
2343
+ * @en Send action input
963
2344
  */
964
- extrapolate(state, deltaTime) {
965
- return {
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
- };
2345
+ sendActionInput(action) {
2346
+ this._inputSystem?.addActionInput(action);
973
2347
  }
974
- };
975
- __name(_TransformInterpolator, "TransformInterpolator");
976
- var TransformInterpolator = _TransformInterpolator;
977
- var _HermiteTransformInterpolator = class _HermiteTransformInterpolator {
978
2348
  /**
979
- * @zh 使用赫尔米特插值
980
- * @en Use Hermite interpolation
2349
+ * @zh 设置本地玩家网络 ID(用于预测)
2350
+ * @en Set local player network ID (for prediction)
981
2351
  */
982
- interpolate(from, to, t) {
983
- const t2 = t * t;
984
- const t3 = t2 * t;
985
- const h00 = 2 * t3 - 3 * t2 + 1;
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
- };
2352
+ setLocalPlayerNetId(netId) {
2353
+ if (this._predictionSystem) {
2354
+ this._predictionSystem.setLocalPlayerNetId(netId);
2355
+ }
1040
2356
  }
1041
2357
  /**
1042
- * @zh 获取当前预测状态
1043
- * @en Get current predicted state
2358
+ * @zh 启用/禁用预测
2359
+ * @en Enable/disable prediction
1044
2360
  */
1045
- get predictedState() {
1046
- return this._predictedState;
2361
+ setPredictionEnabled(enabled) {
2362
+ if (this._predictionSystem) {
2363
+ this._predictionSystem.enabled = enabled;
2364
+ }
1047
2365
  }
2366
+ // =========================================================================
2367
+ // AOI API | AOI 接口
2368
+ // =========================================================================
1048
2369
  /**
1049
- * @zh 获取校正偏移
1050
- * @en Get correction offset
2370
+ * @zh 添加 AOI 观察者(玩家)
2371
+ * @en Add AOI observer (player)
1051
2372
  */
1052
- get correctionOffset() {
1053
- return this._correctionOffset;
2373
+ addAOIObserver(netId, x, y, viewRange) {
2374
+ this._aoiSystem?.addObserver(netId, x, y, viewRange);
1054
2375
  }
1055
2376
  /**
1056
- * @zh 获取待确认输入数量
1057
- * @en Get pending input count
2377
+ * @zh 移除 AOI 观察者
2378
+ * @en Remove AOI observer
1058
2379
  */
1059
- get pendingInputCount() {
1060
- return this._pendingInputs.length;
2380
+ removeAOIObserver(netId) {
2381
+ this._aoiSystem?.removeObserver(netId);
1061
2382
  }
1062
2383
  /**
1063
- * @zh 记录并预测输入
1064
- * @en Record and predict input
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
2384
+ * @zh 更新 AOI 观察者位置
2385
+ * @en Update AOI observer position
1070
2386
  */
1071
- recordInput(input, currentState, deltaTime) {
1072
- this._currentSequence++;
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;
2387
+ updateAOIObserverPosition(netId, x, y) {
2388
+ this._aoiSystem?.updateObserverPosition(netId, x, y);
1084
2389
  }
1085
2390
  /**
1086
- * @zh 获取下一个要发送的输入
1087
- * @en Get next input to send
2391
+ * @zh 获取观察者可见的实体
2392
+ * @en Get entities visible to observer
1088
2393
  */
1089
- getInputToSend() {
1090
- return this._pendingInputs.length > 0 ? this._pendingInputs[this._pendingInputs.length - 1] : null;
2394
+ getVisibleEntities(observerNetId) {
2395
+ return this._aoiSystem?.getVisibleEntities(observerNetId) ?? [];
1091
2396
  }
1092
2397
  /**
1093
- * @zh 获取当前序列号
1094
- * @en Get current sequence number
2398
+ * @zh 检查是否可见
2399
+ * @en Check if visible
1095
2400
  */
1096
- get currentSequence() {
1097
- return this._currentSequence;
2401
+ canSee(observerNetId, targetNetId) {
2402
+ return this._aoiSystem?.canSee(observerNetId, targetNetId) ?? true;
1098
2403
  }
1099
2404
  /**
1100
- * @zh 处理服务器状态并进行校正
1101
- * @en Process server state and reconcile
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
2405
+ * @zh 启用/禁用 AOI
2406
+ * @en Enable/disable AOI
1107
2407
  */
1108
- reconcile(serverState, acknowledgedSequence, stateGetter, deltaTime) {
1109
- this._lastServerState = serverState;
1110
- this._lastAcknowledgedSequence = acknowledgedSequence;
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;
2408
+ setAOIEnabled(enabled) {
2409
+ if (this._aoiSystem) {
2410
+ this._aoiSystem.enabled = enabled;
1127
2411
  }
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
2412
  }
1149
2413
  };
1150
- __name(_ClientPrediction, "ClientPrediction");
1151
- var ClientPrediction = _ClientPrediction;
1152
- function createClientPrediction(predictor, config) {
1153
- return new ClientPrediction(predictor, config);
1154
- }
1155
- __name(createClientPrediction, "createClientPrediction");
2414
+ __name(_NetworkPlugin, "NetworkPlugin");
2415
+ var NetworkPlugin = _NetworkPlugin;
1156
2416
 
1157
2417
  // src/nodes/NetworkNodes.ts
1158
2418
  var IsLocalPlayerTemplate = {
@@ -1184,15 +2444,15 @@ var IsLocalPlayerTemplate = {
1184
2444
  };
1185
2445
  var _IsLocalPlayerExecutor = class _IsLocalPlayerExecutor {
1186
2446
  execute(node, context) {
1187
- var _a;
2447
+ var _a2;
1188
2448
  const ctx = context;
1189
2449
  let isLocal = false;
1190
2450
  if (ctx.entity) {
1191
- const identity = ctx.entity.getComponent((_a = class {
2451
+ const identity = ctx.entity.getComponent((_a2 = class {
1192
2452
  constructor() {
1193
2453
  __publicField(this, "bIsLocalPlayer", false);
1194
2454
  }
1195
- }, __name(_a, "NetworkIdentity"), _a));
2455
+ }, __name(_a2, "NetworkIdentity"), _a2));
1196
2456
  if (identity) {
1197
2457
  isLocal = identity.bIsLocalPlayer;
1198
2458
  }
@@ -1272,15 +2532,15 @@ var HasAuthorityTemplate = {
1272
2532
  };
1273
2533
  var _HasAuthorityExecutor = class _HasAuthorityExecutor {
1274
2534
  execute(node, context) {
1275
- var _a;
2535
+ var _a2;
1276
2536
  const ctx = context;
1277
2537
  let hasAuthority = false;
1278
2538
  if (ctx.entity) {
1279
- const identity = ctx.entity.getComponent((_a = class {
2539
+ const identity = ctx.entity.getComponent((_a2 = class {
1280
2540
  constructor() {
1281
2541
  __publicField(this, "bHasAuthority", false);
1282
2542
  }
1283
- }, __name(_a, "NetworkIdentity"), _a));
2543
+ }, __name(_a2, "NetworkIdentity"), _a2));
1284
2544
  if (identity) {
1285
2545
  hasAuthority = identity.bHasAuthority;
1286
2546
  }
@@ -1327,17 +2587,17 @@ var GetNetworkIdTemplate = {
1327
2587
  };
1328
2588
  var _GetNetworkIdExecutor = class _GetNetworkIdExecutor {
1329
2589
  execute(node, context) {
1330
- var _a;
2590
+ var _a2;
1331
2591
  const ctx = context;
1332
2592
  let netId = 0;
1333
2593
  let ownerId = 0;
1334
2594
  if (ctx.entity) {
1335
- const identity = ctx.entity.getComponent((_a = class {
2595
+ const identity = ctx.entity.getComponent((_a2 = class {
1336
2596
  constructor() {
1337
2597
  __publicField(this, "netId", 0);
1338
2598
  __publicField(this, "ownerId", 0);
1339
2599
  }
1340
- }, __name(_a, "NetworkIdentity"), _a));
2600
+ }, __name(_a2, "NetworkIdentity"), _a2));
1341
2601
  if (identity) {
1342
2602
  netId = identity.netId;
1343
2603
  ownerId = identity.ownerId;
@@ -1424,6 +2684,7 @@ var NetworkNodeDefinitions = {
1424
2684
  };
1425
2685
  export {
1426
2686
  ClientPrediction,
2687
+ DeltaFlags,
1427
2688
  GameNetworkService,
1428
2689
  GetLocalPlayerIdExecutor,
1429
2690
  GetLocalPlayerIdTemplate,
@@ -1436,11 +2697,15 @@ export {
1436
2697
  IsLocalPlayerTemplate,
1437
2698
  IsServerExecutor,
1438
2699
  IsServerTemplate,
2700
+ NetworkAOISystem,
2701
+ NetworkAOISystemToken,
1439
2702
  NetworkIdentity,
1440
2703
  NetworkInputSystem,
1441
2704
  NetworkInputSystemToken,
1442
2705
  NetworkNodeDefinitions,
1443
2706
  NetworkPlugin,
2707
+ NetworkPredictionSystem,
2708
+ NetworkPredictionSystemToken,
1444
2709
  GameNetworkService as NetworkService,
1445
2710
  NetworkServiceToken,
1446
2711
  NetworkSpawnSystem,
@@ -1451,11 +2716,16 @@ export {
1451
2716
  NetworkTransform,
1452
2717
  RpcService,
1453
2718
  SnapshotBuffer,
2719
+ StateDeltaCompressor,
1454
2720
  TransformInterpolator,
1455
2721
  createClientPrediction,
1456
2722
  createHermiteTransformInterpolator,
2723
+ createNetworkAOISystem,
2724
+ createNetworkInputSystem,
2725
+ createNetworkPredictionSystem,
1457
2726
  createNetworkService,
1458
2727
  createSnapshotBuffer,
2728
+ createStateDeltaCompressor,
1459
2729
  createTransformInterpolator,
1460
2730
  gameProtocol,
1461
2731
  lerp,