Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rollback systems with app.add_system(system.in_schedule(GGRSSchedule)) #51

Merged
merged 8 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# IDE
.idea
.vscode

# Generated by Cargo
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ categories = ["network-programming", "game-development"]
wasm-bindgen = ["instant/wasm-bindgen", "ggrs/wasm-bindgen"]

[dependencies]
bevy = { version = "0.9.1", default-features = false, features = ["bevy_render", "bevy_asset","bevy_scene",]}
bevy = { version = "0.10", default-features = false, features = ["bevy_render", "bevy_asset","bevy_scene",]}
bytemuck = { version = "1.7", features=["derive"]}
instant = "0.1"
log = "0.4"
Expand All @@ -27,9 +27,10 @@ parking_lot = "0.12.1"
[dev-dependencies]
structopt = "0.3"
rand = "0.8.4"
bevy = "0.9.1"
bevy = "0.10"
serde = "1.0.130"
serde_json = "1.0"
serial_test = "1.0.0"

# Examples
[[example]]
Expand Down
5 changes: 4 additions & 1 deletion examples/box_game/box_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ pub fn setup_system(

// plane
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: PLANE_SIZE })),
mesh: meshes.add(Mesh::from(shape::Plane {
size: PLANE_SIZE,
..default()
})),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
Expand Down
23 changes: 7 additions & 16 deletions examples/box_game/box_game_p2p.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use std::net::SocketAddr;

use bevy::prelude::*;
use bevy_ggrs::{GGRSPlugin, Session};
use bevy::{prelude::*, window::WindowResolution};
use bevy_ggrs::{GGRSPlugin, GGRSSchedule, Session};
use ggrs::{PlayerType, SessionBuilder, UdpNonBlockingSocket};
use structopt::StructOpt;

mod box_game;
use box_game::*;

const FPS: usize = 60;
const ROLLBACK_DEFAULT: &str = "rollback_default";

// structopt will read command line parameters for u
#[derive(StructOpt, Resource)]
Expand Down Expand Up @@ -68,30 +67,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.register_rollback_component::<Transform>()
.register_rollback_component::<Velocity>()
.register_rollback_resource::<FrameCount>()
// these systems will be executed as part of the advance frame update
.with_rollback_schedule(
Schedule::default().with_stage(
ROLLBACK_DEFAULT,
SystemStage::parallel()
.with_system(move_cube_system)
.with_system(increase_frame_system),
),
)
// make it happen in the bevy app
.build(&mut app);

// continue building/running the app like you normally would
app.insert_resource(opt)
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: 720.,
height: 720.,
primary_window: Some(Window {
resolution: WindowResolution::new(720., 720.),
title: "GGRS Box Game".to_owned(),
..default()
},
}),
..default()
}))
.add_startup_system(setup_system)
// these systems will be executed as part of the advance frame update
.add_systems((move_cube_system, increase_frame_system).in_schedule(GGRSSchedule))
// add your GGRS session
.insert_resource(Session::P2PSession(sess))
// register a resource that will be rolled back
Expand Down
14 changes: 3 additions & 11 deletions examples/box_game/box_game_spectator.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use std::net::SocketAddr;

use bevy::prelude::*;
use bevy_ggrs::{GGRSPlugin, Session};
use bevy_ggrs::{GGRSPlugin, GGRSSchedule, Session};
use ggrs::{SessionBuilder, UdpNonBlockingSocket};
use structopt::StructOpt;

mod box_game;
use box_game::*;

const FPS: usize = 60;
const ROLLBACK_DEFAULT: &str = "rollback_default";

// structopt will read command line parameters for u
#[derive(StructOpt, Resource)]
Expand Down Expand Up @@ -47,22 +46,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.register_rollback_component::<Transform>()
.register_rollback_component::<Velocity>()
.register_rollback_resource::<FrameCount>()
// these systems will be executed as part of the advance frame update
.with_rollback_schedule(
Schedule::default().with_stage(
ROLLBACK_DEFAULT,
SystemStage::parallel()
.with_system(move_cube_system)
.with_system(increase_frame_system),
),
)
// make it happen in the bevy app
.build(&mut app);

// continue building/running the app like you normally would
app.insert_resource(opt)
.add_plugins(DefaultPlugins)
.add_startup_system(setup_system)
// these systems will be executed as part of the advance frame update
.add_systems((move_cube_system, increase_frame_system).in_schedule(GGRSSchedule))
// add your GGRS session
.insert_resource(Session::SpectatorSession(sess))
// register a resource that will be rolled back
Expand Down
14 changes: 3 additions & 11 deletions examples/box_game/box_game_synctest.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use bevy::prelude::*;
use bevy_ggrs::{GGRSPlugin, Session};
use bevy_ggrs::{GGRSPlugin, GGRSSchedule, Session};
use ggrs::{PlayerType, SessionBuilder};
use structopt::StructOpt;

mod box_game;
use box_game::*;

const FPS: usize = 60;
const ROLLBACK_DEFAULT: &str = "rollback_default";

// structopt will read command line parameters for u
#[derive(StructOpt, Resource)]
Expand Down Expand Up @@ -47,22 +46,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.register_rollback_component::<Transform>()
.register_rollback_component::<Velocity>()
.register_rollback_resource::<FrameCount>()
// these systems will be executed as part of the advance frame update
.with_rollback_schedule(
Schedule::default().with_stage(
ROLLBACK_DEFAULT,
SystemStage::parallel()
.with_system(move_cube_system)
.with_system(increase_frame_system),
),
)
// make it happen in the bevy app
.build(&mut app);

// continue building/running the app like you normally would
app.insert_resource(opt)
.add_plugins(DefaultPlugins)
.add_startup_system(setup_system)
// these systems will be executed as part of the advance frame update
.add_systems((move_cube_system, increase_frame_system).in_schedule(GGRSSchedule))
// add your GGRS session
.insert_resource(Session::SyncTestSession(sess))
// register a resource that will be rolled back
Expand Down
44 changes: 22 additions & 22 deletions src/ggrs_stage.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use crate::{world_snapshot::WorldSnapshot, PlayerInputs, Session};
use crate::{world_snapshot::WorldSnapshot, GGRSSchedule, PlayerInputs, Session};
use bevy::{prelude::*, reflect::TypeRegistry};
use ggrs::{
Config, GGRSError, GGRSRequest, GameStateCell, InputStatus, PlayerHandle, SessionState,
};
use instant::{Duration, Instant};

#[derive(Resource)]
/// The GGRSStage handles updating, saving and loading the game state.
pub(crate) struct GGRSStage<T>
where
T: Config,
{
/// Inside this schedule, all rollback systems are registered.
schedule: Schedule,
/// Used to register all types considered when loading and saving
pub(crate) type_registry: TypeRegistry,
/// This system is used to get an encoded representation of the input that GGRS can handle
Expand All @@ -30,16 +29,20 @@ where
run_slow: bool,
}

impl<T: Config + Send + Sync> Stage for GGRSStage<T> {
fn run(&mut self, world: &mut World) {
impl<T: Config + Send + Sync> GGRSStage<T> {
pub(crate) fn run(world: &mut World) {
let mut stage = world
.remove_resource::<GGRSStage<T>>()
.expect("failed to extract ggrs schedule");

// get delta time from last run() call and accumulate it
let delta = Instant::now().duration_since(self.last_update);
let mut fps_delta = 1. / self.update_frequency as f64;
if self.run_slow {
let delta = Instant::now().duration_since(stage.last_update);
let mut fps_delta = 1. / stage.update_frequency as f64;
if stage.run_slow {
fps_delta *= 1.1;
}
self.accumulator = self.accumulator.saturating_add(delta);
self.last_update = Instant::now();
stage.accumulator = stage.accumulator.saturating_add(delta);
stage.last_update = Instant::now();

// no matter what, poll remotes and send responses
if let Some(mut session) = world.get_resource_mut::<Session<T>>() {
Expand All @@ -55,28 +58,29 @@ impl<T: Config + Send + Sync> Stage for GGRSStage<T> {
}

// if we accumulated enough time, do steps
while self.accumulator.as_secs_f64() > fps_delta {
while stage.accumulator.as_secs_f64() > fps_delta {
// decrease accumulator
self.accumulator = self
stage.accumulator = stage
.accumulator
.saturating_sub(Duration::from_secs_f64(fps_delta));

// depending on the session type, doing a single update looks a bit different
let session = world.get_resource::<Session<T>>();
match session {
Some(&Session::SyncTestSession(_)) => self.run_synctest(world),
Some(&Session::P2PSession(_)) => self.run_p2p(world),
Some(&Session::SpectatorSession(_)) => self.run_spectator(world),
_ => self.reset(), // No session has been started yet
Some(&Session::SyncTestSession(_)) => stage.run_synctest(world),
Some(&Session::P2PSession(_)) => stage.run_p2p(world),
Some(&Session::SpectatorSession(_)) => stage.run_spectator(world),
_ => stage.reset(), // No session has been started yet
}
}

world.insert_resource(stage);
}
}

impl<T: Config> GGRSStage<T> {
pub(crate) fn new(input_system: Box<dyn System<In = PlayerHandle, Out = T::Input>>) -> Self {
Self {
schedule: Schedule::default(),
type_registry: TypeRegistry::default(),
input_system,
snapshots: Vec::new(),
Expand Down Expand Up @@ -248,7 +252,7 @@ impl<T: Config> GGRSStage<T> {
) {
debug!("advancing to frame: {}", self.frame + 1);
world.insert_resource(PlayerInputs::<T>(inputs));
self.schedule.run_once(world);
world.run_schedule(GGRSSchedule);
world.remove_resource::<PlayerInputs<T>>();
self.frame += 1;
debug!("frame {} completed", self.frame);
Expand All @@ -258,10 +262,6 @@ impl<T: Config> GGRSStage<T> {
self.update_frequency = update_frequency
}

pub(crate) fn set_schedule(&mut self, schedule: Schedule) {
self.schedule = schedule;
}

pub(crate) fn set_type_registry(&mut self, type_registry: TypeRegistry) {
self.type_registry = type_registry;
}
Expand Down
27 changes: 14 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![forbid(unsafe_code)] // let us try

use bevy::{
ecs::schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel},
prelude::*,
reflect::{FromType, GetTypeRegistration, TypeRegistry, TypeRegistryInternal},
};
Expand All @@ -15,10 +16,11 @@ pub use ggrs;
pub(crate) mod ggrs_stage;
pub(crate) mod world_snapshot;

/// Stage label for the Custom GGRS Stage.
pub const GGRS_UPDATE: &str = "ggrs_update";
const DEFAULT_FPS: usize = 60;

#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
pub struct GGRSSchedule;

/// Defines the Session that the GGRS Plugin should expect as a resource.
#[derive(Resource)]
pub enum Session<T: Config> {
Expand Down Expand Up @@ -94,7 +96,6 @@ pub struct GGRSPlugin<T: Config + Send + Sync> {
input_system: Option<Box<dyn System<In = PlayerHandle, Out = T::Input>>>,
fps: usize,
type_registry: TypeRegistry,
schedule: Schedule,
}

impl<T: Config + Send + Sync> Default for GGRSPlugin<T> {
Expand All @@ -118,7 +119,6 @@ impl<T: Config + Send + Sync> Default for GGRSPlugin<T> {
r
})),
},
schedule: Default::default(),
}
}
}
Expand Down Expand Up @@ -172,13 +172,6 @@ impl<T: Config + Send + Sync> GGRSPlugin<T> {
self
}

/// Adds a schedule into the GGRSStage that holds the game logic systems. This schedule should contain all
/// systems you want to be executed during frame advances.
pub fn with_rollback_schedule(mut self, schedule: Schedule) -> Self {
self.schedule = schedule;
self
}

/// Consumes the builder and makes changes on the bevy app according to the settings.
pub fn build(self, app: &mut App) {
let mut input_system = self
Expand All @@ -188,9 +181,17 @@ impl<T: Config + Send + Sync> GGRSPlugin<T> {
input_system.initialize(&mut app.world);
let mut stage = GGRSStage::<T>::new(input_system);
stage.set_update_frequency(self.fps);
stage.set_schedule(self.schedule);

let mut schedule = Schedule::default();
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Error,
..default()
});
app.add_schedule(GGRSSchedule, schedule);

stage.set_type_registry(self.type_registry);
app.add_stage_before(CoreStage::Update, GGRS_UPDATE, stage);
app.add_system(GGRSStage::<T>::run.in_base_set(CoreSet::PreUpdate));
app.insert_resource(stage);
// other resources
app.insert_resource(RollbackIdProvider::default());
}
Expand Down
16 changes: 11 additions & 5 deletions src/world_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ impl WorldSnapshot {
.enumerate()
{
let entity = entity.entity();
if let Some(component) = reflect_component.reflect(world, entity) {
let entity_ref = world.entity(entity);
if let Some(component) = reflect_component.reflect(entity_ref) {
assert_eq!(entity, snapshot.entities[entities_offset + i].entity);
// add the hash value of that component to the shapshot checksum, if that component supports hashing
if let Some(hash) = component.reflect_hash() {
Expand Down Expand Up @@ -174,11 +175,15 @@ impl WorldSnapshot {
// For example, an apply() will do an in-place update such that apply an
// array to an array will add items to the array instead of completely
// replacing the current array with the new one.
reflect_component.remove(world, entity);
reflect_component.insert(world, entity, &**component);
let mut entity_mut = world.entity_mut(entity);
reflect_component.remove(&mut entity_mut);
reflect_component.insert(&mut entity_mut, &**component);
}
// if we don't have any data saved, we need to remove that component from the entity
None => reflect_component.remove(world, entity),
None => {
let mut entity_mut = world.entity_mut(entity);
reflect_component.remove(&mut entity_mut);
}
}
} else {
// the entity in the world has no such component
Expand All @@ -188,7 +193,8 @@ impl WorldSnapshot {
.find(|comp| comp.type_name() == registration.type_name())
{
// if we have data saved in the snapshot, add the component to the entity
reflect_component.insert(world, entity, &**component);
let mut entity_mut = world.entity_mut(entity);
reflect_component.insert(&mut entity_mut, &**component);
}
// if both the snapshot and the world does not have the registered component, we don't need to to anything
}
Expand Down
Loading