diff --git a/Cargo.toml b/Cargo.toml
index 0bd7682..03a01f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,7 @@ categories = ["network-programming", "game-development"]
wasm-bindgen = ["instant/wasm-bindgen", "ggrs/wasm-bindgen"]
[dependencies]
-bevy = { version = "0.12", default-features = false }
+bevy = { version = "0.13", default-features = false }
bytemuck = { version = "1.7", features=["derive"]}
instant = { version = "0.1", optional = true }
log = "0.4"
@@ -24,11 +24,11 @@ log = "0.4"
ggrs = { git = "https://github.com/gschup/ggrs", features=["sync-send"]}
[dev-dependencies]
-bevy = { version = "0.12", default-features = true }
+bevy = { version = "0.13", default-features = true }
clap = { version = "4.4", features = ["derive"] }
rand = "0.8.4"
rand_xoshiro = "0.6"
-serde = "1.0.130"
+serde = "1.0.196"
serde_json = "1.0"
serial_test = "2.0"
diff --git a/examples/box_game/box_game.rs b/examples/box_game/box_game.rs
index 077a15e..16761f6 100644
--- a/examples/box_game/box_game.rs
+++ b/examples/box_game/box_game.rs
@@ -1,4 +1,4 @@
-use bevy::{prelude::*, utils::HashMap};
+use bevy::{prelude::*, render::mesh::PlaneMeshBuilder, utils::HashMap};
use bevy_ggrs::{
AddRollbackCommandExtension, GgrsConfig, LocalInputs, LocalPlayers, PlayerInputs, Rollback,
Session,
@@ -54,7 +54,7 @@ pub struct FrameCount {
/// Collects player inputs during [`ReadInputs`](`bevy_ggrs::ReadInputs`) and creates a [`LocalInputs`] resource.
pub fn read_local_inputs(
mut commands: Commands,
- keyboard_input: Res>,
+ keyboard_input: Res>,
local_players: Res,
) {
let mut local_inputs = HashMap::new();
@@ -62,16 +62,16 @@ pub fn read_local_inputs(
for handle in &local_players.0 {
let mut input: u8 = 0;
- if keyboard_input.pressed(KeyCode::W) {
+ if keyboard_input.pressed(KeyCode::KeyW) {
input |= INPUT_UP;
}
- if keyboard_input.pressed(KeyCode::A) {
+ if keyboard_input.pressed(KeyCode::KeyA) {
input |= INPUT_LEFT;
}
- if keyboard_input.pressed(KeyCode::S) {
+ if keyboard_input.pressed(KeyCode::KeyS) {
input |= INPUT_DOWN;
}
- if keyboard_input.pressed(KeyCode::D) {
+ if keyboard_input.pressed(KeyCode::KeyD) {
input |= INPUT_RIGHT;
}
@@ -95,16 +95,16 @@ pub fn setup_system(
// A ground plane
commands.spawn(PbrBundle {
- mesh: meshes.add(Mesh::from(shape::Plane {
- size: PLANE_SIZE,
- ..default()
+ mesh: meshes.add(Mesh::from(PlaneMeshBuilder {
+ plane: Plane3d::new(Vec3::Y),
+ half_size: Vec2::splat(PLANE_SIZE / 2.0),
})),
- material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
+ material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
..default()
});
let r = PLANE_SIZE / 4.;
- let mesh = meshes.add(Mesh::from(shape::Cube { size: CUBE_SIZE }));
+ let mesh = meshes.add(Mesh::from(Cuboid::from_size(Vec3::splat(CUBE_SIZE))));
for handle in 0..num_players {
let rot = handle as f32 / num_players as f32 * 2. * std::f32::consts::PI;
@@ -123,7 +123,7 @@ pub fn setup_system(
// ...add visual information...
PbrBundle {
mesh: mesh.clone(),
- material: materials.add(color.into()),
+ material: materials.add(StandardMaterial::from(color)),
transform,
..default()
},
diff --git a/examples/stress_tests/particles.rs b/examples/stress_tests/particles.rs
index 15d7a33..b7525a0 100644
--- a/examples/stress_tests/particles.rs
+++ b/examples/stress_tests/particles.rs
@@ -74,7 +74,7 @@ const INPUT_NOOP: u8 = 1 << 5;
fn read_local_inputs(
mut commands: Commands,
- keyboard_input: Res>,
+ keyboard_input: Res>,
local_players: Res,
) {
let mut local_inputs = HashMap::new();
@@ -88,7 +88,7 @@ fn read_local_inputs(
}
// n is a no-op key, press to simply trigger a rollback
- if keyboard_input.pressed(KeyCode::N) {
+ if keyboard_input.pressed(KeyCode::KeyN) {
input |= INPUT_NOOP;
}
diff --git a/src/lib.rs b/src/lib.rs
index c099b29..af0e732 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@
//!
//! See [`GgrsPlugin`] for getting started.
#![forbid(unsafe_code)] // let us try
+#![allow(clippy::type_complexity)] // Suppress warnings around Query
use bevy::{
ecs::{
diff --git a/src/rollback.rs b/src/rollback.rs
index 75e52de..cebc456 100644
--- a/src/rollback.rs
+++ b/src/rollback.rs
@@ -44,9 +44,9 @@ pub trait AddRollbackCommandExtension: private::AddRollbackCommandExtensionSeal
fn add_rollback(&mut self) -> &mut Self;
}
-impl<'w, 's, 'a> private::AddRollbackCommandExtensionSeal for EntityCommands<'w, 's, 'a> {}
+impl<'a> private::AddRollbackCommandExtensionSeal for EntityCommands<'a> {}
-impl<'w, 's, 'a> AddRollbackCommandExtension for EntityCommands<'w, 's, 'a> {
+impl<'a> AddRollbackCommandExtension for EntityCommands<'a> {
fn add_rollback(&mut self) -> &mut Self {
self.add(AddRollbackCommand);
self
diff --git a/src/snapshot/component_map.rs b/src/snapshot/component_map.rs
index 607feff..ed7340f 100644
--- a/src/snapshot/component_map.rs
+++ b/src/snapshot/component_map.rs
@@ -1,9 +1,6 @@
use std::marker::PhantomData;
-use bevy::{
- ecs::entity::{EntityMapper, MapEntities},
- prelude::*,
-};
+use bevy::{ecs::entity::MapEntities, prelude::*};
use crate::{LoadWorld, LoadWorldSet, RollbackEntityMap};
@@ -26,8 +23,8 @@ use crate::{LoadWorld, LoadWorldSet, RollbackEntityMap};
/// struct BestFriend(Entity);
///
/// impl MapEntities for BestFriend {
-/// fn map_entities(&mut self, entity_mapper: &mut EntityMapper) {
-/// self.0 = entity_mapper.get_or_reserve(self.0);
+/// fn map_entities(&mut self, entity_mapper: &mut M) {
+/// self.0 = entity_mapper.map_entity(self.0);
/// }
/// }
///
@@ -72,55 +69,16 @@ fn apply_rollback_map_to_component_inner(world: &mut World, map: Mut);
+ for (original, _new) in map.iter() {
+ if let Some(mut component) = world.get_mut::(original) {
+ component.map_entities(&mut map.as_ref());
+ }
+ }
trace!(
"Mapped {}",
bevy::utils::get_short_name(std::any::type_name::())
);
-
- // If the entity map is now larger than the set of rollback entities, then dead entities were created.
- // TODO: This workaround is required because the current behavior of `map_all_entities` is to change all entities,
- // creating dead entities instead of leaving them with their original value. If `EntityMapper` behavior changes,
- // then this workaround may no longer be required.
- if applied_entity_map.len() > map.len() {
- // Reverse dead-mappings, no-op correct mappings
- for original in applied_entity_map.keys().copied().collect::>() {
- let mapped = applied_entity_map.remove(&original).unwrap();
-
- if map.get(original).is_some() {
- // Rollback entity was correctly mapped; no-op
- applied_entity_map.insert(mapped, mapped);
- } else {
- // An untracked bystander was mapped to a dead end; reverse
- applied_entity_map.insert(mapped, original);
- }
- }
-
- // Map entities a second time, fixing dead entities
- EntityMapper::world_scope(&mut applied_entity_map, world, apply_map::);
-
- trace!(
- "Re-Mapped {}",
- bevy::utils::get_short_name(std::any::type_name::())
- );
- }
-}
-
-fn apply_map(world: &mut World, entity_mapper: &mut EntityMapper) {
- let entities = entity_mapper
- .get_map()
- .values()
- .copied()
- .collect::>();
-
- for entity in &entities {
- if let Some(mut component) = world.get_mut::(*entity) {
- component.map_entities(entity_mapper);
- }
- }
}
impl Plugin for ComponentMapEntitiesPlugin
diff --git a/src/snapshot/resource_map.rs b/src/snapshot/resource_map.rs
index 541869b..b00978e 100644
--- a/src/snapshot/resource_map.rs
+++ b/src/snapshot/resource_map.rs
@@ -1,9 +1,6 @@
use std::marker::PhantomData;
-use bevy::{
- ecs::entity::{EntityMapper, MapEntities},
- prelude::*,
-};
+use bevy::{ecs::entity::MapEntities, prelude::*};
use crate::{LoadWorld, LoadWorldSet, RollbackEntityMap};
@@ -26,8 +23,8 @@ use crate::{LoadWorld, LoadWorldSet, RollbackEntityMap};
/// struct Player(Entity);
///
/// impl MapEntities for Player {
-/// fn map_entities(&mut self, entity_mapper: &mut EntityMapper) {
-/// self.0 = entity_mapper.get_or_reserve(self.0);
+/// fn map_entities(&mut self, entity_mapper: &mut M) {
+/// self.0 = entity_mapper.map_entity(self.0);
/// }
/// }
///
@@ -72,47 +69,14 @@ fn apply_rollback_map_to_resource_inner(world: &mut World, map: Mut);
+ if let Some(mut resource) = world.get_resource_mut::() {
+ resource.map_entities(&mut map.as_ref());
+ }
trace!(
"Mapped {}",
bevy::utils::get_short_name(std::any::type_name::())
);
-
- // If the entity map is now larger than the set of rollback entities, then dead entities were created.
- // TODO: This workaround is required because the current behavior of `map_all_entities` is to change all entities,
- // creating dead entities instead of leaving them with their original value. If `EntityMapper` behavior changes,
- // then this workaround may no longer be required.
- if applied_entity_map.len() > map.len() {
- // Reverse dead-mappings, no-op correct mappings
- for original in applied_entity_map.keys().copied().collect::>() {
- let mapped = applied_entity_map.remove(&original).unwrap();
-
- if map.get(original).is_some() {
- // Rollback entity was correctly mapped; no-op
- applied_entity_map.insert(mapped, mapped);
- } else {
- // An untracked bystander was mapped to a dead end; reverse
- applied_entity_map.insert(mapped, original);
- }
- }
-
- // Map entities a second time, fixing dead entities
- EntityMapper::world_scope(&mut applied_entity_map, world, apply_map::);
-
- trace!(
- "Re-Mapped {}",
- bevy::utils::get_short_name(std::any::type_name::())
- );
- }
-}
-
-fn apply_map(world: &mut World, entity_mapper: &mut EntityMapper) {
- if let Some(mut resource) = world.get_resource_mut::() {
- resource.map_entities(entity_mapper);
- }
}
impl Plugin for ResourceMapEntitiesPlugin
diff --git a/src/snapshot/rollback_entity_map.rs b/src/snapshot/rollback_entity_map.rs
index d008762..b6cafaf 100644
--- a/src/snapshot/rollback_entity_map.rs
+++ b/src/snapshot/rollback_entity_map.rs
@@ -1,14 +1,26 @@
-use bevy::{prelude::*, utils::HashMap};
+use bevy::{ecs::entity::EntityHashMap, prelude::*, utils::HashMap};
/// A [`Resource`] which provides an [`EntityMap`], describing how [`Entities`](`Entity`)
/// changed during a rollback.
#[derive(Resource, Default)]
-pub struct RollbackEntityMap(HashMap);
+pub struct RollbackEntityMap(EntityHashMap);
+
+impl From> for RollbackEntityMap {
+ fn from(value: EntityHashMap) -> Self {
+ Self(value)
+ }
+}
+
+impl From> for RollbackEntityMap {
+ fn from(value: HashMap) -> Self {
+ Self(value.into_iter().collect())
+ }
+}
impl RollbackEntityMap {
/// Create a new [`RollbackEntityMap`], which can generate [`EntityMaps`](`EntityMap`) as required.
pub fn new(map: HashMap) -> Self {
- Self(map)
+ map.into()
}
/// Generate an owned [`EntityMap`], which can be used concurrently with other systems.
@@ -46,3 +58,10 @@ impl RollbackEntityMap {
map.is_empty()
}
}
+
+impl<'a> EntityMapper for &'a RollbackEntityMap {
+ /// Map the provided [`Entity`], or return it unmodified if it does not need to be mapped.
+ fn map_entity(&mut self, entity: Entity) -> Entity {
+ self.get(entity).unwrap_or(entity)
+ }
+}
diff --git a/src/time.rs b/src/time.rs
index b5e1296..e808776 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -86,7 +86,7 @@ impl GgrsTimePlugin {
impl Plugin for GgrsTimePlugin {
fn build(&self, app: &mut App) {
- app.insert_resource(Time::new_with(GgrsTime::default()))
+ app.insert_resource(Time::new_with(GgrsTime))
.add_plugins(ResourceSnapshotPlugin::>>::default())
.add_systems(
AdvanceWorld,
diff --git a/tests/integration.rs b/tests/integration.rs
index 1843154..2b60c7b 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -1,5 +1,8 @@
use bevy::{
- input::{keyboard::KeyboardInput, ButtonState, Input, InputPlugin},
+ input::{
+ keyboard::{Key, KeyboardInput},
+ ButtonInput, ButtonState, InputPlugin,
+ },
prelude::*,
time::TimeUpdateStrategy,
utils::{Duration, HashMap},
@@ -55,7 +58,7 @@ fn it_syncs_rollback_components() -> Result<(), Box> {
let mut app2 = create_app::(session2);
for _ in 0..50 {
- press_key(&mut app1, KeyCode::W);
+ press_key(&mut app1, KeyCode::KeyW);
app1.update();
app2.update();
}
@@ -72,7 +75,7 @@ fn it_syncs_rollback_components() -> Result<(), Box> {
fn create_app(session: P2PSession) -> App {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
- .add_plugins(InputPlugin::default())
+ .add_plugins(InputPlugin)
.add_plugins(GgrsPlugin::::default())
.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f64(
1.0 / 60.0,
@@ -103,7 +106,7 @@ fn create_players() -> (TestPlayer, TestPlayer) {
const REMOTE_PORT: u16 = 8082;
let remote_addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), PLAYER1_PORT);
let remote_addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), REMOTE_PORT);
- return (
+ (
TestPlayer {
handle: 0,
address: remote_addr1,
@@ -112,7 +115,7 @@ fn create_players() -> (TestPlayer, TestPlayer) {
handle: 1,
address: remote_addr2,
},
- );
+ )
}
fn start_session(
@@ -138,14 +141,14 @@ const INPUT_UP: u8 = 1 << 0;
pub fn read_local_inputs(
mut commands: Commands,
- keyboard_input: Res>,
+ keyboard_input: Res>,
local_players: Res,
) {
let mut local_inputs = HashMap::new();
for handle in &local_players.0 {
let mut input: u8 = 0;
- if keyboard_input.pressed(KeyCode::W) {
+ if keyboard_input.pressed(KeyCode::KeyW) {
input |= INPUT_UP;
}
local_inputs.insert(*handle, BoxInput { inp: input });
@@ -160,8 +163,8 @@ pub fn increase_frame_system(mut frame_count: ResMut) {
fn press_key(app: &mut App, key: KeyCode) {
app.world.send_event(KeyboardInput {
- scan_code: 0,
- key_code: Option::from(key),
+ logical_key: Key::Character("w".into()),
+ key_code: key,
state: ButtonState::Pressed,
window: Entity::PLACEHOLDER,
});