#[allow(dead_code)]
mod common;
use bevy::prelude::*;
use bevy_ggrs::{prelude::*, *};
use common::base_synctest_app;
#[derive(Component, Reflect, Default, Clone, Hash, Debug, PartialEq)]
struct Score(u32);
fn increment_score(mut query: Query<&mut Score, With<Rollback>>) {
for mut score in &mut query {
score.0 += 1;
}
}
#[test]
fn reflect_strategy_rolls_back_component_data() {
let mut app = base_synctest_app(2);
app.add_systems(Startup, |mut commands: Commands| {
commands.spawn((Score(0), Rollback));
})
.rollback_component_with_reflect::<Score>()
.checksum_component_with_hash::<Score>()
.add_systems(GgrsSchedule, increment_score);
app.world_mut().add_observer(|_: On<SyncTestMismatch>| {
panic!("SyncTestMismatch: Reflect strategy component rollback is non-deterministic");
});
let updates = 20usize;
for _ in 0..updates {
app.update();
}
let frame = app.world().resource::<RollbackFrameCount>().0 as u32;
let score = app
.world_mut()
.query::<&Score>()
.single(app.world())
.unwrap()
.0;
assert_eq!(
score, frame,
"Score (Reflect) should equal the rollback frame count after {updates} updates"
);
}
#[derive(Component, Copy, Clone, Hash, Default, Debug, PartialEq)]
#[component(immutable)]
struct ImmutableTick(u32);
fn increment_tick(mut commands: Commands, query: Query<(Entity, &ImmutableTick), With<Rollback>>) {
for (entity, tick) in &query {
commands.entity(entity).insert(ImmutableTick(tick.0 + 1));
}
}
#[test]
fn immutable_component_copy_strategy_rolls_back() {
let mut app = base_synctest_app(2);
app.add_systems(Startup, |mut commands: Commands| {
commands.spawn((ImmutableTick(0), Rollback));
})
.rollback_immutable_component_with_copy::<ImmutableTick>()
.checksum_component_with_hash::<ImmutableTick>()
.add_systems(GgrsSchedule, increment_tick);
app.world_mut().add_observer(|_: On<SyncTestMismatch>| {
panic!("SyncTestMismatch: immutable Copy component rollback is non-deterministic");
});
let updates = 20usize;
for _ in 0..updates {
app.update();
}
let frame = app.world().resource::<RollbackFrameCount>().0 as u32;
let tick = app
.world_mut()
.query::<&ImmutableTick>()
.single(app.world())
.unwrap()
.0;
assert_eq!(
tick, frame,
"ImmutableTick (Copy) should equal the rollback frame count after {updates} updates"
);
}
#[derive(Component, Clone, Hash, Debug, PartialEq)]
#[component(immutable)]
struct ImmutableLabel(String);
impl Default for ImmutableLabel {
fn default() -> Self {
Self(String::from("frame_0"))
}
}
fn update_label(
mut commands: Commands,
frame: Res<RollbackFrameCount>,
query: Query<Entity, (With<ImmutableLabel>, With<Rollback>)>,
) {
for entity in &query {
commands
.entity(entity)
.insert(ImmutableLabel(format!("frame_{}", frame.0)));
}
}
#[test]
fn immutable_component_clone_strategy_rolls_back() {
let mut app = base_synctest_app(2);
app.add_systems(Startup, |mut commands: Commands| {
commands.spawn((ImmutableLabel::default(), Rollback));
})
.rollback_immutable_component_with_clone::<ImmutableLabel>()
.checksum_component_with_hash::<ImmutableLabel>()
.add_systems(GgrsSchedule, update_label);
app.world_mut().add_observer(|_: On<SyncTestMismatch>| {
panic!("SyncTestMismatch: immutable Clone component rollback is non-deterministic");
});
let updates = 20usize;
for _ in 0..updates {
app.update();
}
let frame = app.world().resource::<RollbackFrameCount>().0;
let label = app
.world_mut()
.query::<&ImmutableLabel>()
.single(app.world())
.unwrap()
.0
.clone();
assert_eq!(
label,
format!("frame_{frame}"),
"ImmutableLabel (Clone) should reflect the current rollback frame after {updates} updates"
);
}
#[derive(Component, Reflect, Clone, Hash, Default, Debug, PartialEq)]
#[component(immutable)]
struct ImmutableGeneration(u32);
fn increment_generation(
mut commands: Commands,
query: Query<(Entity, &ImmutableGeneration), With<Rollback>>,
) {
for (entity, generation) in &query {
commands
.entity(entity)
.insert(ImmutableGeneration(generation.0 + 1));
}
}
#[test]
fn immutable_component_reflect_strategy_rolls_back() {
let mut app = base_synctest_app(2);
app.add_systems(Startup, |mut commands: Commands| {
commands.spawn((ImmutableGeneration(0), Rollback));
})
.rollback_immutable_component_with_reflect::<ImmutableGeneration>()
.checksum_component_with_hash::<ImmutableGeneration>()
.add_systems(GgrsSchedule, increment_generation);
app.world_mut().add_observer(|_: On<SyncTestMismatch>| {
panic!("SyncTestMismatch: immutable Reflect component rollback is non-deterministic");
});
let updates = 20usize;
for _ in 0..updates {
app.update();
}
let frame = app.world().resource::<RollbackFrameCount>().0 as u32;
let generation = app
.world_mut()
.query::<&ImmutableGeneration>()
.single(app.world())
.unwrap()
.0;
assert_eq!(
generation, frame,
"ImmutableGeneration (Reflect) should equal the rollback frame count after {updates} updates"
);
}