326 lines
13 KiB
Rust
326 lines
13 KiB
Rust
|
|
#![no_std]
|
||
|
|
#![no_main]
|
||
|
|
|
||
|
|
extern crate alloc;
|
||
|
|
|
||
|
|
use pxl8d::*;
|
||
|
|
use pxl8d::chunk::ChunkId;
|
||
|
|
use pxl8d::chunk::stream::ClientChunkState;
|
||
|
|
|
||
|
|
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 {
|
||
|
|
log::init();
|
||
|
|
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
|
||
|
|
Some(t) => {
|
||
|
|
pxl8_info!("[SERVER] Bound to port 7777");
|
||
|
|
t
|
||
|
|
}
|
||
|
|
None => {
|
||
|
|
pxl8_error!("[SERVER] Failed to bind to port 7777");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut sim = Simulation::new();
|
||
|
|
let mut player_id: Option<u64> = None;
|
||
|
|
let mut last_client_tick: u64 = 0;
|
||
|
|
let mut client_chunks = ClientChunkState::new();
|
||
|
|
|
||
|
|
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() };
|
||
|
|
|
||
|
|
pxl8_debug!("[SERVER] Entering main loop");
|
||
|
|
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);
|
||
|
|
player_id = Some(sim.spawn_player(x, y, z) as u64);
|
||
|
|
|
||
|
|
pxl8_debug!("[SERVER] Spawn command received, generating BSP...");
|
||
|
|
sim.world.generate_bsp(1, None);
|
||
|
|
sim.world.set_active(ChunkId::Bsp(1));
|
||
|
|
client_chunks.request(ChunkId::Bsp(1));
|
||
|
|
pxl8_debug!("[SERVER] Sending CHUNK_ENTER for BSP id=1");
|
||
|
|
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
|
||
|
|
sequence = sequence.wrapping_add(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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.unwrap_or(u64::MAX), |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: player_id.unwrap_or(0),
|
||
|
|
tick: last_client_tick,
|
||
|
|
time: sim.time,
|
||
|
|
};
|
||
|
|
|
||
|
|
transport.send_snapshot(&header, &entities_buf[..count], sequence);
|
||
|
|
sequence = sequence.wrapping_add(1);
|
||
|
|
|
||
|
|
if let Some(pid) = player_id {
|
||
|
|
if let Some(player) = sim.get_player_position(pid) {
|
||
|
|
let pos = Vec3 { x: player.0, y: player.1, z: player.2 };
|
||
|
|
|
||
|
|
if sim.world.active().is_some() {
|
||
|
|
while let Some(chunk_id) = client_chunks.next_pending() {
|
||
|
|
match chunk_id {
|
||
|
|
ChunkId::Bsp(id) => {
|
||
|
|
pxl8_debug!("[SERVER] Processing pending BSP chunk");
|
||
|
|
if let Some(chunk) = sim.world.get(&chunk_id) {
|
||
|
|
if let Some(bsp) = chunk.as_bsp() {
|
||
|
|
let msgs = bsp_to_messages(bsp, id, chunk.version());
|
||
|
|
pxl8_debug!("[SERVER] BSP serialized, queueing messages");
|
||
|
|
client_chunks.queue_messages(msgs);
|
||
|
|
client_chunks.mark_sent(chunk_id, chunk.version());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ChunkId::Vxl(cx, cy, cz) => {
|
||
|
|
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
||
|
|
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
||
|
|
client_chunks.queue_messages(msgs);
|
||
|
|
client_chunks.mark_sent(chunk_id, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _ in 0..4 {
|
||
|
|
if let Some(msg) = client_chunks.next_message() {
|
||
|
|
transport.send_chunk(&msg, sequence);
|
||
|
|
sequence = sequence.wrapping_add(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
client_chunks.request_vxl_radius(pos, 2, &sim.voxels);
|
||
|
|
|
||
|
|
for _ in 0..2 {
|
||
|
|
if let Some(chunk_id) = client_chunks.next_pending() {
|
||
|
|
if let ChunkId::Vxl(cx, cy, cz) = chunk_id {
|
||
|
|
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
||
|
|
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
||
|
|
for msg in &msgs {
|
||
|
|
transport.send_chunk(msg, sequence);
|
||
|
|
sequence = sequence.wrapping_add(1);
|
||
|
|
}
|
||
|
|
client_chunks.mark_sent(chunk_id, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sleep_ms(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
|
||
|
|
use alloc::vec::Vec;
|
||
|
|
|
||
|
|
pxl8_debug!("[SERVER] bsp_to_messages: serializing BSP");
|
||
|
|
|
||
|
|
let mut data = Vec::new();
|
||
|
|
|
||
|
|
let num_verts = bsp.vertices.len();
|
||
|
|
let num_edges = bsp.edges.len();
|
||
|
|
let num_faces = bsp.faces.len();
|
||
|
|
let num_planes = bsp.planes.len();
|
||
|
|
let num_nodes = bsp.nodes.len();
|
||
|
|
let num_leafs = bsp.leafs.len();
|
||
|
|
let num_surfedges = bsp.surfedges.len();
|
||
|
|
|
||
|
|
data.extend_from_slice(&(num_verts as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(num_edges as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(num_faces as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(num_planes as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(num_nodes as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(num_leafs as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(num_surfedges as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(bsp.marksurfaces.len() as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(bsp.cell_portals.len() as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(bsp.visdata.len() as u32).to_be_bytes());
|
||
|
|
data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes());
|
||
|
|
|
||
|
|
for v in &bsp.vertices {
|
||
|
|
data.extend_from_slice(&v.position.x.to_be_bytes());
|
||
|
|
data.extend_from_slice(&v.position.y.to_be_bytes());
|
||
|
|
data.extend_from_slice(&v.position.z.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for e in &bsp.edges {
|
||
|
|
data.extend_from_slice(&e.vertex[0].to_be_bytes());
|
||
|
|
data.extend_from_slice(&e.vertex[1].to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for se in &bsp.surfedges {
|
||
|
|
data.extend_from_slice(&se.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for p in &bsp.planes {
|
||
|
|
data.extend_from_slice(&p.normal.x.to_be_bytes());
|
||
|
|
data.extend_from_slice(&p.normal.y.to_be_bytes());
|
||
|
|
data.extend_from_slice(&p.normal.z.to_be_bytes());
|
||
|
|
data.extend_from_slice(&p.dist.to_be_bytes());
|
||
|
|
data.extend_from_slice(&p.plane_type.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for f in &bsp.faces {
|
||
|
|
data.extend_from_slice(&f.first_edge.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.lightmap_offset.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.num_edges.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.plane_id.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.side.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.styles);
|
||
|
|
data.extend_from_slice(&f.material_id.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.aabb_min.x.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.aabb_min.y.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.aabb_min.z.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.aabb_max.x.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.aabb_max.y.to_be_bytes());
|
||
|
|
data.extend_from_slice(&f.aabb_max.z.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for n in &bsp.nodes {
|
||
|
|
data.extend_from_slice(&n.children[0].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.children[1].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.first_face.to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.maxs[0].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.maxs[1].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.maxs[2].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.mins[0].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.mins[1].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.mins[2].to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.num_faces.to_be_bytes());
|
||
|
|
data.extend_from_slice(&n.plane_id.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for l in &bsp.leafs {
|
||
|
|
data.extend_from_slice(&l.ambient_level);
|
||
|
|
data.extend_from_slice(&l.contents.to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.first_marksurface.to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.maxs[0].to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.maxs[1].to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.maxs[2].to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.mins[0].to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.mins[1].to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.mins[2].to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.num_marksurfaces.to_be_bytes());
|
||
|
|
data.extend_from_slice(&l.visofs.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for ms in &bsp.marksurfaces {
|
||
|
|
data.extend_from_slice(&ms.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
for cp in &bsp.cell_portals {
|
||
|
|
data.push(cp.num_portals);
|
||
|
|
data.push(0);
|
||
|
|
data.push(0);
|
||
|
|
data.push(0);
|
||
|
|
for i in 0..4 {
|
||
|
|
data.extend_from_slice(&cp.portals[i].x0.to_be_bytes());
|
||
|
|
data.extend_from_slice(&cp.portals[i].z0.to_be_bytes());
|
||
|
|
data.extend_from_slice(&cp.portals[i].x1.to_be_bytes());
|
||
|
|
data.extend_from_slice(&cp.portals[i].z1.to_be_bytes());
|
||
|
|
data.extend_from_slice(&cp.portals[i].target_leaf.to_be_bytes());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
data.extend_from_slice(&bsp.visdata);
|
||
|
|
|
||
|
|
for vl in &bsp.vertex_lights {
|
||
|
|
data.extend_from_slice(&vl.to_be_bytes());
|
||
|
|
}
|
||
|
|
|
||
|
|
transport::ChunkMessage::from_bsp(data, id, version)
|
||
|
|
}
|