Skip to content

Commit

Permalink
feat: Client-side prediction first steps (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
AudranTourneur committed Jan 26, 2025
1 parent e74b2f5 commit 53589b6
Show file tree
Hide file tree
Showing 17 changed files with 352 additions and 236 deletions.
12 changes: 9 additions & 3 deletions client/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use crate::entities::stack::stack_update_system;
use crate::mob::*;
use crate::network::buffered_client::BufferedInputs;
use crate::network::buffered_client::{CurrentFrameInputs, PlayerTickInputsBuffer, SyncTime};
use crate::ui::hud::chat::{render_chat, setup_chat};
use crate::ui::menus::{setup_server_connect_loading_screen, update_server_connect_loading_screen};
use bevy::prelude::*;
Expand Down Expand Up @@ -90,7 +90,9 @@ pub fn game_plugin(app: &mut App) {
.init_resource::<FoxFeetTargets>()
.init_resource::<Animations>()
.init_resource::<TargetedMob>()
.init_resource::<BufferedInputs>()
.init_resource::<PlayerTickInputsBuffer>()
.init_resource::<CurrentFrameInputs>()
.init_resource::<SyncTime>()
.insert_resource(Time::<Fixed>::from_hz(TICKS_PER_SECOND as f64))
.add_event::<WorldRenderRequestUpdateEvent>()
.add_event::<PlayerSpawnEvent>()
Expand Down Expand Up @@ -195,12 +197,16 @@ pub fn game_plugin(app: &mut App) {
)
.run_if(in_state(GameState::Game)),
)
.add_systems(
PreUpdate,
pre_input_update_system.run_if(in_state(GameState::Game)),
)
.add_systems(
FixedPreUpdate,
poll_network_messages.run_if(in_state(GameState::Game)),
)
.add_systems(
Update,
FixedUpdate,
upload_player_inputs_system.run_if(in_state(GameState::Game)),
)
.add_systems(
Expand Down
61 changes: 51 additions & 10 deletions client/src/network/buffered_client.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,57 @@
use bevy::{prelude::*, utils::HashSet};
use serde::{Deserialize, Serialize};
use shared::messages::NetworkPlayerInput;
use shared::messages::PlayerFrameInput;

#[derive(Debug, Default, Resource)]
pub struct BufferedInputs {
#[allow(dead_code)]
pub buffer: Vec<PlayerFrameInputs>,
pub struct PlayerTickInputsBuffer {
pub buffer: Vec<PlayerFrameInput>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerFrameInputs {
pub time_ms: u64,
pub inputs: HashSet<NetworkPlayerInput>,
pub camera: Quat,
#[derive(Resource, Default)]
pub struct CurrentFrameInputs(pub PlayerFrameInput);

pub trait CurrentFrameInputsExt {
fn reset(&mut self, time: u64);
}

impl CurrentFrameInputsExt for CurrentFrameInputs {
fn reset(&mut self, time: u64) {
self.0 = PlayerFrameInput {
time_ms: time,
inputs: HashSet::default(),
camera: Quat::default(),
};
}
}

// Represents the synchronized time of the client
// Currently, this is just the UNIX timestamp in milliseconds, assumed to be the same on the server and client
// A NTP-like system could be implemented in the future
#[derive(Resource)]
pub struct SyncTime {
pub last_time_ms: u64,
pub curr_time_ms: u64,
}

impl Default for SyncTime {
fn default() -> Self {
let current_time_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;

Self {
last_time_ms: current_time_ms,
curr_time_ms: current_time_ms,
}
}
}

pub trait SyncTimeExt {
fn delta(&self) -> u64;
}

impl SyncTimeExt for SyncTime {
fn delta(&self) -> u64 {
self.curr_time_ms - self.last_time_ms
}
}
69 changes: 8 additions & 61 deletions client/src/network/inputs.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,18 @@
use crate::input::keyboard::is_action_pressed;
use crate::KeyMap;
use crate::{input::data::GameAction, world::time::ClientTime};
use bevy::input::ButtonInput;
use bevy::prelude::*;
use bevy_renet::renet::RenetClient;
use ordered_float::OrderedFloat;
use shared::messages::{CustomQuaternion, NetworkPlayerInput, PlayerInputs};
use shared::messages::ClientToServerMessage;

use super::buffered_client::PlayerTickInputsBuffer;
use super::SendGameMessageExtension;

pub fn upload_player_inputs_system(
mut client: ResMut<RenetClient>,
keyboard_input: Res<ButtonInput<KeyCode>>,
key_map: Res<KeyMap>,
mut last_camera_orientation: Local<Option<Quat>>,
camera: Query<&Transform, With<Camera>>,
client_time: Res<ClientTime>,
mut inputs: ResMut<PlayerTickInputsBuffer>,
) {
let mut actions: Vec<NetworkPlayerInput> = vec![];
if is_action_pressed(GameAction::MoveBackward, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::MoveBackward)
}
if is_action_pressed(GameAction::MoveForward, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::MoveForward)
}
if is_action_pressed(GameAction::MoveLeft, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::MoveLeft)
}
if is_action_pressed(GameAction::MoveRight, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::MoveRight)
}
if is_action_pressed(GameAction::Jump, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::Jump);
}
if is_action_pressed(GameAction::ToggleFlyMode, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::ToggleFlyMode)
}
if is_action_pressed(GameAction::FlyUp, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::FlyUp);
}
if is_action_pressed(GameAction::FlyDown, &keyboard_input, &key_map) {
actions.push(NetworkPlayerInput::FlyDown);
}

let camera_transform = camera.single();
let camera_orientation = camera_transform.rotation;

if let Some(last_cam) = last_camera_orientation.as_mut() {
if *last_cam != camera_orientation {
actions.push(NetworkPlayerInput::CameraMovement(CustomQuaternion {
x: OrderedFloat(camera_orientation.x),
y: OrderedFloat(camera_orientation.y),
z: OrderedFloat(camera_orientation.z),
w: OrderedFloat(camera_orientation.w),
}));
*last_camera_orientation = Some(camera_orientation);
}
} else {
*last_camera_orientation = Some(camera_orientation);
}

let msg = PlayerInputs {
tick: client_time.0,
actions,
};
if !msg.actions.is_empty() {
// debug!("Sending player inputs: {:?}", msg);
client.send_game_message(msg.into());
let mut frames = vec![];
for input in inputs.buffer.iter() {
frames.push(input.clone());
}
client.send_game_message(ClientToServerMessage::PlayerInputs(frames));
inputs.buffer.clear();
}
4 changes: 2 additions & 2 deletions client/src/network/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ pub fn update_world_from_network(

while let Ok(msg) = client.receive_game_message() {
// truncate the message to 1000 characters
let debug_msg = format!("{:?}", msg).chars().take(1000).collect::<String>();
info!("Received message: {}", debug_msg);
// let debug_msg = format!("{:?}", msg).chars().take(1000).collect::<String>();
// info!("Received message: {}", debug_msg);
match msg {
ServerToClientMessage::WorldUpdate(world_update) => {
debug!(
Expand Down
70 changes: 66 additions & 4 deletions client/src/player/controller.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::input::data::GameAction;
use crate::input::keyboard::*;
use crate::network::buffered_client::{
CurrentFrameInputs, CurrentFrameInputsExt, PlayerTickInputsBuffer, SyncTime, SyncTimeExt,
};
use crate::player::ViewMode;
use crate::ui::hud::debug::DebugOptions;
use crate::ui::hud::UIMode;
use crate::world::{ClientWorldMap, WorldRenderRequestUpdateEvent};
use crate::KeyMap;
use bevy::prelude::*;
use shared::messages::NetworkAction;
use shared::players::movement::simulate_player_movement;
use shared::players::Player;

use super::CurrentPlayerMarker;
Expand All @@ -15,12 +20,43 @@ pub struct PlayerMaterialHandle {
pub handle: Handle<StandardMaterial>,
}

pub fn pre_input_update_system(
mut frame_inputs: ResMut<CurrentFrameInputs>,
mut tick_buffer: ResMut<PlayerTickInputsBuffer>,
mut sync_time: ResMut<SyncTime>,
) {
sync_time.curr_time_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;

let inputs = frame_inputs.0.clone();
tick_buffer.buffer.push(inputs);
frame_inputs.reset(sync_time.curr_time_ms);
}

pub fn player_movement_system(
queries: Query<&mut Player, With<CurrentPlayerMarker>>,
resources: (Res<ButtonInput<KeyCode>>, Res<UIMode>, Res<KeyMap>),
queries: Query<(&mut Player, &mut Transform), (With<CurrentPlayerMarker>, Without<Camera>)>,
camera: Query<&Transform, With<Camera>>,
resources: (
Res<ButtonInput<KeyCode>>,
Res<UIMode>,
Res<KeyMap>,
ResMut<CurrentFrameInputs>,
),
time: Res<SyncTime>,
world_map: Res<ClientWorldMap>,
) {
let mut player_query = queries;
let (keyboard_input, ui_mode, key_map) = resources;
let (keyboard_input, ui_mode, key_map, mut frame_inputs) = resources;

if time.delta() == 0 {
return;
}

let camera = camera.single();
let camera_orientation = camera.rotation;
frame_inputs.0.camera = camera_orientation;

let res = player_query.get_single_mut();
// Return early if the player has not been spawned yet
Expand All @@ -29,13 +65,39 @@ pub fn player_movement_system(
return;
}

let mut player = player_query.single_mut();
let (mut player, mut player_transform) = player_query.single_mut();

if *ui_mode == UIMode::Closed
&& is_action_just_pressed(GameAction::ToggleFlyMode, &keyboard_input, &key_map)
{
player.toggle_fly_mode();
}

if is_action_pressed(GameAction::MoveBackward, &keyboard_input, &key_map) {
frame_inputs.0.inputs.insert(NetworkAction::MoveBackward);
}
if is_action_pressed(GameAction::MoveForward, &keyboard_input, &key_map) {
frame_inputs.0.inputs.insert(NetworkAction::MoveForward);
}
if is_action_pressed(GameAction::MoveLeft, &keyboard_input, &key_map) {
frame_inputs.0.inputs.insert(NetworkAction::MoveLeft);
}
if is_action_pressed(GameAction::MoveRight, &keyboard_input, &key_map) {
frame_inputs.0.inputs.insert(NetworkAction::MoveRight);
}
if is_action_pressed(GameAction::Jump, &keyboard_input, &key_map) {
frame_inputs.0.inputs.insert(NetworkAction::JumpOrFlyUp);
}
if is_action_pressed(GameAction::FlyDown, &keyboard_input, &key_map) {
frame_inputs.0.inputs.insert(NetworkAction::SneakOrFlyDown);
}

let world_clone = world_map.clone();
let frame_inputs = frame_inputs.0.clone();

simulate_player_movement(&mut player, &world_clone, &frame_inputs, 17);

player_transform.translation = player.position;
}

pub fn first_and_third_person_view_system(
Expand Down
18 changes: 18 additions & 0 deletions client/src/world/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,24 @@ impl WorldMap for ClientWorldMap {
false
}
}

fn get_surrounding_chunks(&self, position: Vec3, radius: i32) -> Vec<IVec3> {
let mut chunks: Vec<IVec3> = Vec::new();
let x: i32 = position.x.round() as i32;
let y: i32 = position.y.round() as i32;
let z: i32 = position.z.round() as i32;
let cx: i32 = block_to_chunk_coord(x);
let cy: i32 = block_to_chunk_coord(y);
let cz: i32 = block_to_chunk_coord(z);
for dx in -radius..=radius {
for dy in -radius..=radius {
for dz in -radius..=radius {
chunks.push(IVec3::new(cx + dx, cy + dy, cz + dz));
}
}
}
chunks
}
}

impl ClientWorldMap {
Expand Down
9 changes: 7 additions & 2 deletions server/src/network/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,13 @@ fn server_update_system(
}
}
ClientToServerMessage::PlayerInputs(inputs) => {
// debug!("Received player inputs: {:?} for {:?}", inputs, client_id);
ev_player_inputs.send(PlayerInputsEvent { client_id, inputs });
// info!("Received {} player inputs", inputs.len());
for input in inputs.iter() {
ev_player_inputs.send(PlayerInputsEvent {
client_id,
input: input.clone(),
});
}
}
ClientToServerMessage::SaveWorldRequest => {
debug!("Save request received from client with session token");
Expand Down
6 changes: 5 additions & 1 deletion server/src/world/broadcast_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ pub fn broadcast_world_state(
}

for client in server.clients_id().iter_mut() {
let player = world_map.players.get(client).unwrap().clone();
let player = world_map.players.get_mut(client);
let player = match player {
Some(p) => p.clone(),
None => continue,
};
let msg = WorldUpdate {
tick: time.0,
time: ts,
Expand Down
Loading

0 comments on commit 53589b6

Please sign in to comment.