Created
December 21, 2023 03:28
-
-
Save JacobMJones/5cfe20ae3a916f2e1f4deda32738444e to your computer and use it in GitHub Desktop.
rust server updated commented
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//TcpListener: A TCP socket server, listening for connections from TCP clients. | |
//TcpStream: Represents a TCP connection to a remote server, used for sending and receiving data. | |
use tokio::net::{TcpListener, TcpStream}; | |
//Connection: Manages a connection to a mini-Redis server, handling the sending and receiving of data frames. | |
//Frame: Represents a frame in the Redis protocol, which is a basic unit of communication in Redis, containing commands or data. | |
use mini_redis::{Connection, Frame}; | |
use mini_redis::Command::{self, Get, Set}; | |
// Bytes: A reference-counted slice of bytes, commonly used for efficiently handling and sharing data without copying it. | |
use bytes::Bytes; | |
//HashMap: A hash table data structure that stores key-value pairs. It allows for fast retrieval of values based on their unique keys. | |
use std::collections::HashMap; | |
//Atomic Reference Counting. It's a thread-safe reference-counting pointer used to share immutable data between multiple threads. | |
//Its primary purpose is to enable multiple threads to hold references to data | |
use std::sync::Arc; | |
//This is a compiler directive that disables the warning for using non-camel case type names. I | |
#[allow(non_camel_case_types)] | |
//Type alias to compose several types: Arc, Tokio's mutex, and a Hashmap that maps strings to byte values | |
//player positions is a hash map. where player position data will be stored. | |
//Basically Arc and tokio control the access to the hash | |
type player_positions = Arc<tokio::sync::Mutex<HashMap<String, Bytes>>>; | |
//TCP server entry point that listens for client connections on a specific port. | |
//It handles incoming connections concurrently by spawning separate asynchronous tasks for each connected client. | |
#[tokio::main] | |
async fn main() { | |
// Binds a TCP listener to the local address 127.0.0.1 on port 6379. | |
// This listener will accept incoming TCP connections. | |
let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap(); // let listener = TcpListener::bind("0.0.0.0:6379").await.unwrap(); | |
println!("Listening"); | |
// Creates a new, shared (Arc-wrapped), thread-safe (Mutex-wrapped) HashMap. | |
// This HashMap will store player positions, with each player's IP as the key. | |
let player_positions: Arc<tokio::sync::Mutex<HashMap<String, Bytes>>> = Arc::new(tokio::sync::Mutex::new(HashMap::new())); | |
//infinite loop to continuously listen for and accept new connections. | |
loop { | |
// Accepts a new connection. This will wait (asynchronously) until a new client connects. | |
// 'socket' is the connected socket, 'addr' is the address of the connecting client. | |
let (socket, addr) = listener.accept().await.unwrap(); | |
// Checks if the connecting IP address is already connected. | |
for (ip, _) in player_positions.lock().await.iter() { | |
if ip == &addr.ip().to_string() { | |
println!("Already connected"); | |
continue; | |
} | |
} | |
println!("Accepted from: {}", addr.ip()); | |
// Clones the Arc, increasing the reference count, to safely share player_positions | |
// across multiple threads. | |
let player_positions = player_positions.clone(); // is vector: ip, position | |
// Spawns a new asynchronous task for each client connection. | |
// This allows handling multiple clients concurrently. | |
tokio::spawn(async move { | |
// Calls the 'process' function, passing the socket, shared player_positions, and client address. | |
// Each client will be processed in its own asynchronous task. | |
process(socket, player_positions, addr).await; | |
}); | |
} | |
} | |
async fn process(socket: TcpStream, player_positions: player_positions, sender: std::net::SocketAddr) { | |
let screen_size = [300, 300]; | |
let mut connection = Connection::new(socket); | |
let player_movement: HashMap<&str, [i8; 2]> = HashMap::from([("w", [0, 1]), ("a", [-1, 0]), ("s", [0, -1]), ("d", [1, 0])]); | |
let mut player_positions = player_positions.lock().await; | |
while let Some(frame) = connection.read_frame().await.unwrap() { | |
// println!("GOT: {:?}", frame); | |
let response = match Command::from_frame(frame).unwrap() { | |
Set(cmd) => { | |
//TODO: Make sure when setting a player position, it doesn't go out of bounds | |
player_positions.insert(sender.ip().to_string(), cmd.value().clone()); //sets player position based on sender ip | |
//println!("Set command received. Updated player_positions: {:?}", player_positions); | |
Frame::Simple("OK".to_string()) // throwaway response | |
} | |
Get(cmd) => { | |
if cmd.key() == "PUD" { // returns all the player positions | |
let mut player_update = String::new(); | |
for (key, value) in player_positions.iter() { | |
player_update.push_str(&format!("{}:{};", key, String::from_utf8(value.to_vec()).unwrap())); | |
} | |
Frame::Bulk(player_update.into()) | |
} else if cmd.key() == "w" || cmd.key() == "a" || cmd.key() == "s" || cmd.key() == "d" { // handles player movement | |
println!("Get command received: {:?}", cmd); | |
let mut player_pos_array = split_coords(String::from_utf8(player_positions.get(&sender.ip().to_string()).unwrap().to_vec()).unwrap()).await; | |
let player_movement = player_movement.get(cmd.key()).unwrap(); | |
if player_movement[0] == 1 { // this ugly shit is because we have to subtract i8 from u8, which is not allowed. Also makes sure bounds are checked. | |
if player_pos_array[0] < screen_size[0] { | |
player_pos_array[0] += 1; | |
} | |
} | |
if player_movement[0] == -1 { | |
if player_pos_array[0] > 0 { | |
player_pos_array[0] -= 1; | |
} | |
} | |
if player_movement[1] == 1 { | |
if player_pos_array[1] < screen_size[1] { | |
player_pos_array[1] += 1; | |
} | |
} | |
if player_movement[1] == -1 { | |
if player_pos_array[1] > 0 { | |
player_pos_array[1] -= 1; | |
} | |
} | |
let player_pos = Bytes::from(format!("{},{}", player_pos_array[0], player_pos_array[1])); | |
player_positions.insert(sender.ip().to_string(), player_pos.clone()); // updates database with new player position | |
Frame::Bulk(player_pos.clone()) // returns new position | |
} else { | |
// here if get bytes do not match to PUD or directional keys | |
println!("Get command received: {:?}", cmd); | |
Frame::Null | |
} | |
} | |
cmd => panic!("unimplemented {:?}", cmd), | |
}; | |
// Write the response to the client | |
connection.write_frame(&response).await.unwrap(); | |
} | |
} | |
async fn split_coords(text: String) -> [u16; 2] { // splits bytes like "0,300" into [0, 300] | |
let mut split_text = text.split(|x| x == ','); | |
[split_text.next().unwrap().trim().parse::<u16>().unwrap(), split_text.next().unwrap().trim().parse::<u16>().unwrap()] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment