Skip to content

Commit 77a22b7

Browse files
committed
doc: Include missing doc comments
1 parent b1b826e commit 77a22b7

File tree

9 files changed

+151
-31
lines changed

9 files changed

+151
-31
lines changed

src/app.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
use std::{
2-
collections::HashMap,
3-
error::Error,
4-
time::Duration,
5-
};
1+
use std::{collections::HashMap, error::Error, time::Duration};
62

73
use cgmath::{InnerSpace, Vector2};
84
use tokio::task::JoinHandle;
@@ -80,6 +76,9 @@ struct App<'a> {
8076
gui: Option<Gui>,
8177
client_session: Option<ClientSession>,
8278
connection_task: Option<ConnectionTaskHandle>,
79+
// Pushing pressed keys from event loop into this collection and processing in update() makes
80+
// movement continous. Naively checking for key press during event consumption leads to choppy
81+
// movement.
8382
input_state: InputState,
8483
local_player: Player,
8584
camera_pos: Vector2<f32>,
@@ -107,7 +106,15 @@ impl<'a> App<'a> {
107106
}
108107

109108
fn run(&mut self, event_loop: &mut EventLoop<()>) {
109+
// Frame-rate independent loop with fixed update, variable framerate.
110+
//
111+
// A naive calculation and passing of a deltaTime introduces floating point
112+
// precision errors, leading to choppy camera movement and unstable logic
113+
// even on high framerate. Here, think of it as renderer dictating time, and
114+
// logic update adapting to it.
110115
let mut previous_time = std::time::Instant::now();
116+
// How much application "clock" is behind real time. Also known as
117+
// "accumulator"
111118
let mut lag: f32 = 0.0;
112119
loop {
113120
let current_time = std::time::Instant::now();
@@ -239,6 +246,7 @@ impl<'a> App<'a> {
239246
let base_speed = 10.0;
240247
let mut direction = cgmath::vec2(0.0, 0.0);
241248

249+
// Apply input
242250
if self.input_state[InputEvent::MoveUp] {
243251
direction.y -= 1.0;
244252
}
@@ -257,19 +265,23 @@ impl<'a> App<'a> {
257265
direction = direction.normalize();
258266
}
259267

268+
// Move player
260269
self.local_player.velocity = direction * base_speed;
261270
self.local_player.pos += self.local_player.velocity;
262271
globals::clamp_player_to_bounds(&mut self.local_player);
263272

273+
// Move camera
264274
self.move_camera();
265275

276+
// Message server
266277
if self.local_player.velocity != cgmath::vec2(0.0, 0.0) {
267278
self.client_session
268279
.as_ref()
269280
.unwrap()
270281
.send_pos(&self.local_player);
271282
}
272283

284+
// Server healthcheck
273285
if !self.client_session.as_ref().unwrap().is_server_alive() {
274286
eprintln!("Connection to server was lost");
275287
self.client_session = None;
@@ -303,6 +315,9 @@ impl<'a> App<'a> {
303315
}
304316

305317
impl ApplicationHandler for App<'_> {
318+
// It is recommended for winit applications to create window and initialize their graphics context
319+
// after the first WindowEvent::Resumed even is received. There are systems that won't allow
320+
// applications to create a renderer until that.
306321
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
307322
let (window, renderer, gui) = Renderer::create_graphics(&event_loop);
308323

@@ -372,6 +387,7 @@ impl ApplicationHandler for App<'_> {
372387
_ => (),
373388
}
374389

390+
// Forward rest of events to GUI
375391
gui.handle_events(&window, &event);
376392
}
377393
}

src/client.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use crate::{
1212
Player, PlayerID,
1313
};
1414

15+
// Non-blocking channels are used for lock-free message passing from sync main thread to async
16+
// context and between multiple async tasks.
17+
// TODO: Research how to handle backpressure
1518
type ChannelSender = mpsc::UnboundedSender<String>;
1619
type ChannelReceiver = mpsc::UnboundedReceiver<String>;
1720

@@ -20,20 +23,27 @@ pub struct ClientSession {
2023
send_tx: ChannelSender,
2124
listen_task: JoinHandle<()>,
2225
send_task: JoinHandle<()>,
26+
/// The local player associated with the client
2327
session_player: Player,
28+
/// Last ping time used for initiating timeout when server is unavailable
2429
last_ping: std::time::Instant,
2530
}
2631

2732
pub type ClientSessionResult = Result<ClientSession, Box<dyn Error + Send + Sync>>;
2833

2934
impl ClientSession {
35+
/// Bind socket, initiate handshake procedure to server and setup messaging channels.
36+
/// Connection and handshake are retried until timeout.
3037
pub async fn new(server_address: String) -> ClientSessionResult {
3138
match tokio::time::timeout(globals::CONNECTION_TIMEOUT_SEC, async {
39+
// Socket bind
3240
let client_socket = UdpSocket::bind("0.0.0.0:0").await?;
3341
let client_socket = Arc::new(client_socket);
3442

43+
// Server connect
3544
let session_player = join_server(&client_socket, &server_address).await?;
3645

46+
// Message handlers
3747
let (listen_tx, listen_rx) = mpsc::unbounded_channel();
3848
let (send_tx, send_rx) = mpsc::unbounded_channel();
3949
let listen_task = tokio::spawn(listen_handler(client_socket.clone(), listen_tx));
@@ -71,6 +81,7 @@ impl ClientSession {
7181
pub fn receive_server_response(&mut self) -> Result<String, TryRecvError> {
7282
match self.listen_rx.try_recv() {
7383
Ok(response) => {
84+
// Update last ping
7485
if let Ok(Message::Ping) = Message::deserialize(&response) {
7586
self.last_ping = std::time::Instant::now();
7687
}
@@ -81,12 +92,14 @@ impl ClientSession {
8192
}
8293

8394
pub fn send_pos(&self, player: &Player) {
95+
// TODO: Avoid position self-reporting
8496
let _ = self
8597
.send_tx
8698
.send(Message::Position(player.id, player.pos).serialize());
8799
}
88100

89101
pub fn is_server_alive(&self) -> bool {
102+
// There's no need for separate timeout countdown timer
90103
self.last_ping.elapsed() < globals::CONNECTION_TIMEOUT_SEC
91104
}
92105

@@ -111,11 +124,13 @@ async fn join_server(
111124
let handshake_msg = Message::Handshake.serialize();
112125
// Loop abort happens on timeout in ClientSession::new()
113126
loop {
127+
// Send handshake
114128
client_socket
115129
.send_to(handshake_msg.as_bytes(), server_address)
116130
.await?;
117131
message::trace(format!("Sent: {handshake_msg}"));
118132

133+
// Wait for ACK
119134
match receive_with_retry_timeout(client_socket).await {
120135
Ok(response) => {
121136
if let Ok(Message::Ack(new_id, new_color)) = Message::deserialize(&response) {

src/fsm.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
/// Parameter used for first connection establishment
12
#[derive(Clone, Copy)]
23
pub enum SessionMode {
3-
CreateServer,
4+
CreateServer, /// Peer-hosted, hybrid server-client session
45
ConnectAsClientOnly,
56
}
67

@@ -21,7 +22,7 @@ pub enum State {
2122
/// state like a stack. Basically just a thin wrapper around a Vec, which is recommended for stack
2223
/// data structures by https://doc.rust-lang.org/std/collections/index.html#use-a-vec-when
2324
///
24-
/// There are third-party crates like rust_fsm, but not necessary to include too much crates.
25+
/// There are third-party crates like rust_fsm, but I prefer not to include too many crates.
2526
pub struct StateMachine {
2627
state_stack: Vec<State>,
2728
}

src/gui.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use winit::{event::WindowEvent, event_loop::ActiveEventLoop};
88

99
use crate::{fsm, globals};
1010

11+
/// GUI layer for all dialog boxes and the gameplay log output window.
1112
pub struct Gui {
1213
egui_glow: EguiGlow,
1314
log_messages: String,
@@ -37,17 +38,20 @@ impl Gui {
3738
}
3839
}
3940

41+
/// Forward native window events like input to egui.
4042
pub fn handle_events(&mut self, window: &winit::window::Window, event: &WindowEvent) {
4143
let _ = self.egui_glow.on_window_event(&window, &event);
4244
}
4345

46+
/// Execute UI code and populate batch before draw call
4447
pub fn prepare_frame(
4548
&mut self,
4649
window: &winit::window::Window,
4750
state_machine: &mut fsm::StateMachine,
4851
) {
4952
self.egui_glow
5053
.run(&window, |ctx| match state_machine.peek() {
54+
// Starter connection menu
5155
Some(fsm::State::Menu) | Some(fsm::State::Connecting { .. }) => show_menu(
5256
ctx,
5357
state_machine,
@@ -56,33 +60,40 @@ impl Gui {
5660
&mut self.status_text,
5761
&mut self.status_color,
5862
),
63+
// Gameplay state
5964
Some(fsm::State::Playing) => show_log(ctx, &self.log_messages),
65+
// Disconnect dialog
6066
Some(fsm::State::Disconnected) => show_disconnected_dialog(
6167
ctx,
6268
state_machine,
6369
&mut self.log_messages,
6470
&mut self.status_text,
6571
&mut self.status_color,
6672
),
73+
// Quit confirm dialog
6774
Some(fsm::State::QuitDialog) => show_quit_dialog(ctx, state_machine),
6875
_ => {}
6976
});
7077
}
7178

79+
/// Issue batched draw call
7280
pub fn draw(&mut self, window: &winit::window::Window) {
7381
self.egui_glow.paint(&window);
7482
}
7583

84+
/// Redirect message to gameplay log window
7685
pub fn log(&mut self, msg: String) {
7786
self.log_messages += &format!("{msg}\n");
7887
}
7988

89+
/// Error status on connection menu and Disconnected message dialog
8090
pub fn set_error_status(&mut self, msg: String) {
8191
self.status_color = Color32::RED;
8292
self.status_text = msg;
8393
}
8494
}
8595

96+
/// Starter connection menu
8697
fn show_menu(
8798
ctx: &egui::Context,
8899
state_machine: &mut fsm::StateMachine,
@@ -102,20 +113,23 @@ fn show_menu(
102113
.num_columns(2)
103114
.spacing([10.0, 10.0])
104115
.show(ui, |ui| {
116+
// Server address textbox
105117
ui.label("Server address:");
106118
ui.add(TextEdit::singleline(server_hostname).desired_width(150.0));
107119
ui.end_row();
108120

121+
// Server port number textbox
109122
ui.label("Port:");
110123
ui.add(TextEdit::singleline(server_port).desired_width(150.0));
111124
ui.end_row();
112125

126+
// Disable "Connect" button while client is trying to connect
113127
let connect_buttons_enabled =
114128
!matches!(state_machine.peek(), Some(fsm::State::Connecting { .. }));
129+
130+
// "Create server" button
115131
let create_button =
116132
ui.add_enabled(connect_buttons_enabled, Button::new("Create server"));
117-
let join_button =
118-
ui.add_enabled(connect_buttons_enabled, Button::new("Join server"));
119133
if create_button.clicked() {
120134
match verify_address_format(server_hostname, server_port) {
121135
Ok(_) => {
@@ -132,6 +146,10 @@ fn show_menu(
132146
}
133147
}
134148
}
149+
150+
// "Join server" button
151+
let join_button =
152+
ui.add_enabled(connect_buttons_enabled, Button::new("Join server"));
135153
if join_button.clicked() {
136154
match verify_address_format(server_hostname, server_port) {
137155
Ok(_) => {
@@ -148,9 +166,12 @@ fn show_menu(
148166
}
149167
}
150168
}
169+
170+
// Status label
151171
ui.colored_label(*status_color, status_text);
152172
ui.end_row();
153173

174+
// "Quit" button
154175
if ui.button("Quit").clicked() {
155176
state_machine.push(fsm::State::QuitDialog);
156177
}

src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,21 @@ pub mod globals {
5959
pub const WINDOW_SIZE: (u16, u16) = (800, 600);
6060
pub const WINDOW_TITLE: &str = "Multiplayer game demo by Bálint Kiss";
6161

62+
/// This is the granularity of how often to update logic and not to be confused
63+
/// with framerate limiting or 60 frames per second, because the main loop
64+
/// implementation uses a fixed update, variable framerate timestep algorithm.
65+
///
66+
/// 60 logic updates per second is a common value used in games.
67+
/// - Higher update rate (120) can lead to smoother gameplay, more precise
68+
/// control, at the cost of CPU load. Keep mobile devices in mind.
69+
/// - Lower update rate (30) reduces CPU load, runs game logic less frequently,
70+
/// but can make game less responsive.
6271
pub const MAX_LOGIC_UPDATE_PER_SEC: f32 = 60.0;
6372
pub const FIXED_UPDATE_TIMESTEP_SEC: f32 = 1.0 / MAX_LOGIC_UPDATE_PER_SEC;
6473

6574
pub const PLAYER_QUAD_SIZE: f32 = 24.0;
75+
76+
/// World bounds are relative to origin (0,0)
6677
pub const WORLD_BOUNDS: WorldBounds = WorldBounds {
6778
min_x: -1200.0,
6879
min_y: -1200.0,

src/main.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ fn main() -> Result<(), Box<dyn Error>> {
3636
message::set_trace(true);
3737
}
3838

39-
// Application window events, rendering and GUI are in syncronous environment and async code
40-
// cannot be called from there. Manage Tokio runtime separately to bridge sync
41-
// code with async code easily.
39+
// Application window events, rendering, and GUI are in syncronous context where async code
40+
// cannot be called directly. Managing Tokio runtime separately instead of relying on
41+
// "#[tokio::main]" in order to bridge sync code with async code easily .
4242
let rt = tokio::runtime::Builder::new_multi_thread()
4343
.enable_all()
4444
.build()?;
4545

46+
// Start a headless server only if option is set.
4647
if cli.server_only {
4748
println!("Starting server in headless mode");
4849
rt.block_on(async {
@@ -62,8 +63,9 @@ fn main() -> Result<(), Box<dyn Error>> {
6263
}
6364
}
6465
});
65-
Ok(())
66-
} else {
67-
app::run_app(&rt)
66+
return Ok(());
6867
}
68+
69+
// Run graphical client otherwise.
70+
app::run_app(&rt)
6971
}

src/message.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,26 @@ use crate::{Player, PlayerID};
66

77
#[derive(PartialEq)]
88
pub enum Message {
9+
/// Periodic ping message for server healthcheck
10+
// TODO: Extend for client disconnect check
911
Ping,
12+
13+
/// Initial handshake by client on join. Retried on UDP packet loss until timeout.
1014
Handshake,
15+
16+
/// Server response to received handshake
1117
Ack(PlayerID, Vector3<f32>),
18+
19+
/// Server response notifying all players still remaining on server about player exit so they
20+
/// can update their state.
1221
Leave(PlayerID),
22+
23+
/// Server's world replication of a single player position
24+
/// TODO: Currently sent one-by-one, make it a bulk send instead
1325
Replicate(Player),
14-
// TODO: Avoid clients self-reporting their exact own position and opt for sending input state
26+
27+
/// Player's position response after movement change.
28+
// TODO: Avoid clients self-reporting their exact own position and opt for sending input action
1529
// instead
1630
Position(PlayerID, Vector2<f32>),
1731
}

0 commit comments

Comments
 (0)