Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| #![feature(used)] | |
| extern crate init; | |
| extern crate libc; | |
| #[macro_use] | |
| extern crate log; | |
| extern crate netcode_api; | |
| extern crate netcode_io_sys as nio; | |
| extern crate netcode_terrible_constants; | |
| extern crate reliable_io_sys as rio; | |
| use netcode_api::ClientId; | |
| use netcode_api::ClientSlot; | |
| use netcode_api::NetcodeClient; | |
| use netcode_api::flags as netcode_api_flags; | |
| use std::collections::HashMap; | |
| use std::ffi::CString; | |
| use std::os::raw::c_void; | |
| use std::time::Instant; | |
| #[derive(Clone, Debug)] | |
| pub struct ClientConfig { | |
| pub private_key: [u8; 32], | |
| pub protocol_id: u64, | |
| // TODO(acmcarther): This doesn't seem to match with netcode_api::ClientId | |
| pub client_id: u8, | |
| pub server_addr: String, | |
| pub max_connection_count: u32, | |
| pub max_unfragmented_packet_size: u32, | |
| } | |
| pub struct Client { | |
| config: ClientConfig, | |
| connecting: bool, | |
| nio_client: *mut nio::netcode_client_t, | |
| rio_endpoint: Option<*mut rio::reliable_endpoint_t>, | |
| unprocessed_inbound_packets: Vec<Vec<u8>>, | |
| start_time: Instant, | |
| } | |
| impl Default for ClientConfig { | |
| fn default() -> ClientConfig { | |
| let mut client_id: u8 = 0; | |
| unsafe { | |
| nio::netcode_random_bytes(&mut client_id, 8 /* bytes */); | |
| } | |
| ClientConfig { | |
| private_key: netcode_terrible_constants::PRIVATE_KEY, | |
| protocol_id: netcode_terrible_constants::PROTOCOL_ID, | |
| server_addr: netcode_api_flags::nio_server_addr::CONFIG.get_value(), | |
| client_id: client_id, | |
| max_connection_count: netcode_api_flags::max_connection_count::CONFIG.get_value(), | |
| max_unfragmented_packet_size: netcode_api_flags::max_unfragmented_packet_size::CONFIG | |
| .get_value(), | |
| } | |
| } | |
| } | |
| impl Client { | |
| pub fn start_from_config(config: ClientConfig) -> Box<Client> { | |
| let mut nio_client_config: nio::netcode_client_config_t = unsafe { std::mem::uninitialized() }; | |
| unsafe { | |
| nio::netcode_default_client_config(&mut nio_client_config); | |
| } | |
| let self_addr = CString::new("0.0.0.0".to_owned()).unwrap(); | |
| let nio_client: *mut nio::netcode_client_t = | |
| unsafe { nio::netcode_client_create(self_addr.as_ptr(), &nio_client_config, 0.0) }; | |
| if nio_client == std::ptr::null_mut() { | |
| error!("Could not create netcode_io server"); | |
| panic!("Could not create netcode_io server"); | |
| } | |
| let mut connect_token: [u8; nio::NETCODE_CONNECT_TOKEN_BYTES as usize] = | |
| [0; nio::NETCODE_CONNECT_TOKEN_BYTES as usize]; | |
| let addr = CString::new(config.server_addr.clone()).unwrap(); | |
| let mut all_addresses = [addr.as_ptr()]; | |
| unsafe { | |
| let token_result = nio::netcode_generate_connect_token( | |
| 1, /* num_server_addresses */ | |
| all_addresses.as_mut_ptr(), | |
| all_addresses.as_mut_ptr(), | |
| netcode_terrible_constants::CONNECT_TOKEN_EXPIRY, | |
| netcode_terrible_constants::CONNECT_TOKEN_TIMEOUT, | |
| config.client_id as u64, | |
| config.protocol_id, | |
| 0, /* sequence */ | |
| config.private_key.as_ptr(), | |
| connect_token.as_mut_ptr(), | |
| ); | |
| if token_result != (nio::NETCODE_OK as i32) { | |
| error!("Could not create connect token"); | |
| panic!("Could not create connect token"); | |
| } | |
| } | |
| unsafe { | |
| nio::netcode_client_connect(nio_client, connect_token.as_mut_ptr()); | |
| } | |
| // Create a client without a rio endpoint (temporarily) | |
| // This is necesary so that we can send the client as the context argument to | |
| // the rio callbacks. | |
| // Its a bit jank, but necessary... | |
| let client = unsafe { | |
| let mut client = Box::new(Client { | |
| config: config, | |
| connecting: true, | |
| nio_client: nio_client, | |
| rio_endpoint: None, | |
| unprocessed_inbound_packets: Vec::new(), | |
| start_time: Instant::now(), | |
| }); | |
| let raw_client_ptr = Box::into_raw(client); | |
| let rio_endpoint = unsafe { | |
| util::create_rio_endpoint(raw_client_ptr, 0.0 /* time since start */) | |
| }; | |
| (*raw_client_ptr).rio_endpoint = Some(rio_endpoint); | |
| Box::from_raw(raw_client_ptr) | |
| }; | |
| client | |
| } | |
| fn tend_connection(&mut self, time_since_start_s: f64) { | |
| if self.rio_endpoint.is_none() { | |
| debug!("Tended disconnected connection"); | |
| return; | |
| } | |
| let still_connected = unsafe { | |
| nio::netcode_client_state(self.nio_client) == (nio::NETCODE_CLIENT_STATE_CONNECTED as i32) | |
| }; | |
| if !self.connecting && !still_connected { | |
| // Rio endpoint guaranteed to exist at this point | |
| unsafe { | |
| rio::reliable_endpoint_destroy(self.rio_endpoint.take().unwrap()); | |
| } | |
| return; | |
| } | |
| if self.connecting && still_connected { | |
| self.connecting = false; | |
| } | |
| debug_assert!(self.rio_endpoint.is_some()); | |
| self.tend_packets(); | |
| self.tend_acks(time_since_start_s); | |
| } | |
| fn tend_packets(&self) { | |
| let rio_endpoint = self.rio_endpoint.unwrap(); | |
| loop { | |
| let mut packet_byte_count = 0; | |
| let mut packet_sequence = 0; | |
| let received_packet = unsafe { | |
| nio::netcode_client_receive_packet( | |
| self.nio_client, | |
| &mut packet_byte_count, | |
| &mut packet_sequence, | |
| ) | |
| }; | |
| if received_packet == std::ptr::null_mut() { | |
| break; | |
| } | |
| // Extracts ACK metadata and processes packet via callback | |
| unsafe { | |
| rio::reliable_endpoint_receive_packet(rio_endpoint, received_packet, packet_byte_count); | |
| nio::netcode_client_free_packet(self.nio_client, received_packet); | |
| } | |
| // TODO(acmcarther): This duplicates logic with tend_connection: fix | |
| { | |
| let disconnected = unsafe { | |
| nio::netcode_client_state(self.nio_client) | |
| <= (nio::NETCODE_CLIENT_STATE_DISCONNECTED as i32) | |
| }; | |
| if disconnected { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| fn tend_acks(&self, time_since_start_s: f64) { | |
| let rio_endpoint = self.rio_endpoint.unwrap(); | |
| unsafe { | |
| rio::reliable_endpoint_update(rio_endpoint, time_since_start_s); | |
| } | |
| let mut ack_count = 0; | |
| let acks = unsafe { rio::reliable_endpoint_get_acks(rio_endpoint, &mut ack_count) }; | |
| // TODO(acmcarther): Perform rebroadcast | |
| unsafe { rio::reliable_endpoint_clear_acks(rio_endpoint) }; | |
| } | |
| } | |
| impl NetcodeClient for Client { | |
| fn is_connected(&self) -> bool { | |
| self.rio_endpoint.is_some() | |
| } | |
| fn send_packet(&self, mut payload_bytes: Vec<u8>) { | |
| if self.rio_endpoint.is_none() { | |
| // Client must be remade | |
| warn!("Tried to send a packet with a disconnected client"); | |
| return; | |
| } | |
| unsafe { | |
| rio::reliable_endpoint_send_packet( | |
| self.rio_endpoint.unwrap(), | |
| payload_bytes.as_mut_ptr(), | |
| payload_bytes.len() as i32, | |
| ); | |
| } | |
| } | |
| fn update(&mut self) { | |
| if self.rio_endpoint.is_none() { | |
| // Client must be remade | |
| warn!("Tried to run an update with a disconnected client"); | |
| return; | |
| } | |
| let now = Instant::now(); | |
| let duration = now.duration_since(self.start_time); | |
| let time_since_start_s = | |
| (duration.as_secs() as f64) + ((duration.subsec_nanos()) as f64 / 1_000_000_000.0); | |
| unsafe { | |
| nio::netcode_client_update(self.nio_client, time_since_start_s); | |
| } | |
| self.tend_connection(time_since_start_s); | |
| } | |
| fn retrieve_packets(&mut self) -> Vec<Vec<u8>> { | |
| let mut swap_vec = Vec::new(); | |
| std::mem::swap(&mut self.unprocessed_inbound_packets, &mut swap_vec); | |
| swap_vec | |
| } | |
| } | |
| impl Drop for Client { | |
| fn drop(&mut self) { | |
| if let Some(endpoint) = self.rio_endpoint.take() { | |
| unsafe { | |
| rio::reliable_endpoint_destroy(endpoint); | |
| } | |
| } | |
| unsafe { | |
| nio::netcode_client_destroy(self.nio_client); | |
| } | |
| } | |
| } | |
| mod util { | |
| use Client; | |
| use libc; | |
| use netcode_api::ClientId; | |
| use netcode_api::ClientSlot; | |
| use nio; | |
| use rio; | |
| use std; | |
| use std::ffi::CString; | |
| use std::os::raw::c_void; | |
| use std::ptr; | |
| pub unsafe fn create_rio_endpoint( | |
| client: *mut Client, | |
| init_time: f64, | |
| ) -> *mut rio::reliable_endpoint_t { | |
| let mut rio_client_config: rio::reliable_config_t = std::mem::uninitialized(); | |
| rio::reliable_default_config(&mut rio_client_config); | |
| rio_client_config.fragment_above = (*client).config.max_unfragmented_packet_size as i32; | |
| let rio_client_name = CString::new(format!("singular-client")).unwrap(); | |
| ptr::copy( | |
| rio_client_name.as_ptr(), | |
| rio_client_config.name.as_mut_ptr(), | |
| rio_client_name.as_bytes_with_nul().len(), | |
| ); | |
| rio_client_config.context = client as *mut c_void; | |
| rio_client_config.index = 0; | |
| rio_client_config.transmit_packet_function = Some(rio_transmit_packet_function); | |
| rio_client_config.process_packet_function = Some(rio_process_packet_function); | |
| rio::reliable_endpoint_create(&mut rio_client_config, init_time) | |
| } | |
| unsafe extern "C" fn rio_transmit_packet_function( | |
| context: *mut ::std::os::raw::c_void, | |
| slot: ::std::os::raw::c_int, | |
| sequence: u16, | |
| packet_data: *mut u8, | |
| packet_byte_count: ::std::os::raw::c_int, | |
| ) { | |
| if slot != 0 { | |
| // N.B: Currently assume that client never talks to any slot except the first one. | |
| error!("Client tried to emit packet to non-zero slot"); | |
| return; | |
| } | |
| let client = context as *mut ::Client; | |
| assert!((*client).nio_client != std::ptr::null_mut()); | |
| nio::netcode_client_send_packet((*client).nio_client, packet_data, packet_byte_count); | |
| } | |
| unsafe extern "C" fn rio_process_packet_function( | |
| context: *mut ::std::os::raw::c_void, | |
| slot: ::std::os::raw::c_int, | |
| sequence: u16, | |
| packet_data: *mut u8, | |
| packet_byte_count: ::std::os::raw::c_int, | |
| ) -> ::std::os::raw::c_int { | |
| if slot != 0 { | |
| // N.B: Currently assume that client never talks to any slot except the first one. | |
| error!("Client received packet on non-zero slot, dropping"); | |
| return 1; | |
| } | |
| let slot = slot as usize; | |
| let client = context as *mut ::Client; | |
| assert!((*client).nio_client != std::ptr::null_mut()); | |
| let mut payload = Vec::with_capacity(packet_byte_count as usize); | |
| libc::memcpy( | |
| payload.as_mut_ptr() as *mut libc::c_void, | |
| packet_data as *const libc::c_void, | |
| packet_byte_count as usize, | |
| ); | |
| payload.set_len(packet_byte_count as usize); | |
| (*client).unprocessed_inbound_packets.push(payload); | |
| 1 | |
| } | |
| } |