Bevy应用在WASM和桌面端的响应时间测量不一致问题
问题:Bevy开发的心理学实验响应时间偶发对数型错误
我使用Bevy引擎开发了一款可在WASM和桌面端运行的简易心理学实验,用于测量椭圆显示到用户输入的响应时间。但发现响应时间测量结果有时会出现对数型错误,该问题偶发、难以复现,WASM和桌面端均存在此现象。
核心时间处理代码
#[derive(Resource)] struct TrialState { start_time: Instant, } impl Default for TrialState { fn default() -> Self { TrialState { start_time: Instant::now(), } } } fn refresh_ellipses( // ... 部分代码省略 ... ) { if experiment_state.ellipses_drawn && (keys.just_pressed(KeyCode::Key1) || keys.just_pressed(KeyCode::Key0) || keys.just_pressed(KeyCode::Space)) { trial_state.start_time = Instant::now(); } // ... 部分代码省略 ... } fn update_user_responses( // ... 其他参数省略 ... ) { if keys.just_pressed(KeyCode::Key1) || keys.just_pressed(KeyCode::Key0) || keys.just_pressed(KeyCode::Space) { let elapsed = trial_state.start_time.elapsed().as_secs_f32(); // ... 函数剩余部分省略 ... } }
CSV结果记录相关代码
#[derive(Default, Resource)] struct ExperimentState { final_result: Vec<(usize, usize, String, f32)>, // ... 其他字段省略 ... } fn update_user_responses( keys: Res<Input<KeyCode>>, mut experiment_state: ResMut<ExperimentState>, trial_state: Res<TrialState>, mut app_state: ResMut<AppState>, ) { // ... 部分代码省略 ... if keys.just_pressed(KeyCode::Key1) || keys.just_pressed(KeyCode::Key0) || keys.just_pressed(KeyCode::Space) { let elapsed = trial_state.start_time.elapsed().as_secs_f32(); experiment_state.final_result.push((num_left, num_right, result, elapsed)); // ... 函数剩余部分省略 ... } if experiment_state.num_trials == TOTAL_TRIAL { print_final_results(&experiment_state.final_result); } } fn print_final_results(final_results: &Vec<(usize, usize, String, f32)>) { // ... 前置代码省略 ... let mean_correct_rt: f32 = final_results .iter() .filter(|(_, _, is_correct, _)| is_correct == "Correct") .map(|(_, _, _, response_time)| response_time) .sum::<f32>() / final_results.len() as f32;
问题分析与解决方案
核心问题:系统执行顺序不确定导致的时序错误
当前代码中,同一个按键事件同时被两个独立系统处理:
refresh_ellipses系统负责更新下一次试验的start_timeupdate_user_responses系统负责计算当前试验的响应时间
Bevy中未指定顺序的系统可能并行执行或按任意顺序运行,这会导致两种错误情况:
- 若
refresh_ellipses先执行:先将start_time更新为当前时间,随后update_user_responses计算的是"刚更新的时间到当前时间"的耗时,结果接近0,属于明显错误 - 若
update_user_responses先执行:先计算当前试验的正确耗时,再更新start_time,结果正常
这就是响应时间偶发错误的根本原因。
其他辅助问题:均值计算逻辑错误
在print_final_results中,计算正确响应时间均值时,分母用了总试验数final_results.len(),但正确的分母应该是过滤后的正确试验数量,否则会导致均值被错误拉低。
解决方案
- 合并逻辑到单一系统:将更新
start_time和记录响应时间的逻辑放在同一个系统中,明确执行顺序,彻底避免时序问题。示例修改方向:
fn handle_trial_flow( keys: Res<Input<KeyCode>>, mut experiment_state: ResMut<ExperimentState>, mut trial_state: ResMut<TrialState>, ) { if experiment_state.ellipses_drawn && (keys.just_pressed(KeyCode::Key1) || keys.just_pressed(KeyCode::Key0) || keys.just_pressed(KeyCode::Space)) { // 1. 先记录当前试验的响应时间 let elapsed = trial_state.start_time.elapsed().as_secs_f32(); experiment_state.final_result.push((num_left, num_right, result, elapsed)); // 2. 再更新start_time为当前时间,用于下一次试验 trial_state.start_time = Instant::now(); // ... 其他试验流程逻辑 ... } }
- 修正均值计算逻辑:
fn print_final_results(final_results: &Vec<(usize, usize, String, f32)>) { // ... 前置代码省略 ... let correct_trials: Vec<&f32> = final_results .iter() .filter(|(_, _, is_correct, _)| is_correct == "Correct") .map(|(_, _, _, response_time)| response_time) .collect(); let mean_correct_rt: f32 = if correct_trials.is_empty() { 0.0 } else { correct_trials.iter().sum::<&f32>() / correct_trials.len() as f32 }; }
- 初始化时确保start_time正确:在第一次椭圆绘制完成后再设置
start_time,避免使用资源创建时的初始时间(可能距离椭圆显示已有较长时间)。
内容的提问来源于stack exchange,提问作者AltunE
相关产品推荐
相关产品推荐

