Entity Lifecycle 与 State Machine 设计
这篇文档解决的是 Apollo 整体框架继续往世界运行时推进时,一个必须先定下来的问题:
实体从创建、上线、进入世界、迁移、离线到销毁,这条生命周期到底怎么统一。
如果这层不清楚,后面会很容易出现:
- session、anchor、avatar 生命周期打架
- world 内实体状态和在线状态不同步
- 迁移、切图、重连时状态混乱
- BigWorld 增强层接进来后找不到统一状态边界
一、设计目标
这层设计要解决 6 个问题:
Proxy / PlayerAnchor / AvatarEntity的生命周期边界如何统一。- 实体状态如何显式表达,而不是散在 bool 字段里。
- 普通 MMO 和 BigWorld 模式下,哪些状态共用,哪些状态扩展。
- 生命周期事件如何和
Domain Event、RemoteEntityCall、Persistence协同。 - 热路径状态变化如何保持简单。
- 如何避免照搬 KBE 的重对象状态机,也避免退化成“没有状态机”。
二、参考来源
1. 参考 KBE 的对象生命周期经验
参考点:
Proxy / Base / Cell生命周期分层- enter world / leave world / migration / witness / ghost 等显式边界
不照搬点:
- 不把所有状态和流程都塞进重实体对象内部
2. 参考状态机设计思路
参考点:
- 状态显式化
- 状态转换有约束
- 失败回滚边界清楚
3. 参考 ECS 兼容原则
参考点:
- 生命周期管理仍然可以是对象语义
- 但不要让局部高频系统被重状态机拖垮
三、为什么这样设计
Apollo 当前已经明确:
ProxyPlayerAnchorAvatarEntity
不是同一个对象。
既然对象分层已经存在,生命周期就必须随之分层。
更合理的方式不是:
- 一个全局大状态机统治全部对象
而是:
- 每层对象有自己的主状态
- 对象间通过少量关键边界状态协同
四、优点
- 生命周期边界清楚
- 重连、切图、迁移更容易做对
- 更适合普通 MMO 和 BigWorld 共存
- 更适合和持久化、事件、调度协同
五、代价与风险
- 状态显式化后,模型数量会增加
- 需要维护转换规则
- 如果状态切得过细,会导致实现臃肿
六、为什么不选其他方案
不选“完全没有状态机”
因为生命周期复杂后,隐式状态一定会失控。
不选“单一全局 Player 状态机”
因为 Proxy / Anchor / Avatar 生命周期根本不同。
不选“照搬 KBE 全量状态模型”
因为 Apollo 还要兼容更轻的普通在线与普通 MMO 模式。
更合理的路线是:
- 分层对象
- 分层状态
- 关键边界对齐
七、推荐对象与状态分层
Apollo 建议至少拆成 3 层主对象状态。
1. ProxyState
语义:
- 连接和客户端附着状态
建议状态:
Detached
Binding
Bound
Replacing
Disconnected
Closed
2. AnchorState
语义:
- 玩家长期在线归属状态
建议状态:
Idle
Activating
Online
Transferring
Disconnected
Recovering
Closing
Closed
3. AvatarState
语义:
- 玩家在 world / cell 中的表现状态
建议状态:
Created
EnteringWorld
Active
LeavingWorld
Migrating
Ghosting
Dormant
Destroyed
八、推荐关键状态转换
Proxy
Detached -> BindingBinding -> BoundBound -> ReplacingBound -> DisconnectedDisconnected -> Closed
PlayerAnchor
Idle -> ActivatingActivating -> OnlineOnline -> TransferringOnline -> DisconnectedDisconnected -> RecoveringRecovering -> OnlineOnline -> ClosingClosing -> Closed
AvatarEntity
Created -> EnteringWorldEnteringWorld -> ActiveActive -> LeavingWorldActive -> MigratingMigrating -> ActiveActive -> GhostingLeavingWorld -> Destroyed
九、普通 MMO 与 BigWorld 的差异
普通 MMO 模式
重点状态:
OnlineTransferringEnteringWorldLeavingWorld
一般不需要:
GhostingMigrating的跨 partition 复杂状态
BigWorld 模式
需要扩展:
MigratingGhosting- authority 迁移窗口
- witness 相关状态
设计结论
Apollo 应该让普通 MMO 先只实现主链状态,
然后在 BigWorld 模式下扩展额外状态,而不是一开始全量打开所有状态。
十、推荐生命周期事件
为了和 Domain Event 对齐,建议生命周期关键边界统一发事件。
例如:
ProxyBoundPlayerAnchorActivatedPlayerEnteredWorldPlayerTransferStartedPlayerTransferCompletedPlayerDisconnectedAvatarDestroyed
为什么要发事件
因为很多旁路系统只需要知道:
- 某件事已经发生
并不需要直接进入对象内部。
十一、和 RemoteEntityCall 的关系
状态机不应直接变成远程调用本身,但很多状态切换需要借助远程调用完成。
例如:
Anchor -> Avatar创建世界会话World -> Anchor回报进入完成Anchor -> Proxy重绑连接
设计原则:
- 状态机负责状态边界
RemoteEntityCall负责跨宿主执行动作
十二、和 Persistence 的关系
并不是所有状态都需要持久化。
建议区分:
需要持久化的
Anchor长期状态- world assignment
- reconnect 窗口关键信息
不需要长期持久化的
- 大多数
ProxyState - world 实时临时状态
- 大部分
Avatar的热路径运行态
十三、和 World Tick / Scheduling 的关系
状态机不应在任何线程、任何回调里随意切换。
更合理的方式是:
- 生命周期动作进入统一调度点
- 在宿主 tick 或任务执行边界完成状态推进
这样更容易保证:
- 顺序
- 回滚
- 诊断
十四、推荐对象模型
LifecycleCoordinator
├── ProxyStateMachine
├── AnchorStateMachine
├── AvatarStateMachine
├── TransitionGuard
└── LifecycleEventEmitter
TransitionGuard
职责:
- 校验状态转换是否合法
- 校验前置条件
LifecycleEventEmitter
职责:
- 在关键状态边界发事件
十五、对当前 Apollo 的直接含义
Apollo 下一步如果继续往代码实现推进,建议优先补:
PlayerAnchor的显式状态机WorldSession / AvatarEntity的进入和离开状态GatewaySession / Proxy的绑定和替换状态
而不是继续靠:
- 零散标志位
- 多处 if/else
十六、结论
Apollo 的对象模型如果已经明确分层,那么生命周期也必须分层。
更合理的方式是:
Proxy / Anchor / Avatar各自有主状态机- 普通 MMO 先实现主链状态
- BigWorld 再扩展迁移、ghost、authority 相关状态
这样才能让在线主链和分布式世界主链都真正稳定下来。
