世界分区与AOI
全库目录221
本文目录15
玩家看到的对象集合与对象由哪台服务器计算,是两份不同的状态。兴趣区域(Area of Interest,AOI)筛选玩家需要接收的实体;世界分区给服务器分配权威。玩家走过区界时,系统既要保证附近对象正确出现和移除,也要保证每条输入只由当前权威产生一次效果。两条链通过稳定实体ID相连,复制代数与权威代次分别解决客户端身份和服务器所有权的问题。
空间候选与复制集合
网格查询
设二维位置单位为米,格子边长为正数cellSize。实体作为一个点,只放入floor(position / cellSize)对应的桶。向负无穷取整很重要:位置−0.1米属于−1号格,向零截断却会错误归入0号格。
查询半径R先求覆盖圆的轴对齐包围盒,再遍历盒内格子,最后精筛圆内实体。格子只是减少候选量,包围盒角落仍在圆外。
CellKey ToCell(Vec2 p) { return Floor(p / cellSize); }
void QueryAoi(Vec2 center, float radius, Vector<EntityId>& out)
{
CellKey lo = ToCell(center - Vec2(radius));
CellKey hi = ToCell(center + Vec2(radius));
for (int y = lo.y; y <= hi.y; ++y)
for (int x = lo.x; x <= hi.x; ++x)
for (EntityId id : cells[{x, y}])
if (DistanceSquared(position[id], center) <= radius * radius)
out.push_back(id);
}这是依赖容器和向量库的C++机制片段,调用方清空输出,传入非负半径。移动阶段先把实体从旧桶移到新桶,再发布同一版本的位置与桶快照;查询读取这个完整版本。若位置已更新、桶还在旧处,观察者只查新位置附近便可能漏掉实体。锁住更新与查询,或帧末交换完整快照,都能建立所需的一致视图。
格子越小,查询访问的桶越多;格子越大,桶内距离检查越多。选择尺寸时记录访问桶数、候选数和最终集合,而不是仅统计实体总数。大体积对象可占多个桶,此时按实体ID去重,精筛改为观察范围与对象边界相交;单点规则不能直接代表大型建筑的可见范围。
迟滞与非空间关系
进入半径取R,退出半径取R+h,h为正的迟滞宽度。新实体距离不超过R才加入,已有实体距离大于R+h才移除。R=10、h=2时,11米的陌生实体不加入;它移到9米后加入,再回到11米仍保留,12.1米才移除。
因此候选查询覆盖R+h,并与旧集合合并后逐个判断;只查R再删除未返回者,会在11米时破坏迟滞。队伍、任务和观战关系形成另外的相关集合,最终集合是空间相关与玩法相关的并集。删除前同时检查所有来源,避免队友离开空间范围时被误删。权限过滤位于并集之后:空间相近本身不授予隐身对象或私有库存的读取权限。
生成、更新与移除
集合差分得到进入、保留和退出三个集合。进入发送完整生成状态,保留发送更新,退出发送移除。接收端必须知道更新依赖哪次生成,因此每个观察者会话给每次进入分配递增的复制代数g;实体本身另有实例代数,区分删除后复用同一数值ID的对象。
生成与移除进入可靠有序控制流,带会话ID与控制序号c。发送端保存未确认消息并重发,接收端只连续应用控制序号,重复消息直接确认。生成确认之前,发送端暂存或丢弃该对象的非关键更新,不能假定客户端已创建对象。位置更新可以走非可靠流,但必须带实体实例代数、g和快照序号;接收端只对当前已生成的g应用更新,并丢弃不更新的快照。
例如控制10生成g=4,控制11移除g=4,控制12重新生成g=5。网络先送来12时,接收端等待11;旧更新g=4随后到达,也无法修改g=5。移除确认前保存必要的墓碑,避免旧包重新创建对象。控制日志达到上限或断线后,不无限保留墓碑:建立新会话ID,发送完整相关集合,双方废弃旧会话的全部包。这样有限缓存与身份寿命形成完整规则。
带宽不足时,生成、移除和关键玩法状态先占预算;保留集合按重要性与距上次更新的时长排序,逐项扣除序列化字节数,下一轮继续未发对象。某实体连续被跳过时,等待时间提高优先级;完整基线过期则重新发送基线,不能继续引用客户端已淘汰的增量状态。相关性决定“能否发送”,调度决定“何时发送”,两者不能混为一个距离阈值。
调度与实体观察时间
发送预算选中一个实体时,冻结该模拟时刻的位置、速度和旋转,再生成这一运动观察;没有选中的实体保留自己的上次观察时间。快照包时间持续增长,只说明服务器还在发包,不能给未发送实体制造新样本。完整解码表、字段掩码、单实体外推和查询有效性由快照同步定义。关键可受击体或遮挡物持续缺更时,客户端会暂停相关视图查询,因此调度器须优先为这类对象保留更新预算,不能让装饰物占满预算后仅靠更长外推补救。
分区权威与相邻代理
世界可以按固定区域、动态区域或实例划分。权威服务器计算实体,邻区只保存带epoch与状态序号的只读代理。代理更新落后时可以降低展示精度,技能结算仍路由到权威;两个区域各自修改同一生命值会产生无法靠位置同步合并的结果。
跨区交互由实体当前权威校验。跨区转移物品等多实体操作另需事务ID与共同结算记录,具体业务事务见 对局服务器架构。下面把玩家输入和该玩家库存放在同一持久事务中说明交接;若库存实际属于独立服务,提交边界还要延伸到该服务,不能把一次远程调用视为本地写入。
持久冻结与准备
每个实体的持久记录保存(entityId, owner, epoch, stateVersion, activeInputSession, lastInput, gameplayState, phase)。epoch为单调递增的权威代次,lastInput为连续执行的最大输入序号。状态、序号与所有权由同一事务存储管理;服务器缓存只有提交后才向客户端确认效果。
A服epoch7准备把玩家42迁往B。A先停止领取该实体的新输入,等待已执行事务结束,再在事务内标记Frozen,保存迁移IDm及快照引用。快照包括位置、速度、技能计时、库存、随机状态、待结算事件、数据模式版本和lastInput=100,内容哈希用于目标校验。冻结标记与快照同时持久提交,所以A崩溃后也能判断冻结点;只在内存打标记会让恢复进程继续消费101。
B按m加载快照并校验模式、哈希与资源前提,然后写Ready记录,包含源epoch、快照版本和哈希。重复准备m先查已有迁移记录并核对源身份,返回已有状态;只有新的m且源owner、epoch和Active前提仍匹配,才允许把源实体改为Frozen。A7到B8已经提交后迟到重试m,只返回该迁移已提交的事实,不能再把当前B8冻结。Ready表示状态可恢复,尚未允许B运行。若迁移被撤销,先在所有权未改变的条件下原子记录Aborted并恢复A;B的提交检查必须拒绝Aborted,避免撤销与迟到准备形成竞态。
所有权条件提交
交接事务比较当前记录仍是(A,7,Frozen,m,version),并比较B的Ready引用与该快照一致;全部成立才写(B,8,Active)及对应状态引用。这个条件更新是交接的唯一提交点。两个目标竞争时至多一个符合旧epoch;源服在冻结后写状态,同样因为phase或epoch不符被拒绝。
存储提交需要线性一致的比较与更新。本文将所有权与业务状态放在同一事务数据库,条件交接与业务写入争用同一实体记录,因此业务写入校验旧epoch之后,交接不能插进来让该写入越过提交边界。不可变快照可以先写到外部存储,校验可读后再在交接事务中发布引用,未被引用的暂存内容稍后回收。
etcd可以保存路由镜像并发布通知,权威判定仍回到业务事务数据库。把etcd作为独立权威、业务状态放在另一数据库时,单次先查etcd再写数据库存在竞态:查询到epoch7后交接为8,旧写入仍可能落库。这样的组合必须建立跨域的共同提交或等价栅栏协议;直接采用本文的同库所有权记录,才能让条件写入在同一提交边界生效。权威存储不可达时保留冻结与待处理输入,恢复可达后查询提交记录。超时只表明没有拿到答案,不决定所有权。
提交后,A发起的新状态写入仍携带epoch7,状态存储拒绝它。已在epoch7提交的发件箱事件则属于合法历史效果,即使迁移后才送达也应完成交付;下游核对来自可信发件箱发送器的已提交事件身份并去重,不能单凭事件的源epoch低于当前epoch就丢弃。旧进程直接发起的未提交业务调用不具备这份交付资格,不能绕过发件箱访问效果入口。仅让B知道epoch8、却允许A绕过存储校验写库存,仍会双重修改。代理、路由缓存也携带epoch,低代次通知不能覆盖高代次。
输入、路由与崩溃恢复
输入效果的共同提交
客户端每个会话使用连续输入序号,重试保留原序号和内容摘要。所有者在一个持久事务内核对owner、epoch、Active、activeInputSession及输入序号。seq <= lastInput返回已保存结果;seq > lastInput+1请求缺失输入;相邻输入才执行游戏规则,把状态、结果、lastInput和待发送事件一起提交。
BEGIN
check owner, epoch, Active and activeInputSession
check seq and immutable input digest
state' = ApplyRules(state, input)
save state', result(seq), lastInput = seq
append outbox(eventId = (entityInstance, session, fullSeq, effectSlot), event)
COMMIT
ack committed resultApplyRules对当前事务快照计算,不能先调用一个不可撤销外部服务再提交。跨服务效果进入持久发件箱;一个输入可能产生多个效果,eventId使用实体实例、输入会话、完整序号和稳定效果槽组合;同一槽的载荷在提交时固定,重试核对摘要。提交后重发相同eventId,下游在它自己的业务事务中同时去重和应用效果,确认后清理发件箱。保留重放窗口内的输入摘要和结果,相同序号不同内容拒绝,超出窗口的重试要求重新同步。
设库存3个,输入101消耗1个。写库存2与序号101之间崩溃时,事务尚未提交,恢复仍读到库存3、序号100,重试执行一次。提交后回执前崩溃,恢复读到库存2、序号101,重试返回原结果。若把库存与序号分别提交,就会出现库存2、序号100,恢复重放再扣成1;保存一个“最新序号”字段本身不足以解决这个问题。
路由切换与有限缓冲
冻结期间,网关把101以后的输入写入按会话排序的有界队列;只有持久入队才确认收到,业务成功仍等当前权威提交。所有权提交后,通过持久路由通知发布(42,B,8,m);网关按通知或重新查询仲裁刷新缓存,向B补发队列。旧路由把输入发A时,A返回当前权威或拒绝,网关重查并以相同序号重试。
队列按目标lastInput清理,而不是按一次发送完成清理。若B先收到102,等待101;若101回执丢失,重发由事务去重。通知丢失时,网关周期核对仲裁版本,重启后从未确认输入继续发送。客户端断线重连沿用旧输入会话ID和最后确认序号,因此链路恢复沿同一条重放规则进行。输入会话独立于连接与AOI复制会话:换网络连接或重建可见集合,不重置输入序号。
建立全新输入会话前,网关先关闭旧会话的入队入口,让所有已确认入队操作得到已提交结果或持久终止结果;权威与队列共同确认该关闭屏障后,才在事务中把activeInputSession切到新ID并将lastInput置零。该事务同时保存旧会话已关闭的状态,迟到旧包按会话ID拒绝。若只把seq从100重置为1,新会话输入会被旧lastInput误判重复;若只清lastInput,旧会话输入101又可能进入新状态。会话ID、去重摘要、结果与序号因此成组保存并参与校验。迁移快照也携带这组字段。
每个会话限制队列条数、字节和等待时间。达到上限即停止接收新指令并向客户端返回忙或要求重连;已经确认入队的指令继续保存,直到完成或以明确的会话终止结果结束。移动输入若允许合并,协议先定义可合并字段与时间区间;技能和物品指令保留逐项序号,不能在队列满时静默覆盖。背压保护容量,同时给未执行操作一个可解释结果。
目标故障与恢复
B在提交前崩溃,记录仍指A、epoch7且Frozen;恢复器可以重建B的准备状态继续提交,或条件撤销m恢复A。B在提交后崩溃,记录已指B、epoch8。所有者标识还包含进程实例ID;启动替代进程时,即使机器名仍是B,也先以条件更新把epoch推进到9,再从最新已提交状态恢复。这样网络隔离后恢复运行的旧B进程仍带8,会被存储拒绝。若改由C接管,同样分配新实例与新epoch。恢复器不能重新激活A的旧缓存,也不能让两个自称B的进程共享一个epoch。
因此恢复所需的不只是交接瞬间的快照。所有权变成B后,101及后续状态仍持续持久提交;恢复以最近提交状态和输入结果为准。事件发件箱与状态同在恢复域内,崩溃后重复投递也沿下游去重继续。磁盘或仲裁永久丢失属于备份恢复问题;恢复策略必须保留已确认提交的状态,不能用迁移前快照覆盖它。
参考实现与验证
reference.py使用Python标准库SQLite,把所有权、冻结快照、库存与消费序号放在一个持久事务域。prepare冻结并记录迁移状态,transfer条件核对准备快照,consume在事务中执行库存扣减与序号更新。它提供可运行的提交边界,正文的可靠控制流、发件箱及跨存储发布协议用于说明接入时必须保持的相同契约。
python -B 知识库/examples/mechanism_chain/protocol/test_protocol.py测试真正启动子进程,在SQLite提交前与提交后强制退出,再重开数据库观察状态;同时检查未准备迁移、旧epoch和输入缺口被拒绝。15项测试还包含新输入会话、待处理队列阻止重置,以及编辑器事务与发布恢复。它们支持本机持久事务行为,不代表多服务器压测、外部服务或全部网络时序均已运行。恢复器根据持久状态区分提交前、提交后与等待仲裁,超时不能单独触发源服恢复。
复核时先用全量距离判断对照网格集合,再逐一交换生成、移除、旧更新的到达顺序。迁移则在冻结、Ready、条件提交、状态提交和回执五个位置切断进程,观察持久记录决定哪个所有者继续。性能检查分别记录候选筛选、复制字节、迁移队列积压与存储提交延迟,避免把降低复制量误当作迁移恢复正确。
相关主题
参考资料
- etcd API guarantees:比较更新所需的一致性,以及通知与请求结果的边界。本文迁移与AOI协议为具体设计,不是etcd提供的现成业务协议。
- SQLite Atomic Commit:参考实现所采用的本地原子提交与恢复机制。