add networking, 3d improvements, reorganize src structure
This commit is contained in:
parent
39b604b333
commit
415d424057
122 changed files with 5358 additions and 721 deletions
2
server/.cargo/config.toml
Normal file
2
server/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
1
server/.gitignore
vendored
Normal file
1
server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
target/
|
||||
1090
server/Cargo.lock
generated
Normal file
1090
server/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
34
server/Cargo.toml
Normal file
34
server/Cargo.toml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[package]
|
||||
name = "pxl8-server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72"
|
||||
|
||||
[dependencies]
|
||||
bevy_ecs = { version = "0.18", default-features = false }
|
||||
libc = { version = "0.2", default-features = false }
|
||||
libm = { version = "0.2", default-features = false }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61", default-features = false, features = [
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Performance",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Networking_WinSock",
|
||||
] }
|
||||
|
||||
[[bin]]
|
||||
name = "pxl8-server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
std = ["bevy_ecs/std"]
|
||||
23
server/build.rs
Normal file
23
server/build.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let client_src = PathBuf::from(&manifest_dir).join("../client/src");
|
||||
|
||||
println!("cargo:rerun-if-changed=../client/src/net/pxl8_protocol.h");
|
||||
println!("cargo:rerun-if-changed=../client/src/core/pxl8_types.h");
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(client_src.join("net/pxl8_protocol.h").to_str().unwrap())
|
||||
.clang_arg(format!("-I{}", client_src.join("core").display()))
|
||||
.use_core()
|
||||
.rustified_enum(".*")
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("protocol.rs"))
|
||||
.expect("Couldn't write bindings");
|
||||
}
|
||||
2
server/rust-toolchain.toml
Normal file
2
server/rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
36
server/src/allocator.rs
Normal file
36
server/src/allocator.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use core::alloc::{GlobalAlloc, Layout};
|
||||
|
||||
pub struct Allocator;
|
||||
|
||||
#[cfg(unix)]
|
||||
unsafe impl GlobalAlloc for Allocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
unsafe { libc::memalign(layout.align(), layout.size()) as *mut u8 }
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
unsafe { libc::free(ptr as *mut libc::c_void) }
|
||||
}
|
||||
|
||||
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
|
||||
unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe impl GlobalAlloc for Allocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
unsafe { HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 }
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
unsafe { HeapFree(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void) };
|
||||
}
|
||||
|
||||
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
unsafe { HeapReAlloc(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void, new_size) as *mut u8 }
|
||||
}
|
||||
}
|
||||
45
server/src/components.rs
Normal file
45
server/src/components.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Position {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Rotation {
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Velocity {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct Health {
|
||||
pub current: f32,
|
||||
pub max: f32,
|
||||
}
|
||||
|
||||
impl Health {
|
||||
pub fn new(max: f32) -> Self {
|
||||
Self { current: max, max }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Health {
|
||||
fn default() -> Self {
|
||||
Self::new(100.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct TypeId(pub u16);
|
||||
18
server/src/lib.rs
Normal file
18
server/src/lib.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod components;
|
||||
mod simulation;
|
||||
pub mod transport;
|
||||
|
||||
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
pub mod protocol {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
|
||||
}
|
||||
|
||||
pub use bevy_ecs::prelude::*;
|
||||
pub use components::*;
|
||||
pub use protocol::*;
|
||||
pub use simulation::*;
|
||||
pub use transport::*;
|
||||
142
server/src/main.rs
Normal file
142
server/src/main.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod allocator;
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
use pxl8_server::*;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
const TICK_RATE: u64 = 30;
|
||||
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_time_ns() -> u64 {
|
||||
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
|
||||
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_time_ns() -> u64 {
|
||||
use windows_sys::Win32::System::Performance::*;
|
||||
static mut FREQ: i64 = 0;
|
||||
unsafe {
|
||||
if FREQ == 0 {
|
||||
QueryPerformanceFrequency(&mut FREQ);
|
||||
}
|
||||
let mut count: i64 = 0;
|
||||
QueryPerformanceCounter(&mut count);
|
||||
((count as u128 * 1_000_000_000) / FREQ as u128) as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
let ts = libc::timespec {
|
||||
tv_sec: (ms / 1000) as i64,
|
||||
tv_nsec: ((ms % 1000) * 1_000_000) as i64,
|
||||
};
|
||||
unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
use windows_sys::Win32::System::Threading::Sleep;
|
||||
unsafe { Sleep(ms as u32) };
|
||||
}
|
||||
|
||||
fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
|
||||
let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
||||
let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
|
||||
let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
|
||||
(x, y, z, yaw, pitch)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
|
||||
Some(t) => t,
|
||||
None => return 1,
|
||||
};
|
||||
|
||||
let mut sim = Simulation::new();
|
||||
let mut player_id: u64 = 0;
|
||||
let mut last_client_tick: u64 = 0;
|
||||
|
||||
let mut sequence: u32 = 0;
|
||||
let mut last_tick = get_time_ns();
|
||||
let mut entities_buf = [protocol::pxl8_entity_state {
|
||||
entity_id: 0,
|
||||
userdata: [0u8; 56],
|
||||
}; 64];
|
||||
let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
|
||||
|
||||
loop {
|
||||
let now = get_time_ns();
|
||||
let elapsed = now.saturating_sub(last_tick);
|
||||
|
||||
if elapsed >= TICK_NS {
|
||||
last_tick = now;
|
||||
let dt = (elapsed as f32) / 1_000_000_000.0;
|
||||
|
||||
let mut latest_input: Option<protocol::pxl8_input_msg> = None;
|
||||
while let Some(msg_type) = transport.recv() {
|
||||
match msg_type {
|
||||
x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
|
||||
latest_input = Some(transport.get_input());
|
||||
}
|
||||
x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
|
||||
let cmd = transport.get_command();
|
||||
if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
|
||||
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
|
||||
let player = sim.spawn_player(x, y, z);
|
||||
player_id = player.to_bits();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(input) = latest_input {
|
||||
last_client_tick = input.tick;
|
||||
inputs_buf[0] = input;
|
||||
sim.step(&inputs_buf[..1], dt);
|
||||
} else {
|
||||
sim.step(&[], dt);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
sim.generate_snapshot(player_id, |state| {
|
||||
if count < entities_buf.len() {
|
||||
entities_buf[count] = *state;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
let header = protocol::pxl8_snapshot_header {
|
||||
entity_count: count as u16,
|
||||
event_count: 0,
|
||||
player_id,
|
||||
tick: last_client_tick,
|
||||
time: sim.time,
|
||||
};
|
||||
|
||||
transport.send_snapshot(&header, &entities_buf[..count], sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
133
server/src/simulation.rs
Normal file
133
server/src/simulation.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
use libm::{cosf, sinf};
|
||||
|
||||
use crate::components::*;
|
||||
use crate::protocol::*;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct SimTime {
|
||||
pub dt: f32,
|
||||
pub time: f32,
|
||||
}
|
||||
|
||||
pub struct Simulation {
|
||||
pub player: Option<Entity>,
|
||||
pub tick: u64,
|
||||
pub time: f32,
|
||||
pub world: World,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
world.insert_resource(SimTime::default());
|
||||
Self {
|
||||
player: None,
|
||||
tick: 0,
|
||||
time: 0.0,
|
||||
world,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
|
||||
self.tick += 1;
|
||||
self.time += dt;
|
||||
|
||||
if let Some(mut sim_time) = self.world.get_resource_mut::<SimTime>() {
|
||||
sim_time.dt = dt;
|
||||
sim_time.time = self.time;
|
||||
}
|
||||
|
||||
for input in inputs {
|
||||
self.apply_input(input, dt);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_input(&mut self, input: &pxl8_input_msg, dt: f32) {
|
||||
let Some(player) = self.player else { return };
|
||||
let speed = 200.0;
|
||||
|
||||
let yaw = input.yaw;
|
||||
let sin_yaw = sinf(yaw);
|
||||
let cos_yaw = cosf(yaw);
|
||||
|
||||
let move_x = input.move_x;
|
||||
let move_y = input.move_y;
|
||||
let len_sq = move_x * move_x + move_y * move_y;
|
||||
|
||||
if len_sq > 0.0 {
|
||||
let len = libm::sqrtf(len_sq);
|
||||
let norm_x = move_x / len;
|
||||
let norm_y = move_y / len;
|
||||
|
||||
let forward_x = -sin_yaw * norm_y * speed * dt;
|
||||
let forward_z = -cos_yaw * norm_y * speed * dt;
|
||||
let strafe_x = cos_yaw * norm_x * speed * dt;
|
||||
let strafe_z = -sin_yaw * norm_x * speed * dt;
|
||||
|
||||
if let Some(mut pos) = self.world.get_mut::<Position>(player) {
|
||||
pos.x += forward_x + strafe_x;
|
||||
pos.z += forward_z + strafe_z;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut rot) = self.world.get_mut::<Rotation>(player) {
|
||||
rot.yaw = yaw;
|
||||
rot.pitch = (rot.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_entity(&mut self) -> Entity {
|
||||
self.world.spawn((
|
||||
Position::default(),
|
||||
Rotation::default(),
|
||||
Velocity::default(),
|
||||
)).id()
|
||||
}
|
||||
|
||||
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> Entity {
|
||||
let entity = self.world.spawn((
|
||||
Player,
|
||||
Position { x, y, z },
|
||||
Rotation::default(),
|
||||
Velocity::default(),
|
||||
Health::default(),
|
||||
)).id();
|
||||
self.player = Some(entity);
|
||||
entity
|
||||
}
|
||||
|
||||
pub fn generate_snapshot<F>(&mut self, player_id: u64, mut writer: F)
|
||||
where
|
||||
F: FnMut(&pxl8_entity_state),
|
||||
{
|
||||
let mut query = self.world.query::<(Entity, &Position, &Rotation)>();
|
||||
for (entity, pos, rot) in query.iter(&self.world) {
|
||||
let mut state = pxl8_entity_state {
|
||||
entity_id: entity.to_bits(),
|
||||
userdata: [0u8; 56],
|
||||
};
|
||||
|
||||
let bytes = &mut state.userdata;
|
||||
bytes[0..4].copy_from_slice(&pos.x.to_be_bytes());
|
||||
bytes[4..8].copy_from_slice(&pos.y.to_be_bytes());
|
||||
bytes[8..12].copy_from_slice(&pos.z.to_be_bytes());
|
||||
bytes[12..16].copy_from_slice(&rot.yaw.to_be_bytes());
|
||||
bytes[16..20].copy_from_slice(&rot.pitch.to_be_bytes());
|
||||
|
||||
writer(&state);
|
||||
}
|
||||
|
||||
let _ = player_id;
|
||||
}
|
||||
|
||||
pub fn entity_count(&self) -> usize {
|
||||
self.world.entities().len() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Simulation {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
281
server/src/transport.rs
Normal file
281
server/src/transport.rs
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
use crate::protocol::*;
|
||||
|
||||
pub const DEFAULT_PORT: u16 = 7777;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod sys {
|
||||
use libc::{c_int, c_void, sockaddr, sockaddr_in, socklen_t};
|
||||
|
||||
pub type RawSocket = c_int;
|
||||
pub const INVALID_SOCKET: RawSocket = -1;
|
||||
|
||||
pub fn socket() -> RawSocket {
|
||||
unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }
|
||||
}
|
||||
|
||||
pub fn bind(sock: RawSocket, port: u16) -> c_int {
|
||||
let addr = sockaddr_in {
|
||||
sin_family: libc::AF_INET as u16,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: libc::in_addr { s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() },
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
unsafe { libc::bind(sock, &addr as *const _ as *const sockaddr, core::mem::size_of::<sockaddr_in>() as socklen_t) }
|
||||
}
|
||||
|
||||
pub fn set_nonblocking(sock: RawSocket) -> c_int {
|
||||
unsafe {
|
||||
let flags = libc::fcntl(sock, libc::F_GETFL, 0);
|
||||
libc::fcntl(sock, libc::F_SETFL, flags | libc::O_NONBLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut sockaddr_in) -> isize {
|
||||
let mut addr_len = core::mem::size_of::<sockaddr_in>() as socklen_t;
|
||||
unsafe {
|
||||
libc::recvfrom(
|
||||
sock,
|
||||
buf.as_mut_ptr() as *mut c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
addr as *mut _ as *mut sockaddr,
|
||||
&mut addr_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &sockaddr_in) -> isize {
|
||||
unsafe {
|
||||
libc::sendto(
|
||||
sock,
|
||||
buf.as_ptr() as *const c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
addr as *const _ as *const sockaddr,
|
||||
core::mem::size_of::<sockaddr_in>() as socklen_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(sock: RawSocket) {
|
||||
unsafe { libc::close(sock) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use windows_sys::Win32::Networking::WinSock::*;
|
||||
|
||||
pub type RawSocket = SOCKET;
|
||||
pub const INVALID_SOCKET_VAL: RawSocket = INVALID_SOCKET;
|
||||
|
||||
pub fn socket() -> RawSocket {
|
||||
unsafe { socket(AF_INET as i32, SOCK_DGRAM as i32, 0) }
|
||||
}
|
||||
|
||||
pub fn bind(sock: RawSocket, port: u16) -> i32 {
|
||||
let addr = SOCKADDR_IN {
|
||||
sin_family: AF_INET,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() } },
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
unsafe { bind(sock, &addr as *const _ as *const SOCKADDR, core::mem::size_of::<SOCKADDR_IN>() as i32) }
|
||||
}
|
||||
|
||||
pub fn set_nonblocking(sock: RawSocket) -> i32 {
|
||||
let mut nonblocking: u32 = 1;
|
||||
unsafe { ioctlsocket(sock, FIONBIO as i32, &mut nonblocking) }
|
||||
}
|
||||
|
||||
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut SOCKADDR_IN) -> i32 {
|
||||
let mut addr_len = core::mem::size_of::<SOCKADDR_IN>() as i32;
|
||||
unsafe {
|
||||
recvfrom(
|
||||
sock,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as i32,
|
||||
0,
|
||||
addr as *mut _ as *mut SOCKADDR,
|
||||
&mut addr_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &SOCKADDR_IN) -> i32 {
|
||||
unsafe {
|
||||
sendto(
|
||||
sock,
|
||||
buf.as_ptr(),
|
||||
buf.len() as i32,
|
||||
0,
|
||||
addr as *const _ as *const SOCKADDR,
|
||||
core::mem::size_of::<SOCKADDR_IN>() as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(sock: RawSocket) {
|
||||
unsafe { closesocket(sock) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
type SockAddr = libc::sockaddr_in;
|
||||
|
||||
#[cfg(windows)]
|
||||
type SockAddr = windows_sys::Win32::Networking::WinSock::SOCKADDR_IN;
|
||||
|
||||
pub struct Transport {
|
||||
client_addr: SockAddr,
|
||||
has_client: bool,
|
||||
recv_buf: [u8; 4096],
|
||||
send_buf: [u8; 4096],
|
||||
socket: sys::RawSocket,
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
pub fn bind(port: u16) -> Option<Self> {
|
||||
let sock = sys::socket();
|
||||
if sock == sys::INVALID_SOCKET {
|
||||
return None;
|
||||
}
|
||||
|
||||
if sys::bind(sock, port) < 0 {
|
||||
sys::close(sock);
|
||||
return None;
|
||||
}
|
||||
|
||||
if sys::set_nonblocking(sock) < 0 {
|
||||
sys::close(sock);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
client_addr: unsafe { core::mem::zeroed() },
|
||||
has_client: false,
|
||||
recv_buf: [0u8; 4096],
|
||||
send_buf: [0u8; 4096],
|
||||
socket: sock,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn recv(&mut self) -> Option<u8> {
|
||||
let mut addr: SockAddr = unsafe { core::mem::zeroed() };
|
||||
let len = sys::recvfrom(self.socket, &mut self.recv_buf, &mut addr);
|
||||
|
||||
if len <= 0 || (len as usize) < size_of::<pxl8_msg_header>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.client_addr = addr;
|
||||
self.has_client = true;
|
||||
|
||||
let header = self.deserialize_header();
|
||||
Some(header.type_)
|
||||
}
|
||||
|
||||
pub fn get_input(&self) -> pxl8_input_msg {
|
||||
self.deserialize_input()
|
||||
}
|
||||
|
||||
pub fn get_command(&self) -> pxl8_command_msg {
|
||||
self.deserialize_command()
|
||||
}
|
||||
|
||||
pub fn send_snapshot(
|
||||
&mut self,
|
||||
header: &pxl8_snapshot_header,
|
||||
entities: &[pxl8_entity_state],
|
||||
sequence: u32,
|
||||
) {
|
||||
if !self.has_client {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let msg_header = pxl8_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: pxl8_msg_type::PXL8_MSG_SNAPSHOT as u8,
|
||||
version: PXL8_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
offset += self.serialize_header(&msg_header, offset);
|
||||
offset += self.serialize_snapshot_header(header, offset);
|
||||
|
||||
for entity in entities {
|
||||
offset += self.serialize_entity_state(entity, offset);
|
||||
}
|
||||
|
||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||
}
|
||||
|
||||
fn serialize_header(&mut self, h: &pxl8_msg_header, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..4].copy_from_slice(&h.sequence.to_be_bytes());
|
||||
buf[4..6].copy_from_slice(&h.size.to_be_bytes());
|
||||
buf[6] = h.type_;
|
||||
buf[7] = h.version;
|
||||
8
|
||||
}
|
||||
|
||||
fn serialize_snapshot_header(&mut self, h: &pxl8_snapshot_header, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..2].copy_from_slice(&h.entity_count.to_be_bytes());
|
||||
buf[2..4].copy_from_slice(&h.event_count.to_be_bytes());
|
||||
buf[4..12].copy_from_slice(&h.player_id.to_be_bytes());
|
||||
buf[12..20].copy_from_slice(&h.tick.to_be_bytes());
|
||||
buf[20..24].copy_from_slice(&h.time.to_be_bytes());
|
||||
24
|
||||
}
|
||||
|
||||
fn serialize_entity_state(&mut self, e: &pxl8_entity_state, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..8].copy_from_slice(&e.entity_id.to_be_bytes());
|
||||
buf[8..64].copy_from_slice(&e.userdata);
|
||||
64
|
||||
}
|
||||
|
||||
fn deserialize_header(&self) -> pxl8_msg_header {
|
||||
let buf = &self.recv_buf;
|
||||
pxl8_msg_header {
|
||||
sequence: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
|
||||
size: u16::from_be_bytes([buf[4], buf[5]]),
|
||||
type_: buf[6],
|
||||
version: buf[7],
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_input(&self) -> pxl8_input_msg {
|
||||
let buf = &self.recv_buf[8..];
|
||||
pxl8_input_msg {
|
||||
buttons: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
|
||||
look_dx: f32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]),
|
||||
look_dy: f32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]),
|
||||
move_x: f32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]),
|
||||
move_y: f32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]),
|
||||
yaw: f32::from_be_bytes([buf[20], buf[21], buf[22], buf[23]]),
|
||||
tick: u64::from_be_bytes([buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31]]),
|
||||
timestamp: u64::from_be_bytes([buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39]]),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_command(&self) -> pxl8_command_msg {
|
||||
let buf = &self.recv_buf[8..];
|
||||
let mut cmd = pxl8_command_msg {
|
||||
cmd_type: u16::from_be_bytes([buf[0], buf[1]]),
|
||||
payload: [0u8; 64],
|
||||
payload_size: u16::from_be_bytes([buf[66], buf[67]]),
|
||||
tick: u64::from_be_bytes([buf[68], buf[69], buf[70], buf[71], buf[72], buf[73], buf[74], buf[75]]),
|
||||
};
|
||||
cmd.payload.copy_from_slice(&buf[2..66]);
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Transport {
|
||||
fn drop(&mut self) {
|
||||
sys::close(self.socket);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue