关于Bevy引擎中Event更新与消费时序的疑问
解析Bevy Event队列的清空逻辑与你的观测现象
核心机制:Bevy事件的双缓冲队列
Bevy的事件系统采用双缓冲设计,核心流程如下:
- 每次调用
app.update()(即一次完整调度周期)开始前,会执行事件队列的交换:将用于写入的current队列与用于读取的previous队列互换,随后清空新的current队列。 - 本次调度中,
EventWriter写入的事件都会进入current队列;而EventReader只能读取previous队列的内容(也就是上一次调度周期中写入的事件)。 - 事件队列的“清空”不是在调度结束时,而是在下一次调度开始的交换环节——旧的
previous队列会被替换,未被处理的事件是否还能被读取,取决于EventReader的内部跟踪状态(generation和cursor)。
结合你的代码与观测现象分析
你的send_event和read_event系统都绑定在FixedUpdate阶段,只有当FixedUpdate执行时,这两个系统才会运行。而FixedUpdate的执行次数由Time<Fixed>的累计时长决定:只有当虚拟时间(Virtual Time)推进的时长超过Fixed步长(你设置的1秒),FixedUpdate才会触发。
1. 频繁触发Update的场景
当你快速连续调用app.update()时,两次调用间隔极短,虚拟时间累计未达Fixed步长,导致中间两次调度仅运行PreUpdate和Update阶段,FixedUpdate不执行:
- 第一次调度(触发FixedUpdate):
调度开始交换队列后,previous队列为空;send_event向current队列写入事件;read_event读取previous队列(空),此时无事件输出。调度结束后,current队列保留写入的事件。 - 连续两次仅运行PreUpdate/Update的调度:
每次调度开始时,交换队列将上一次的current队列转为previous队列(包含之前写入的事件),但因为FixedUpdate未运行,read_event不执行,EventReader的cursor未前进,也未同步队列的generation,所以每次读取都会显示有未处理事件。直到第二次FixedUpdate运行时,read_event才会读取previous队列的事件,cursor前进,标记事件为已读取;下一次调度开始交换队列时,旧事件被彻底替换清空。
2. 低频率触发Update的场景
当你两次调用app.update()间隔足够长,虚拟时间累计达到Fixed步长,第二次调度会触发FixedUpdate:
- 第二次调度开始交换队列后,
previous队列包含之前写入的事件;read_event运行并读取事件,cursor前进,标记为已读取。 - 第三次调度开始时,交换队列将旧的
previous队列替换,EventReader同步generation,未读事件数归零,队列看起来被清空。
纠正你的误解
你以为app.update()会直接清空事件队列,但实际上:
- 事件队列的清理是调度开始时的交换操作实现的,而非调度结束时。
- 事件是否“被清空”(即不再被Reader读取),取决于Reader是否在对应阶段读取过事件,以及队列generation是否同步——如果Reader从未读取事件,即使队列被交换,旧事件可能仍会被Reader识别为未读,直到generation超过Reader的跟踪值。
内容的提问来源于stack exchange,提问作者youzheyin
相关产品推荐
相关产品推荐

