#![no_std] #![no_main] extern crate alloc; use pxl8d::*; use pxl8d::chunk::ChunkId; use pxl8d::chunk::stream::ClientChunkState; use pxl8d::math::Vec3; 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 = None; let mut last_client_tick: u64 = 0; let mut client_chunks = ClientChunkState::new(); let mut client_stream_radius: i32 = 3; let mut sequence: u32 = 0; let mut last_tick = get_time_ns(); let mut entities_buf = [pxl8d::pxl8_entity_state { entity_id: 0, userdata: [0u8; 56], }; 64]; let mut inputs_buf: [pxl8d::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 = None; while let Some(msg_type) = transport.recv() { match msg_type { x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => { latest_input = Some(transport.get_input()); } x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => { let cmd = transport.get_command(); if cmd.cmd_type == pxl8d::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); pxl8_debug!("[SERVER] Preloading voxel chunks around spawn and door"); let spawn_pos = Vec3 { x, y, z }; let door_y = sim.voxels.find_surface_y(942.0, 416.0); let door_pos = Vec3 { x: 942.0, y: door_y, z: 416.0 }; pxl8_debug!("[SERVER] Door placed at surface y={}", door_y); sim.voxels.load_chunks_around(spawn_pos, client_stream_radius); sim.voxels.load_chunks_around(door_pos, client_stream_radius); client_chunks.request_vxl_radius(spawn_pos, client_stream_radius, &sim.voxels); client_chunks.request_vxl_radius(door_pos, client_stream_radius, &sim.voxels); } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_EXIT_CHUNK as u16 { sim.world.clear_active(); client_chunks.clear_pending(); client_chunks.clear_known_vxl(); client_chunks.clear_known_bsp(); transport.send_chunk_exit(sequence); sequence = sequence.wrapping_add(1); let exit_x = f32::from_be_bytes([cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]]); let exit_y = f32::from_be_bytes([cmd.payload[4], cmd.payload[5], cmd.payload[6], cmd.payload[7]]); let exit_z = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]); let exit_pos = Vec3 { x: exit_x, y: exit_y, z: exit_z }; sim.teleport_player(exit_x, exit_y, exit_z); sim.voxels.load_chunks_around(exit_pos, client_stream_radius); client_chunks.request_vxl_radius(exit_pos, client_stream_radius, &sim.voxels); } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_CHUNK as u16 { let chunk_id = u32::from_be_bytes([ cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3] ]); pxl8_debug!("[SERVER] Enter chunk command - entering BSP {}", chunk_id); if sim.world.contains(&ChunkId::Bsp(chunk_id)) { sim.world.set_active(ChunkId::Bsp(chunk_id)); client_chunks.request(ChunkId::Bsp(chunk_id)); transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence); sequence = sequence.wrapping_add(1); } } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SET_CHUNK_SETTINGS as u16 { let render_dist = i32::from_be_bytes([ cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3] ]); client_stream_radius = render_dist.clamp(1, 8); } } _ => {} } } 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 = pxl8d::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 }; sim.voxels.load_chunks_around(pos, client_stream_radius); client_chunks.request_vxl_radius(pos, client_stream_radius, &sim.voxels); while let Some(chunk_id) = client_chunks.next_pending() { match chunk_id { ChunkId::Bsp(id) => { 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()); 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..8 { if let Some(msg) = client_chunks.next_message() { transport.send_chunk(&msg, sequence); sequence = sequence.wrapping_add(1); } } } } } sleep_ms(1); } } fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec { 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.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) }