pxl8/pxl8d/src/main.rs

350 lines
15 KiB
Rust
Raw Normal View History

2026-01-25 09:26:30 -06:00
#![no_std]
#![no_main]
extern crate alloc;
use pxl8d::*;
use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState;
2026-01-31 09:31:17 -06:00
use pxl8d::math::Vec3;
2026-01-25 09:26:30 -06:00
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();
2026-01-31 09:31:17 -06:00
let mut client_stream_radius: i32 = 3;
2026-01-25 09:26:30 -06:00
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
2026-01-31 09:31:17 -06:00
let mut entities_buf = [pxl8d::pxl8_entity_state {
2026-01-25 09:26:30 -06:00
entity_id: 0,
userdata: [0u8; 56],
}; 64];
2026-01-31 09:31:17 -06:00
let mut inputs_buf: [pxl8d::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
2026-01-25 09:26:30 -06:00
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;
2026-01-31 09:31:17 -06:00
let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
2026-01-25 09:26:30 -06:00
while let Some(msg_type) = transport.recv() {
match msg_type {
2026-01-31 09:31:17 -06:00
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
2026-01-25 09:26:30 -06:00
latest_input = Some(transport.get_input());
}
2026-01-31 09:31:17 -06:00
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
2026-01-25 09:26:30 -06:00
let cmd = transport.get_command();
2026-01-31 09:31:17 -06:00
if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
2026-01-25 09:26:30 -06:00
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);
2026-01-31 09:31:17 -06:00
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);
2026-01-25 09:26:30 -06:00
}
}
_ => {}
}
}
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;
}
});
2026-01-31 09:31:17 -06:00
let header = pxl8d::pxl8_snapshot_header {
2026-01-25 09:26:30 -06:00
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 };
2026-01-31 09:31:17 -06:00
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());
2026-01-25 09:26:30 -06:00
client_chunks.queue_messages(msgs);
2026-01-31 09:31:17 -06:00
client_chunks.mark_sent(chunk_id, chunk.version());
2026-01-25 09:26:30 -06:00
}
}
}
2026-01-31 09:31:17 -06:00
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);
2026-01-25 09:26:30 -06:00
}
}
}
}
2026-01-31 09:31:17 -06:00
for _ in 0..8 {
if let Some(msg) = client_chunks.next_message() {
transport.send_chunk(&msg, sequence);
sequence = sequence.wrapping_add(1);
}
}
2026-01-25 09:26:30 -06:00
}
}
}
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());
2026-01-31 09:31:17 -06:00
data.extend_from_slice(&p.type_.to_be_bytes());
2026-01-25 09:26:30 -06:00
}
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)
}