Skip to content

Commit

Permalink
feat: RNG Generator in bones (#473)
Browse files Browse the repository at this point in the history
This PR focused on finally linking the random seed generated by the
matchmaker (pushing it to client was implemented in previous PR) into
the GgrsSessionRunner + SyncingInfo, and then using it to create and
insert a `RngGenerator` resource automatically for the developer to use
easily.

Additionally, I created reworked the previous flow, now having a new
interface that both re-wraps all of the Turborand methods (a few
functions were inconsistent naming-wise, and gives us more flexibility
to change stuff in the future), and also implements a number of missing
functionality that will be helpful to devs. This new `RngGenerator`
struct now supports:

1. Basic Number Generation: Methods for integers, floats, and booleans.
2. Range-based Generation: Methods for numbers within specified ranges.
3. Character/String Generation: Methods for various character types and
ASCII strings.
4. Collection Manipulation: Methods for shuffling and sampling from
collections (Vec/SVec/Vecdeque)
5. Probability/Weighted Selection: Methods for chance-based and weighted
random selection.
6. Vector Generation: Methods for random Vec2 and Vec3 values.
  • Loading branch information
RockasMockas authored Oct 6, 2024
1 parent 1d0837d commit c54f1ec
Show file tree
Hide file tree
Showing 4 changed files with 483 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions framework_crates/bones_framework/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ smallvec = "1.10"
iroh-quinn = "0.10"
iroh-net = "0.22"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
turborand = { version = "0.10.0", features = ["atomic"] }

directories = "5.0"

Expand Down
83 changes: 75 additions & 8 deletions framework_crates/bones_framework/src/networking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use self::{
input::{DenseInput, NetworkInputConfig, NetworkPlayerControl, NetworkPlayerControls},
socket::Socket,
};
use crate::networking::online::OnlineMatchmakerResponse;
pub use crate::networking::random::RngGenerator;
use crate::prelude::*;
use bones_matchmaker_proto::{MATCH_ALPN, PLAY_ALPN};
use ggrs::P2PSession;
Expand All @@ -26,6 +28,7 @@ pub mod online;
pub mod online_lobby;
pub mod online_matchmaking;
pub mod proto;
pub mod random;
pub mod socket;

#[cfg(feature = "net-debug")]
Expand Down Expand Up @@ -58,7 +61,9 @@ impl From<ggrs::InputStatus> for NetworkInputStatus {

/// Module prelude.
pub mod prelude {
pub use super::{input, lan, online, proto, DisconnectedPlayers, SyncingInfo, RUNTIME};
pub use super::{
input, lan, online, proto, random, DisconnectedPlayers, RngGenerator, SyncingInfo, RUNTIME,
};

#[cfg(feature = "net-debug")]
pub use super::debug::prelude::*;
Expand All @@ -73,6 +78,9 @@ pub mod prelude {
/// which is then used to compute timestep so ggrs and networking match.
pub const NETWORK_FRAME_RATE_FACTOR: f32 = 0.9;

/// Default frame rate to run at if user provides none
pub const NETWORK_DEFAULT_SIMULATION_FRAME_RATE: f32 = 60.0;

/// Number of frames client may predict beyond confirmed frame before freezing and waiting
/// for inputs from other players. Default value if not specified in [`GgrsSessionRunnerInfo`].
pub const NETWORK_MAX_PREDICTION_WINDOW_DEFAULT: usize = 7;
Expand Down Expand Up @@ -203,11 +211,15 @@ pub enum SyncingInfo {
local_frame_delay: usize,
/// List of disconnected players (their idx)
disconnected_players: SVec<usize>,
/// The random seed for this session
random_seed: u64,
},
/// Holds data for an offline session
Offline {
/// Current frame of simulation step
current_frame: i32,
/// The random seed for this session
random_seed: u64,
},
}

Expand All @@ -226,7 +238,7 @@ impl SyncingInfo {
pub fn current_frame(&self) -> i32 {
match self {
SyncingInfo::Online { current_frame, .. } => *current_frame,
SyncingInfo::Offline { current_frame } => *current_frame,
SyncingInfo::Offline { current_frame, .. } => *current_frame,
}
}

Expand All @@ -237,7 +249,7 @@ impl SyncingInfo {
last_confirmed_frame,
..
} => *last_confirmed_frame,
SyncingInfo::Offline { current_frame } => *current_frame,
SyncingInfo::Offline { current_frame, .. } => *current_frame,
}
}
/// Getter for socket.
Expand Down Expand Up @@ -467,6 +479,14 @@ impl SyncingInfo {
SyncingInfo::Offline { .. } => None,
}
}

/// Getter for the random seed.
pub fn random_seed(&self) -> u64 {
match self {
SyncingInfo::Online { random_seed, .. } => *random_seed,
SyncingInfo::Offline { random_seed, .. } => *random_seed,
}
}
}

/// Resource tracking which players have been disconnected.
Expand Down Expand Up @@ -521,6 +541,9 @@ pub struct GgrsSessionRunner<'a, InputTypes: NetworkInputConfig<'a>> {

/// Local input delay ggrs session was initialized with
local_input_delay: usize,

/// The random seed used for this session
pub random_seed: u64,
}

/// The info required to create a [`GgrsSessionRunner`].
Expand All @@ -543,6 +566,8 @@ pub struct GgrsSessionRunnerInfo {
///
/// `None` will use Bone's default.
pub local_input_delay: Option<usize>,
/// The random seed used for this session
pub random_seed: u64,
}

impl GgrsSessionRunnerInfo {
Expand All @@ -551,6 +576,7 @@ impl GgrsSessionRunnerInfo {
socket: Socket,
max_prediction_window: Option<usize>,
local_input_delay: Option<usize>,
random_seed: u64,
) -> Self {
let player_idx = socket.player_idx();
let player_count = socket.player_count();
Expand All @@ -560,6 +586,7 @@ impl GgrsSessionRunnerInfo {
player_count,
max_prediction_window,
local_input_delay,
random_seed,
}
}
}
Expand All @@ -568,11 +595,43 @@ impl<'a, InputTypes> GgrsSessionRunner<'a, InputTypes>
where
InputTypes: NetworkInputConfig<'a>,
{
/// Create a new sessino runner.
pub fn new(simulation_fps: f32, info: GgrsSessionRunnerInfo) -> Self
/// Creates a new session runner from a `OnlineMatchmakerResponse::GameStarting`
/// Any input values set as `None` will be set to default.
/// If response is not `GameStarting` returns None.
pub fn new_networked_game_starting(
target_fps: Option<f32>,
max_prediction_window: Option<usize>,
local_input_delay: Option<usize>,
matchmaker_resp_game_starting: OnlineMatchmakerResponse,
) -> Option<Self> {
if let OnlineMatchmakerResponse::GameStarting {
socket,
player_idx: _,
player_count: _,
random_seed,
} = matchmaker_resp_game_starting
{
Some(Self::new(
target_fps,
GgrsSessionRunnerInfo::new(
socket.ggrs_socket(),
max_prediction_window,
local_input_delay,
random_seed,
),
))
} else {
None
}
}

/// Creates a new session runner from scratch.
pub fn new(target_fps: Option<f32>, info: GgrsSessionRunnerInfo) -> Self
where
Self: Sized,
{
let simulation_fps = target_fps.unwrap_or(NETWORK_DEFAULT_SIMULATION_FRAME_RATE);

// Modified FPS may not be an integer, but ggrs requires integer fps, so we clamp and round
// to integer so our computed timestep will match that of ggrs.
let network_fps = (simulation_fps * NETWORK_FRAME_RATE_FACTOR) as f64;
Expand Down Expand Up @@ -634,6 +693,7 @@ where
socket: info.socket.clone(),
local_input_delay,
local_input_disabled: false,
random_seed: info.random_seed,
}
}
}
Expand Down Expand Up @@ -841,9 +901,14 @@ where
}
}

// TODO: Make sure NetworkInfo is initialized immediately when session is created,
// Create and insert the RngGenerator resource if it doesn't exist
if world.resources.get::<RngGenerator>().is_none() {
let rng_generator = RngGenerator::new(self.random_seed);
world.insert_resource(rng_generator);
}

// TODO: Make sure SyncingInfo is initialized immediately when session is created,
// even before a frame has advanced.
//
// The existance of this resource may be used to determine if in an online match, and there could
// be race if expected it to exist but testing before first frame advance.
world.insert_resource(SyncingInfo::Online {
Expand All @@ -857,6 +922,7 @@ where
.disconnected_players
.clone()
.into(),
random_seed: self.random_seed,
});

// Disconnected players persisted on session runner, and updated each frame.
Expand Down Expand Up @@ -951,8 +1017,9 @@ where
player_count: self.session.num_players().try_into().unwrap(),
max_prediction_window: Some(self.session.max_prediction()),
local_input_delay: Some(self.local_input_delay),
random_seed: self.random_seed,
};
*self = GgrsSessionRunner::new(self.original_fps as f32, runner_info);
*self = GgrsSessionRunner::new(Some(self.original_fps as f32), runner_info);
}

fn disable_local_input(&mut self, input_disabled: bool) {
Expand Down
Loading

0 comments on commit c54f1ec

Please sign in to comment.