@esengine/server 2.0.0 → 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.
@@ -1,6 +1,7 @@
1
1
  import { World, Scene, EntitySystem, Entity } from '@esengine/ecs-framework';
2
2
  export { Component, Entity, EntitySystem, Scene, SyncFieldMetadata, SyncMetadata, SyncOperation, SyncType, World, clearChanges, getSyncMetadata, hasChanges, hasSyncFields, initChangeTracker, sync } from '@esengine/ecs-framework';
3
3
  import { R as Room, P as Player } from '../Room-BnKpl5Sj.js';
4
+ export { I as IPlayer } from '../Room-BnKpl5Sj.js';
4
5
  export { o as onMessage } from '../decorators-DY8nZ8Nh.js';
5
6
  import '@esengine/rpc';
6
7
 
@@ -24,6 +25,12 @@ interface ECSRoomConfig {
24
25
  * @en Whether to enable delta sync
25
26
  */
26
27
  enableDeltaSync: boolean;
28
+ /**
29
+ * @zh 是否启用自动网络实体广播(基于 @NetworkEntity 装饰器)
30
+ * @en Whether to enable automatic network entity broadcasting (based on @NetworkEntity decorator)
31
+ * @default true
32
+ */
33
+ enableAutoNetworkEntity: boolean;
27
34
  }
28
35
  /**
29
36
  * @zh ECS 房间基类,带有 ECS World 支持和自动状态同步
@@ -74,12 +81,22 @@ declare abstract class ECSRoom<TState = any, TPlayerData = Record<string, unknow
74
81
  * @en Player ID to Entity mapping
75
82
  */
76
83
  private readonly _playerEntities;
84
+ /**
85
+ * @zh 网络实体映射(实体 ID -> prefabType)
86
+ * @en Network entity mapping (entity ID -> prefabType)
87
+ */
88
+ private readonly _networkEntities;
77
89
  /**
78
90
  * @zh 上次同步时间
79
91
  * @en Last sync time
80
92
  */
81
93
  private _lastSyncTime;
82
94
  constructor(ecsConfig?: Partial<ECSRoomConfig>);
95
+ /**
96
+ * @zh 设置自动网络实体广播
97
+ * @en Setup automatic network entity broadcasting
98
+ */
99
+ private _setupAutoNetworkEntity;
83
100
  /**
84
101
  * @zh 添加系统到场景
85
102
  * @en Add system to scene
@@ -156,4 +173,4 @@ declare abstract class ECSRoom<TState = any, TPlayerData = Record<string, unknow
156
173
  private _clearChangeTrackers;
157
174
  }
158
175
 
159
- export { ECSRoom, type ECSRoomConfig };
176
+ export { ECSRoom, type ECSRoomConfig, Player };
package/dist/ecs/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { Room } from '../chunk-O3VN2QVN.js';
2
- export { onMessage } from '../chunk-O3VN2QVN.js';
2
+ export { Player, onMessage } from '../chunk-O3VN2QVN.js';
3
3
  import { __name, __publicField } from '../chunk-T626JPC7.js';
4
- import { Core, encodeDespawn, encodeSnapshot, SyncOperation, encodeSpawn, SYNC_METADATA, CHANGE_TRACKER, initChangeTracker } from '@esengine/ecs-framework';
4
+ import { Core, ECSEventType, NETWORK_ENTITY_METADATA, encodeDespawn, encodeSnapshot, SyncOperation, encodeSpawn, SYNC_METADATA, CHANGE_TRACKER, initChangeTracker } from '@esengine/ecs-framework';
5
5
  export { SyncOperation, clearChanges, getSyncMetadata, hasChanges, hasSyncFields, initChangeTracker, sync } from '@esengine/ecs-framework';
6
6
 
7
7
  var DEFAULT_ECS_CONFIG = {
8
8
  syncInterval: 50,
9
- enableDeltaSync: true
9
+ enableDeltaSync: true,
10
+ enableAutoNetworkEntity: true
10
11
  };
11
12
  var NETWORK_ENTITY_OWNER = /* @__PURE__ */ Symbol("NetworkEntityOwner");
12
13
  var _ECSRoom = class _ECSRoom extends Room {
@@ -37,6 +38,11 @@ var _ECSRoom = class _ECSRoom extends Room {
37
38
  * @en Player ID to Entity mapping
38
39
  */
39
40
  __publicField(this, "_playerEntities", /* @__PURE__ */ new Map());
41
+ /**
42
+ * @zh 网络实体映射(实体 ID -> prefabType)
43
+ * @en Network entity mapping (entity ID -> prefabType)
44
+ */
45
+ __publicField(this, "_networkEntities", /* @__PURE__ */ new Map());
40
46
  /**
41
47
  * @zh 上次同步时间
42
48
  * @en Last sync time
@@ -51,6 +57,36 @@ var _ECSRoom = class _ECSRoom extends Room {
51
57
  this.scene = this.world.createScene("game");
52
58
  this.world.setSceneActive("game", true);
53
59
  this.world.start();
60
+ if (this.ecsConfig.enableAutoNetworkEntity) {
61
+ this._setupAutoNetworkEntity();
62
+ }
63
+ }
64
+ /**
65
+ * @zh 设置自动网络实体广播
66
+ * @en Setup automatic network entity broadcasting
67
+ */
68
+ _setupAutoNetworkEntity() {
69
+ this.scene.eventSystem.on(ECSEventType.COMPONENT_ADDED, (event) => {
70
+ const { entity, component } = event;
71
+ const metadata = component.constructor[NETWORK_ENTITY_METADATA];
72
+ if (metadata?.autoSpawn) {
73
+ if (!this._networkEntities.has(entity.id)) {
74
+ this._networkEntities.set(entity.id, metadata.prefabType);
75
+ this.broadcastSpawn(entity, metadata.prefabType);
76
+ }
77
+ }
78
+ if (metadata?.autoDespawn && !this._networkEntities.has(entity.id)) {
79
+ this._networkEntities.set(entity.id, metadata.prefabType);
80
+ }
81
+ });
82
+ this.scene.eventSystem.on(ECSEventType.ENTITY_DESTROYED, (event) => {
83
+ const { entityId } = event;
84
+ if (this._networkEntities.has(entityId)) {
85
+ const despawnData = encodeDespawn(entityId);
86
+ this.broadcastBinary(despawnData);
87
+ this._networkEntities.delete(entityId);
88
+ }
89
+ });
54
90
  }
55
91
  // =========================================================================
56
92
  // Scene Management | 场景管理
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ecs/ECSRoom.ts"],"names":["DEFAULT_ECS_CONFIG","syncInterval","enableDeltaSync","NETWORK_ENTITY_OWNER","ECSRoom","Room","ecsConfig","world","worldId","scene","_playerEntities","Map","_lastSyncTime","Date","now","Math","random","toString","slice","Core","worldManager","createWorld","createScene","setSceneActive","start","addSystem","system","createEntity","name","createPlayerEntity","playerId","entityName","entity","set","getPlayerEntity","get","destroyPlayerEntity","despawnData","encodeDespawn","id","broadcastBinary","destroy","delete","data","player","players","sendBinary","send","Array","from","sendFullState","entities","_getSyncEntities","length","_initComponentTrackers","encodeSnapshot","SyncOperation","FULL","broadcastSpawn","prefabType","encodeSpawn","broadcastDelta","changedEntities","filter","_hasChanges","DELTA","_clearChangeTrackers","onTick","_dt","onLeave","reason","onDispose","clear","removeWorld","buffer","_hasSyncComponents","push","component","components","metadata","SYNC_METADATA","fields","tracker","CHANGE_TRACKER","hasChanges","initChangeTracker"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @zh ECS 房间基类\n * @en ECS Room base class\n */\n\nimport {\n Core,\n Scene,\n World,\n Entity,\n EntitySystem,\n type Component,\n // Sync\n SyncOperation,\n SYNC_METADATA,\n CHANGE_TRACKER,\n type SyncMetadata,\n type ChangeTracker,\n encodeSnapshot,\n encodeSpawn,\n encodeDespawn,\n initChangeTracker,\n} from '@esengine/ecs-framework';\n\nimport { Room, type RoomOptions } from '../room/Room.js';\nimport type { Player } from '../room/Player.js';\n\n// =============================================================================\n// Types | 类型定义\n// =============================================================================\n\n/**\n * @zh ECS 房间配置\n * @en ECS room configuration\n */\nexport interface ECSRoomConfig {\n /**\n * @zh 状态同步间隔(毫秒)\n * @en State sync interval in milliseconds\n */\n syncInterval: number;\n\n /**\n * @zh 是否启用增量同步\n * @en Whether to enable delta sync\n */\n enableDeltaSync: boolean;\n}\n\nconst DEFAULT_ECS_CONFIG: ECSRoomConfig = {\n syncInterval: 50, // 20 Hz\n enableDeltaSync: true,\n};\n\n/**\n * @zh 网络实体标识组件\n * @en Network entity identity component\n */\nconst NETWORK_ENTITY_OWNER = Symbol('NetworkEntityOwner');\n\n// =============================================================================\n// ECSRoom | ECS 房间\n// =============================================================================\n\n/**\n * @zh ECS 房间基类,带有 ECS World 支持和自动状态同步\n * @en ECS Room base class with ECS World support and automatic state synchronization\n *\n * @example\n * ```typescript\n * // 服务端启动\n * Core.create();\n * setInterval(() => Core.update(1/60), 16);\n *\n * // 定义房间\n * class GameRoom extends ECSRoom {\n * onCreate() {\n * this.addSystem(new PhysicsSystem());\n * }\n *\n * onJoin(player: Player) {\n * const entity = this.createPlayerEntity(player.id);\n * entity.addComponent(new PlayerComponent());\n * }\n * }\n * ```\n */\nexport abstract class ECSRoom<TState = any, TPlayerData = Record<string, unknown>> extends Room<TState, TPlayerData> {\n /**\n * @zh ECS World(由 Core.worldManager 管理)\n * @en ECS World (managed by Core.worldManager)\n */\n protected readonly world: World;\n\n /**\n * @zh World 在 WorldManager 中的 ID\n * @en World ID in WorldManager\n */\n protected readonly worldId: string;\n\n /**\n * @zh 房间的主场景\n * @en Room's main scene\n */\n protected readonly scene: Scene;\n\n /**\n * @zh ECS 配置\n * @en ECS configuration\n */\n protected readonly ecsConfig: ECSRoomConfig;\n\n /**\n * @zh 玩家 ID 到实体的映射\n * @en Player ID to Entity mapping\n */\n private readonly _playerEntities: Map<string, Entity> = new Map();\n\n /**\n * @zh 上次同步时间\n * @en Last sync time\n */\n private _lastSyncTime: number = 0;\n\n constructor(ecsConfig?: Partial<ECSRoomConfig>) {\n super();\n this.ecsConfig = { ...DEFAULT_ECS_CONFIG, ...ecsConfig };\n\n this.worldId = `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n this.world = Core.worldManager.createWorld(this.worldId);\n this.scene = this.world.createScene('game');\n this.world.setSceneActive('game', true);\n this.world.start();\n }\n\n // =========================================================================\n // Scene Management | 场景管理\n // =========================================================================\n\n /**\n * @zh 添加系统到场景\n * @en Add system to scene\n */\n protected addSystem(system: EntitySystem): void {\n this.scene.addSystem(system);\n }\n\n /**\n * @zh 创建实体\n * @en Create entity\n */\n protected createEntity(name?: string): Entity {\n return this.scene.createEntity(name ?? `entity_${Date.now()}`);\n }\n\n /**\n * @zh 为玩家创建实体\n * @en Create entity for player\n *\n * @param playerId - @zh 玩家 ID @en Player ID\n * @param name - @zh 实体名称 @en Entity name\n * @returns @zh 创建的实体 @en Created entity\n */\n protected createPlayerEntity(playerId: string, name?: string): Entity {\n const entityName = name ?? `player_${playerId}`;\n const entity = this.scene.createEntity(entityName);\n (entity as any)[NETWORK_ENTITY_OWNER] = playerId;\n this._playerEntities.set(playerId, entity);\n return entity;\n }\n\n /**\n * @zh 获取玩家的实体\n * @en Get player's entity\n */\n protected getPlayerEntity(playerId: string): Entity | undefined {\n return this._playerEntities.get(playerId);\n }\n\n /**\n * @zh 销毁玩家的实体\n * @en Destroy player's entity\n */\n protected destroyPlayerEntity(playerId: string): void {\n const entity = this._playerEntities.get(playerId);\n if (entity) {\n const despawnData = encodeDespawn(entity.id);\n this.broadcastBinary(despawnData);\n entity.destroy();\n this._playerEntities.delete(playerId);\n }\n }\n\n // =========================================================================\n // State Sync | 状态同步\n // =========================================================================\n\n /**\n * @zh 广播二进制数据\n * @en Broadcast binary data\n */\n protected broadcastBinary(data: Uint8Array): void {\n for (const player of this.players) {\n this.sendBinary(player, data);\n }\n }\n\n /**\n * @zh 发送二进制数据给指定玩家\n * @en Send binary data to specific player\n */\n protected sendBinary(player: Player<TPlayerData>, data: Uint8Array): void {\n player.send('$sync', { data: Array.from(data) });\n }\n\n /**\n * @zh 发送完整状态给玩家(用于玩家刚加入时)\n * @en Send full state to player (for when player just joined)\n */\n protected sendFullState(player: Player<TPlayerData>): void {\n const entities = this._getSyncEntities();\n if (entities.length === 0) return;\n\n for (const entity of entities) {\n this._initComponentTrackers(entity);\n }\n\n const data = encodeSnapshot(entities, SyncOperation.FULL);\n this.sendBinary(player, data);\n }\n\n /**\n * @zh 广播实体生成\n * @en Broadcast entity spawn\n */\n protected broadcastSpawn(entity: Entity, prefabType?: string): void {\n this._initComponentTrackers(entity);\n const data = encodeSpawn(entity, prefabType);\n this.broadcastBinary(data);\n }\n\n /**\n * @zh 广播增量状态更新\n * @en Broadcast delta state update\n */\n protected broadcastDelta(): void {\n const entities = this._getSyncEntities();\n const changedEntities = entities.filter(entity => this._hasChanges(entity));\n\n if (changedEntities.length === 0) return;\n\n const data = encodeSnapshot(changedEntities, SyncOperation.DELTA);\n this.broadcastBinary(data);\n this._clearChangeTrackers(changedEntities);\n }\n\n // =========================================================================\n // Lifecycle Overrides | 生命周期重载\n // =========================================================================\n\n /**\n * @zh 游戏循环,处理状态同步\n * @en Game tick, handles state sync\n */\n override onTick(_dt: number): void {\n if (this.ecsConfig.enableDeltaSync) {\n const now = Date.now();\n if (now - this._lastSyncTime >= this.ecsConfig.syncInterval) {\n this._lastSyncTime = now;\n this.broadcastDelta();\n }\n }\n }\n\n /**\n * @zh 玩家离开时自动销毁其实体\n * @en Auto destroy player entity when leaving\n */\n override async onLeave(player: Player<TPlayerData>, reason?: string): Promise<void> {\n this.destroyPlayerEntity(player.id);\n }\n\n /**\n * @zh 房间销毁时从 WorldManager 移除 World\n * @en Remove World from WorldManager when room is disposed\n */\n override onDispose(): void {\n this._playerEntities.clear();\n Core.worldManager.removeWorld(this.worldId);\n }\n\n // =========================================================================\n // Internal | 内部方法\n // =========================================================================\n\n private _getSyncEntities(): Entity[] {\n const entities: Entity[] = [];\n for (const entity of this.scene.entities.buffer) {\n if (this._hasSyncComponents(entity)) {\n entities.push(entity);\n }\n }\n return entities;\n }\n\n private _hasSyncComponents(entity: Entity): boolean {\n for (const component of entity.components) {\n const metadata: SyncMetadata | undefined = (component.constructor as any)[SYNC_METADATA];\n if (metadata && metadata.fields.length > 0) {\n return true;\n }\n }\n return false;\n }\n\n private _hasChanges(entity: Entity): boolean {\n for (const component of entity.components) {\n const tracker = (component as any)[CHANGE_TRACKER] as ChangeTracker | undefined;\n if (tracker?.hasChanges()) {\n return true;\n }\n }\n return false;\n }\n\n private _initComponentTrackers(entity: Entity): void {\n for (const component of entity.components) {\n const metadata: SyncMetadata | undefined = (component.constructor as any)[SYNC_METADATA];\n if (metadata && metadata.fields.length > 0) {\n initChangeTracker(component);\n }\n }\n }\n\n private _clearChangeTrackers(entities: Entity[]): void {\n for (const entity of entities) {\n for (const component of entity.components) {\n const tracker = (component as any)[CHANGE_TRACKER] as ChangeTracker | undefined;\n if (tracker) {\n tracker.clear();\n }\n }\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/ecs/ECSRoom.ts"],"names":["DEFAULT_ECS_CONFIG","syncInterval","enableDeltaSync","enableAutoNetworkEntity","NETWORK_ENTITY_OWNER","ECSRoom","Room","ecsConfig","world","worldId","scene","_playerEntities","Map","_networkEntities","_lastSyncTime","Date","now","Math","random","toString","slice","Core","worldManager","createWorld","createScene","setSceneActive","start","_setupAutoNetworkEntity","eventSystem","on","ECSEventType","COMPONENT_ADDED","event","entity","component","metadata","NETWORK_ENTITY_METADATA","autoSpawn","has","id","set","prefabType","broadcastSpawn","autoDespawn","ENTITY_DESTROYED","entityId","despawnData","encodeDespawn","broadcastBinary","delete","addSystem","system","createEntity","name","createPlayerEntity","playerId","entityName","getPlayerEntity","get","destroyPlayerEntity","destroy","data","player","players","sendBinary","send","Array","from","sendFullState","entities","_getSyncEntities","length","_initComponentTrackers","encodeSnapshot","SyncOperation","FULL","encodeSpawn","broadcastDelta","changedEntities","filter","_hasChanges","DELTA","_clearChangeTrackers","onTick","_dt","onLeave","reason","onDispose","clear","removeWorld","buffer","_hasSyncComponents","push","components","SYNC_METADATA","fields","tracker","CHANGE_TRACKER","hasChanges","initChangeTracker"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @zh ECS 房间基类\n * @en ECS Room base class\n */\n\nimport {\n Core,\n Scene,\n World,\n Entity,\n EntitySystem,\n type Component,\n // Sync\n SyncOperation,\n SYNC_METADATA,\n CHANGE_TRACKER,\n type SyncMetadata,\n type ChangeTracker,\n encodeSnapshot,\n encodeSpawn,\n encodeDespawn,\n initChangeTracker,\n // Network Entity\n NETWORK_ENTITY_METADATA,\n type NetworkEntityMetadata,\n // Events\n ECSEventType,\n} from '@esengine/ecs-framework';\n\nimport { Room, type RoomOptions } from '../room/Room.js';\nimport type { Player } from '../room/Player.js';\n\n// =============================================================================\n// Types | 类型定义\n// =============================================================================\n\n/**\n * @zh ECS 房间配置\n * @en ECS room configuration\n */\nexport interface ECSRoomConfig {\n /**\n * @zh 状态同步间隔(毫秒)\n * @en State sync interval in milliseconds\n */\n syncInterval: number;\n\n /**\n * @zh 是否启用增量同步\n * @en Whether to enable delta sync\n */\n enableDeltaSync: boolean;\n\n /**\n * @zh 是否启用自动网络实体广播(基于 @NetworkEntity 装饰器)\n * @en Whether to enable automatic network entity broadcasting (based on @NetworkEntity decorator)\n * @default true\n */\n enableAutoNetworkEntity: boolean;\n}\n\nconst DEFAULT_ECS_CONFIG: ECSRoomConfig = {\n syncInterval: 50, // 20 Hz\n enableDeltaSync: true,\n enableAutoNetworkEntity: true,\n};\n\n/**\n * @zh 网络实体标识组件\n * @en Network entity identity component\n */\nconst NETWORK_ENTITY_OWNER = Symbol('NetworkEntityOwner');\n\n// =============================================================================\n// ECSRoom | ECS 房间\n// =============================================================================\n\n/**\n * @zh ECS 房间基类,带有 ECS World 支持和自动状态同步\n * @en ECS Room base class with ECS World support and automatic state synchronization\n *\n * @example\n * ```typescript\n * // 服务端启动\n * Core.create();\n * setInterval(() => Core.update(1/60), 16);\n *\n * // 定义房间\n * class GameRoom extends ECSRoom {\n * onCreate() {\n * this.addSystem(new PhysicsSystem());\n * }\n *\n * onJoin(player: Player) {\n * const entity = this.createPlayerEntity(player.id);\n * entity.addComponent(new PlayerComponent());\n * }\n * }\n * ```\n */\nexport abstract class ECSRoom<TState = any, TPlayerData = Record<string, unknown>> extends Room<TState, TPlayerData> {\n /**\n * @zh ECS World(由 Core.worldManager 管理)\n * @en ECS World (managed by Core.worldManager)\n */\n protected readonly world: World;\n\n /**\n * @zh World 在 WorldManager 中的 ID\n * @en World ID in WorldManager\n */\n protected readonly worldId: string;\n\n /**\n * @zh 房间的主场景\n * @en Room's main scene\n */\n protected readonly scene: Scene;\n\n /**\n * @zh ECS 配置\n * @en ECS configuration\n */\n protected readonly ecsConfig: ECSRoomConfig;\n\n /**\n * @zh 玩家 ID 到实体的映射\n * @en Player ID to Entity mapping\n */\n private readonly _playerEntities: Map<string, Entity> = new Map();\n\n /**\n * @zh 网络实体映射(实体 ID -> prefabType)\n * @en Network entity mapping (entity ID -> prefabType)\n */\n private readonly _networkEntities: Map<number, string> = new Map();\n\n /**\n * @zh 上次同步时间\n * @en Last sync time\n */\n private _lastSyncTime: number = 0;\n\n constructor(ecsConfig?: Partial<ECSRoomConfig>) {\n super();\n this.ecsConfig = { ...DEFAULT_ECS_CONFIG, ...ecsConfig };\n\n this.worldId = `room_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;\n this.world = Core.worldManager.createWorld(this.worldId);\n this.scene = this.world.createScene('game');\n this.world.setSceneActive('game', true);\n this.world.start();\n\n // 设置自动网络实体广播\n if (this.ecsConfig.enableAutoNetworkEntity) {\n this._setupAutoNetworkEntity();\n }\n }\n\n /**\n * @zh 设置自动网络实体广播\n * @en Setup automatic network entity broadcasting\n */\n private _setupAutoNetworkEntity(): void {\n // 监听组件添加事件,自动广播 spawn\n this.scene.eventSystem.on(ECSEventType.COMPONENT_ADDED, (event: any) => {\n const { entity, component } = event;\n const metadata: NetworkEntityMetadata | undefined =\n (component.constructor as any)[NETWORK_ENTITY_METADATA];\n\n if (metadata?.autoSpawn) {\n // 避免重复广播同一实体\n if (!this._networkEntities.has(entity.id)) {\n this._networkEntities.set(entity.id, metadata.prefabType);\n this.broadcastSpawn(entity, metadata.prefabType);\n }\n }\n\n // 记录需要自动 despawn 的实体\n if (metadata?.autoDespawn && !this._networkEntities.has(entity.id)) {\n this._networkEntities.set(entity.id, metadata.prefabType);\n }\n });\n\n // 监听实体销毁事件,自动广播 despawn\n this.scene.eventSystem.on(ECSEventType.ENTITY_DESTROYED, (event: any) => {\n const { entityId } = event;\n if (this._networkEntities.has(entityId)) {\n const despawnData = encodeDespawn(entityId);\n this.broadcastBinary(despawnData);\n this._networkEntities.delete(entityId);\n }\n });\n }\n\n // =========================================================================\n // Scene Management | 场景管理\n // =========================================================================\n\n /**\n * @zh 添加系统到场景\n * @en Add system to scene\n */\n protected addSystem(system: EntitySystem): void {\n this.scene.addSystem(system);\n }\n\n /**\n * @zh 创建实体\n * @en Create entity\n */\n protected createEntity(name?: string): Entity {\n return this.scene.createEntity(name ?? `entity_${Date.now()}`);\n }\n\n /**\n * @zh 为玩家创建实体\n * @en Create entity for player\n *\n * @param playerId - @zh 玩家 ID @en Player ID\n * @param name - @zh 实体名称 @en Entity name\n * @returns @zh 创建的实体 @en Created entity\n */\n protected createPlayerEntity(playerId: string, name?: string): Entity {\n const entityName = name ?? `player_${playerId}`;\n const entity = this.scene.createEntity(entityName);\n (entity as any)[NETWORK_ENTITY_OWNER] = playerId;\n this._playerEntities.set(playerId, entity);\n return entity;\n }\n\n /**\n * @zh 获取玩家的实体\n * @en Get player's entity\n */\n protected getPlayerEntity(playerId: string): Entity | undefined {\n return this._playerEntities.get(playerId);\n }\n\n /**\n * @zh 销毁玩家的实体\n * @en Destroy player's entity\n */\n protected destroyPlayerEntity(playerId: string): void {\n const entity = this._playerEntities.get(playerId);\n if (entity) {\n const despawnData = encodeDespawn(entity.id);\n this.broadcastBinary(despawnData);\n entity.destroy();\n this._playerEntities.delete(playerId);\n }\n }\n\n // =========================================================================\n // State Sync | 状态同步\n // =========================================================================\n\n /**\n * @zh 广播二进制数据\n * @en Broadcast binary data\n */\n protected broadcastBinary(data: Uint8Array): void {\n for (const player of this.players) {\n this.sendBinary(player, data);\n }\n }\n\n /**\n * @zh 发送二进制数据给指定玩家\n * @en Send binary data to specific player\n */\n protected sendBinary(player: Player<TPlayerData>, data: Uint8Array): void {\n player.send('$sync', { data: Array.from(data) });\n }\n\n /**\n * @zh 发送完整状态给玩家(用于玩家刚加入时)\n * @en Send full state to player (for when player just joined)\n */\n protected sendFullState(player: Player<TPlayerData>): void {\n const entities = this._getSyncEntities();\n if (entities.length === 0) return;\n\n for (const entity of entities) {\n this._initComponentTrackers(entity);\n }\n\n const data = encodeSnapshot(entities, SyncOperation.FULL);\n this.sendBinary(player, data);\n }\n\n /**\n * @zh 广播实体生成\n * @en Broadcast entity spawn\n */\n protected broadcastSpawn(entity: Entity, prefabType?: string): void {\n this._initComponentTrackers(entity);\n const data = encodeSpawn(entity, prefabType);\n this.broadcastBinary(data);\n }\n\n /**\n * @zh 广播增量状态更新\n * @en Broadcast delta state update\n */\n protected broadcastDelta(): void {\n const entities = this._getSyncEntities();\n const changedEntities = entities.filter(entity => this._hasChanges(entity));\n\n if (changedEntities.length === 0) return;\n\n const data = encodeSnapshot(changedEntities, SyncOperation.DELTA);\n this.broadcastBinary(data);\n this._clearChangeTrackers(changedEntities);\n }\n\n // =========================================================================\n // Lifecycle Overrides | 生命周期重载\n // =========================================================================\n\n /**\n * @zh 游戏循环,处理状态同步\n * @en Game tick, handles state sync\n */\n override onTick(_dt: number): void {\n if (this.ecsConfig.enableDeltaSync) {\n const now = Date.now();\n if (now - this._lastSyncTime >= this.ecsConfig.syncInterval) {\n this._lastSyncTime = now;\n this.broadcastDelta();\n }\n }\n }\n\n /**\n * @zh 玩家离开时自动销毁其实体\n * @en Auto destroy player entity when leaving\n */\n override async onLeave(player: Player<TPlayerData>, reason?: string): Promise<void> {\n this.destroyPlayerEntity(player.id);\n }\n\n /**\n * @zh 房间销毁时从 WorldManager 移除 World\n * @en Remove World from WorldManager when room is disposed\n */\n override onDispose(): void {\n this._playerEntities.clear();\n Core.worldManager.removeWorld(this.worldId);\n }\n\n // =========================================================================\n // Internal | 内部方法\n // =========================================================================\n\n private _getSyncEntities(): Entity[] {\n const entities: Entity[] = [];\n for (const entity of this.scene.entities.buffer) {\n if (this._hasSyncComponents(entity)) {\n entities.push(entity);\n }\n }\n return entities;\n }\n\n private _hasSyncComponents(entity: Entity): boolean {\n for (const component of entity.components) {\n const metadata: SyncMetadata | undefined = (component.constructor as any)[SYNC_METADATA];\n if (metadata && metadata.fields.length > 0) {\n return true;\n }\n }\n return false;\n }\n\n private _hasChanges(entity: Entity): boolean {\n for (const component of entity.components) {\n const tracker = (component as any)[CHANGE_TRACKER] as ChangeTracker | undefined;\n if (tracker?.hasChanges()) {\n return true;\n }\n }\n return false;\n }\n\n private _initComponentTrackers(entity: Entity): void {\n for (const component of entity.components) {\n const metadata: SyncMetadata | undefined = (component.constructor as any)[SYNC_METADATA];\n if (metadata && metadata.fields.length > 0) {\n initChangeTracker(component);\n }\n }\n }\n\n private _clearChangeTrackers(entities: Entity[]): void {\n for (const entity of entities) {\n for (const component of entity.components) {\n const tracker = (component as any)[CHANGE_TRACKER] as ChangeTracker | undefined;\n if (tracker) {\n tracker.clear();\n }\n }\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esengine/server",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Game server framework for ESEngine with file-based routing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,7 +42,7 @@
42
42
  "peerDependencies": {
43
43
  "ws": ">=8.0.0",
44
44
  "jsonwebtoken": ">=9.0.0",
45
- "@esengine/ecs-framework": ">=2.5.0"
45
+ "@esengine/ecs-framework": ">=2.6.0"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "jsonwebtoken": {
@@ -62,7 +62,7 @@
62
62
  "typescript": "^5.7.0",
63
63
  "vitest": "^2.0.0",
64
64
  "ws": "^8.18.0",
65
- "@esengine/ecs-framework": "2.5.0"
65
+ "@esengine/ecs-framework": "2.6.0"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"