@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/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/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());
462
- }
463
- /**
464
- * @zh 处理同步消息
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
- * @zh 注销网络实体
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 根据网络 ID 获取实体 ID
505
- * @en Get entity ID by network ID
482
+ * @zh 获取插值延迟
483
+ * @en Get interpolation delay
506
484
  */
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();
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 设置本地玩家 ID
538
- * @en Set local player ID
489
+ * @zh 添加快照
490
+ * @en Add snapshot
539
491
  */
540
- setLocalPlayerId(id) {
541
- this._localPlayerId = id;
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 Handle spawn message
509
+ * @zh 获取用于插值的两个快照
510
+ * @en Get two snapshots for interpolation
546
511
  */
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}`);
512
+ getInterpolationSnapshots(renderTime) {
513
+ if (this._buffer.length < 2) {
552
514
  return null;
553
515
  }
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;
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 Handle despawn message
544
+ * @zh 获取最新快照
545
+ * @en Get latest snapshot
570
546
  */
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);
547
+ getLatest() {
548
+ return this._buffer.length > 0 ? this._buffer[this._buffer.length - 1] : null;
579
549
  }
580
550
  /**
581
- * @zh 注册预制体工厂
582
- * @en Register prefab factory
551
+ * @zh 获取特定时间之后的所有快照
552
+ * @en Get all snapshots after a specific time
583
553
  */
584
- registerPrefab(prefabType, factory) {
585
- this._prefabFactories.set(prefabType, factory);
554
+ getSnapshotsAfter(timestamp) {
555
+ return this._buffer.filter((s) => s.timestamp > timestamp);
586
556
  }
587
557
  /**
588
- * @zh 注销预制体工厂
589
- * @en Unregister prefab factory
558
+ * @zh 清空缓冲区
559
+ * @en Clear buffer
590
560
  */
591
- unregisterPrefab(prefabType) {
592
- this._prefabFactories.delete(prefabType);
593
- }
594
- onDestroy() {
595
- this._prefabFactories.clear();
561
+ clear() {
562
+ this._buffer.length = 0;
596
563
  }
597
564
  };
598
- __name(_NetworkSpawnSystem, "NetworkSpawnSystem");
599
- var NetworkSpawnSystem = _NetworkSpawnSystem;
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/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;
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 Process input queue
612
+ * @zh 在两个变换状态之间插值
613
+ * @en Interpolate between two transform states
614
614
  */
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
- }
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 Add move input
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.push({
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 action input
2186
+ * @zh 添加事件监听器
2187
+ * @en Add event listener
640
2188
  */
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
- });
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._inputQueue.length = 0;
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(_NetworkInputSystem, "NetworkInputSystem");
660
- var NetworkInputSystem = _NetworkInputSystem;
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.0.0");
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.handleSync({
745
- entities: data.entities
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
- * @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();
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
- * @zh 获取用于插值的两个快照
848
- * @en Get two snapshots for interpolation
849
- */
850
- getInterpolationSnapshots(renderTime) {
851
- if (this._buffer.length < 2) {
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
- 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
- ];
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
- 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
- ];
2583
+ }
2584
+ _clearReconnectTimer() {
2585
+ if (this._reconnectTimer) {
2586
+ clearTimeout(this._reconnectTimer);
2587
+ this._reconnectTimer = null;
878
2588
  }
879
- return null;
880
2589
  }
881
- /**
882
- * @zh 获取最新快照
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
- * @zh 获取特定时间之后的所有快照
890
- * @en Get all snapshots after a specific time
891
- */
892
- getSnapshotsAfter(timestamp) {
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 Clear buffer
2603
+ * @zh 注册预制体工厂
2604
+ * @en Register prefab factory
898
2605
  */
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;
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 Interpolate between two transform states
2610
+ * @zh 发送移动输入
2611
+ * @en Send move input
952
2612
  */
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
- };
2613
+ sendMoveInput(x, y) {
2614
+ this._inputSystem?.addMoveInput(x, y);
959
2615
  }
960
2616
  /**
961
- * @zh 基于速度外推变换状态
962
- * @en Extrapolate transform state based on velocity
2617
+ * @zh 发送动作输入
2618
+ * @en Send action input
963
2619
  */
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
- };
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 Use Hermite interpolation
2624
+ * @zh 设置本地玩家网络 ID(用于预测)
2625
+ * @en Set local player network ID (for prediction)
981
2626
  */
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
- };
2627
+ setLocalPlayerNetId(netId) {
2628
+ if (this._predictionSystem) {
2629
+ this._predictionSystem.setLocalPlayerNetId(netId);
2630
+ }
1040
2631
  }
1041
2632
  /**
1042
- * @zh 获取当前预测状态
1043
- * @en Get current predicted state
2633
+ * @zh 启用/禁用预测
2634
+ * @en Enable/disable prediction
1044
2635
  */
1045
- get predictedState() {
1046
- return this._predictedState;
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 Get correction offset
2645
+ * @zh 添加 AOI 观察者(玩家)
2646
+ * @en Add AOI observer (player)
1051
2647
  */
1052
- get correctionOffset() {
1053
- return this._correctionOffset;
2648
+ addAOIObserver(netId, x, y, viewRange) {
2649
+ this._aoiSystem?.addObserver(netId, x, y, viewRange);
1054
2650
  }
1055
2651
  /**
1056
- * @zh 获取待确认输入数量
1057
- * @en Get pending input count
2652
+ * @zh 移除 AOI 观察者
2653
+ * @en Remove AOI observer
1058
2654
  */
1059
- get pendingInputCount() {
1060
- return this._pendingInputs.length;
2655
+ removeAOIObserver(netId) {
2656
+ this._aoiSystem?.removeObserver(netId);
1061
2657
  }
1062
2658
  /**
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
2659
+ * @zh 更新 AOI 观察者位置
2660
+ * @en Update AOI observer position
1070
2661
  */
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;
2662
+ updateAOIObserverPosition(netId, x, y) {
2663
+ this._aoiSystem?.updateObserverPosition(netId, x, y);
1084
2664
  }
1085
2665
  /**
1086
- * @zh 获取下一个要发送的输入
1087
- * @en Get next input to send
2666
+ * @zh 获取观察者可见的实体
2667
+ * @en Get entities visible to observer
1088
2668
  */
1089
- getInputToSend() {
1090
- return this._pendingInputs.length > 0 ? this._pendingInputs[this._pendingInputs.length - 1] : null;
2669
+ getVisibleEntities(observerNetId) {
2670
+ return this._aoiSystem?.getVisibleEntities(observerNetId) ?? [];
1091
2671
  }
1092
2672
  /**
1093
- * @zh 获取当前序列号
1094
- * @en Get current sequence number
2673
+ * @zh 检查是否可见
2674
+ * @en Check if visible
1095
2675
  */
1096
- get currentSequence() {
1097
- return this._currentSequence;
2676
+ canSee(observerNetId, targetNetId) {
2677
+ return this._aoiSystem?.canSee(observerNetId, targetNetId) ?? true;
1098
2678
  }
1099
2679
  /**
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
2680
+ * @zh 启用/禁用 AOI
2681
+ * @en Enable/disable AOI
1107
2682
  */
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;
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(_ClientPrediction, "ClientPrediction");
1151
- var ClientPrediction = _ClientPrediction;
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 _a;
2722
+ var _a2;
1188
2723
  const ctx = context;
1189
2724
  let isLocal = false;
1190
2725
  if (ctx.entity) {
1191
- const identity = ctx.entity.getComponent((_a = class {
2726
+ const identity = ctx.entity.getComponent((_a2 = class {
1192
2727
  constructor() {
1193
2728
  __publicField(this, "bIsLocalPlayer", false);
1194
2729
  }
1195
- }, __name(_a, "NetworkIdentity"), _a));
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 _a;
2810
+ var _a2;
1276
2811
  const ctx = context;
1277
2812
  let hasAuthority = false;
1278
2813
  if (ctx.entity) {
1279
- const identity = ctx.entity.getComponent((_a = class {
2814
+ const identity = ctx.entity.getComponent((_a2 = class {
1280
2815
  constructor() {
1281
2816
  __publicField(this, "bHasAuthority", false);
1282
2817
  }
1283
- }, __name(_a, "NetworkIdentity"), _a));
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 _a;
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((_a = class {
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(_a, "NetworkIdentity"), _a));
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,