refactor separate framework from game code, add demo3d

This commit is contained in:
asrael 2026-04-14 01:28:38 -05:00
parent 19ae869769
commit 40f5cdcaa5
92 changed files with 2665 additions and 6547 deletions

View file

@ -0,0 +1,2 @@
[unstable]
build-std = ["core", "alloc"]

1
demo3d/server/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

263
demo3d/server/Cargo.lock generated Normal file
View file

@ -0,0 +1,263 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cc"
version = "1.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "demo3d-server"
version = "0.1.0"
dependencies = [
"bindgen",
"cc",
"libc",
"libm",
"windows-sys",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

40
demo3d/server/Cargo.toml Normal file
View file

@ -0,0 +1,40 @@
[package]
name = "demo3d-server"
version = "0.1.0"
edition = "2024"
[lib]
name = "demo3d_server"
[build-dependencies]
bindgen = "0.72"
cc = "1.2"
[dependencies]
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 = "demo3d-server"
path = "src/main.rs"
[profile.dev]
opt-level = 2
panic = "abort"
[profile.dev.package."*"]
opt-level = 3
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
strip = true

68
demo3d/server/build.rs Normal file
View file

@ -0,0 +1,68 @@
use std::env;
use std::path::PathBuf;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let root = PathBuf::from(&manifest_dir).join("../..");
let framework_src = root.join("src");
let game_client = root.join("demo3d/client");
println!("cargo:rerun-if-changed=../client/bsp/demo3d_bsp.h");
println!("cargo:rerun-if-changed=../client/protocol/demo3d_protocol.c");
println!("cargo:rerun-if-changed=../client/protocol/demo3d_protocol.h");
println!("cargo:rerun-if-changed=../client/sim/demo3d_sim.c");
println!("cargo:rerun-if-changed=../client/sim/demo3d_sim.h");
println!("cargo:rerun-if-changed=../../src/core/pxl8_log.c");
println!("cargo:rerun-if-changed=../../src/core/pxl8_log.h");
println!("cargo:rerun-if-changed=../../src/core/pxl8_types.h");
println!("cargo:rerun-if-changed=../../src/math/pxl8_math.c");
println!("cargo:rerun-if-changed=../../src/math/pxl8_math.h");
cc::Build::new()
.file(framework_src.join("core/pxl8_log.c"))
.file(framework_src.join("platform/pxl8_mem.c"))
.file(framework_src.join("math/pxl8_math.c"))
.file(framework_src.join("math/pxl8_noise.c"))
.file(game_client.join("sim/demo3d_sim.c"))
.include(framework_src.join("core"))
.include(framework_src.join("math"))
.include(framework_src.join("platform"))
.include(game_client.join("bsp"))
.include(game_client.join("net"))
.include(game_client.join("sim"))
.compile("pxl8");
let bindings = bindgen::Builder::default()
.header(framework_src.join("core/pxl8_log.h").to_str().unwrap())
.header(framework_src.join("math/pxl8_noise.h").to_str().unwrap())
.header(game_client.join("sim/demo3d_sim.h").to_str().unwrap())
.clang_arg(format!("-I{}", framework_src.join("core").display()))
.clang_arg(format!("-I{}", framework_src.join("math").display()))
.clang_arg(format!("-I{}", framework_src.join("platform").display()))
.clang_arg(format!("-I{}", game_client.join("bsp").display()))
.clang_arg(format!("-I{}", game_client.join("net").display()))
.clang_arg(format!("-I{}", game_client.join("sim").display()))
.blocklist_item("FP_NAN")
.blocklist_item("FP_INFINITE")
.blocklist_item("FP_ZERO")
.blocklist_item("FP_SUBNORMAL")
.blocklist_item("FP_NORMAL")
.blocklist_type("pxl8_vec2")
.blocklist_type("pxl8_vec3")
.blocklist_type("pxl8_vec4")
.blocklist_type("pxl8_mat4")
.blocklist_item(".*_simd.*")
.blocklist_item("PXL8_SIMD.*")
.blocklist_type("__m128.*")
.blocklist_type(".*32x4_t|.*16x8_t")
.raw_line("pub use crate::math::{pxl8_vec2, pxl8_vec3, pxl8_vec4, pxl8_mat4};")
.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("bindings.rs"))
.expect("Couldn't write bindings");
}

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -0,0 +1,58 @@
use core::alloc::{GlobalAlloc, Layout};
pub struct Allocator;
#[cfg(all(unix, not(target_os = "macos")))]
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(target_os = "macos")]
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut ptr: *mut libc::c_void = core::ptr::null_mut();
let align = layout.align().max(8);
let size = layout.size();
let result = unsafe { libc::posix_memalign(&mut ptr, align, size) };
if result != 0 {
return core::ptr::null_mut();
}
ptr 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 }
}
}

270
demo3d/server/src/bsp.rs Normal file
View file

@ -0,0 +1,270 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::math::Vec3;
use crate::bindings::*;
pub type Vertex = demo3d_bsp_vertex;
pub type Edge = demo3d_bsp_edge;
pub type Face = demo3d_bsp_face;
pub type Plane = demo3d_bsp_plane;
pub type Node = demo3d_bsp_node;
pub type Leaf = demo3d_bsp_leaf;
pub type Portal = demo3d_bsp_portal;
pub type CellPortals = demo3d_bsp_cell_portals;
impl Default for Edge {
fn default() -> Self {
Self { vertex: [0, 0] }
}
}
impl Default for Face {
fn default() -> Self {
Self {
first_edge: 0,
lightmap_offset: 0,
num_edges: 0,
plane_id: 0,
side: 0,
styles: [0; 4],
material_id: 0,
aabb_min: Vec3::ZERO,
aabb_max: Vec3::ZERO,
}
}
}
impl Default for Leaf {
fn default() -> Self {
Self {
ambient_level: [0; 4],
contents: 0,
first_marksurface: 0,
maxs: [0; 3],
mins: [0; 3],
num_marksurfaces: 0,
visofs: -1,
}
}
}
impl Default for Node {
fn default() -> Self {
Self {
children: [0, 0],
first_face: 0,
maxs: [0; 3],
mins: [0; 3],
num_faces: 0,
plane_id: 0,
}
}
}
impl Default for Plane {
fn default() -> Self {
Self {
dist: 0.0,
normal: Vec3::ZERO,
type_: 0,
}
}
}
impl Default for Vertex {
fn default() -> Self {
Self { position: Vec3::ZERO }
}
}
impl Default for Portal {
fn default() -> Self {
Self {
x0: 0.0,
z0: 0.0,
x1: 0.0,
z1: 0.0,
target_leaf: 0,
}
}
}
impl Default for CellPortals {
fn default() -> Self {
Self {
portals: [Portal::default(); 4],
num_portals: 0,
}
}
}
impl Clone for Face {
fn clone(&self) -> Self {
Self {
first_edge: self.first_edge,
lightmap_offset: self.lightmap_offset,
num_edges: self.num_edges,
plane_id: self.plane_id,
side: self.side,
styles: self.styles,
material_id: self.material_id,
aabb_min: self.aabb_min,
aabb_max: self.aabb_max,
}
}
}
impl Clone for Plane {
fn clone(&self) -> Self {
Self {
dist: self.dist,
normal: self.normal,
type_: self.type_,
}
}
}
impl Clone for Vertex {
fn clone(&self) -> Self {
Self { position: self.position }
}
}
pub struct Bsp {
inner: demo3d_bsp,
pub cell_portals: Box<[CellPortals]>,
pub edges: Box<[Edge]>,
pub faces: Box<[Face]>,
pub leafs: Box<[Leaf]>,
pub marksurfaces: Box<[u16]>,
pub nodes: Box<[Node]>,
pub planes: Box<[Plane]>,
pub surfedges: Box<[i32]>,
pub vertex_lights: Box<[u32]>,
pub vertices: Box<[Vertex]>,
pub visdata: Box<[u8]>,
pub heightfield: Box<[f32]>,
}
#[derive(Default)]
pub struct BspBuilder {
pub cell_portals: Vec<CellPortals>,
pub edges: Vec<Edge>,
pub faces: Vec<Face>,
pub leafs: Vec<Leaf>,
pub marksurfaces: Vec<u16>,
pub nodes: Vec<Node>,
pub planes: Vec<Plane>,
pub surfedges: Vec<i32>,
pub vertex_lights: Vec<u32>,
pub vertices: Vec<Vertex>,
pub visdata: Vec<u8>,
pub heightfield: Vec<f32>,
pub heightfield_w: u16,
pub heightfield_h: u16,
pub heightfield_ox: f32,
pub heightfield_oz: f32,
pub heightfield_cell_size: f32,
pub bounds_min_x: f32,
pub bounds_min_z: f32,
pub bounds_max_x: f32,
pub bounds_max_z: f32,
}
impl BspBuilder {
pub fn new() -> Self {
Self::default()
}
}
impl From<BspBuilder> for Bsp {
fn from(b: BspBuilder) -> Self {
let cell_portals = b.cell_portals.into_boxed_slice();
let edges = b.edges.into_boxed_slice();
let faces = b.faces.into_boxed_slice();
let leafs = b.leafs.into_boxed_slice();
let marksurfaces = b.marksurfaces.into_boxed_slice();
let nodes = b.nodes.into_boxed_slice();
let planes = b.planes.into_boxed_slice();
let surfedges = b.surfedges.into_boxed_slice();
let vertex_lights = b.vertex_lights.into_boxed_slice();
let vertices = b.vertices.into_boxed_slice();
let visdata = b.visdata.into_boxed_slice();
let heightfield = b.heightfield.into_boxed_slice();
let inner = demo3d_bsp {
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
edges: if edges.is_empty() { core::ptr::null_mut() } else { edges.as_ptr() as *mut _ },
faces: if faces.is_empty() { core::ptr::null_mut() } else { faces.as_ptr() as *mut _ },
leafs: if leafs.is_empty() { core::ptr::null_mut() } else { leafs.as_ptr() as *mut _ },
lightdata: core::ptr::null_mut(),
lightmaps: core::ptr::null_mut(),
marksurfaces: if marksurfaces.is_empty() { core::ptr::null_mut() } else { marksurfaces.as_ptr() as *mut _ },
models: core::ptr::null_mut(),
nodes: if nodes.is_empty() { core::ptr::null_mut() } else { nodes.as_ptr() as *mut _ },
planes: if planes.is_empty() { core::ptr::null_mut() } else { planes.as_ptr() as *mut _ },
surfedges: if surfedges.is_empty() { core::ptr::null_mut() } else { surfedges.as_ptr() as *mut _ },
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
heightfield: if heightfield.is_empty() { core::ptr::null_mut() } else { heightfield.as_ptr() as *mut _ },
lightdata_size: 0,
num_cell_portals: cell_portals.len() as u32,
num_edges: edges.len() as u32,
num_faces: faces.len() as u32,
num_leafs: leafs.len() as u32,
num_lightmaps: 0,
num_marksurfaces: marksurfaces.len() as u32,
num_models: 0,
num_nodes: nodes.len() as u32,
num_planes: planes.len() as u32,
num_surfedges: surfedges.len() as u32,
num_vertex_lights: vertex_lights.len() as u32,
num_vertices: vertices.len() as u32,
num_heightfield: heightfield.len() as u32,
heightfield_ox: b.heightfield_ox,
heightfield_oz: b.heightfield_oz,
heightfield_cell_size: b.heightfield_cell_size,
heightfield_w: b.heightfield_w,
heightfield_h: b.heightfield_h,
visdata_size: visdata.len() as u32,
bounds_min_x: b.bounds_min_x,
bounds_min_z: b.bounds_min_z,
bounds_max_x: b.bounds_max_x,
bounds_max_z: b.bounds_max_z,
};
Self {
inner,
cell_portals,
edges,
faces,
leafs,
marksurfaces,
nodes,
planes,
surfedges,
vertex_lights,
vertices,
visdata,
heightfield,
}
}
}
impl Bsp {
pub fn as_c_bsp(&self) -> &demo3d_bsp {
&self.inner
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
unsafe { demo3d_bsp_trace(&self.inner, from, to, radius) }
}
}
impl Default for Bsp {
fn default() -> Self {
BspBuilder::new().into()
}
}

View file

@ -0,0 +1,26 @@
pub mod stream;
use crate::bsp::Bsp;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ChunkId {
Bsp { cx: i32, cz: i32 },
}
pub enum Chunk {
Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 },
}
impl Chunk {
pub fn version(&self) -> u32 {
match self {
Chunk::Bsp { version, .. } => *version,
}
}
pub fn as_bsp(&self) -> Option<&Bsp> {
match self {
Chunk::Bsp { bsp, .. } => Some(bsp),
}
}
}

View file

@ -0,0 +1,57 @@
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::chunk::ChunkId;
use crate::transport::ChunkMessage;
pub struct ClientChunkState {
known: BTreeMap<ChunkId, u32>,
pending: Vec<ChunkId>,
pending_messages: Vec<ChunkMessage>,
}
impl ClientChunkState {
pub fn new() -> Self {
Self {
known: BTreeMap::new(),
pending: Vec::new(),
pending_messages: Vec::new(),
}
}
pub fn request(&mut self, id: ChunkId) {
if !self.known.contains_key(&id) && !self.pending.contains(&id) {
self.pending.push(id);
}
}
pub fn next_pending(&mut self) -> Option<ChunkId> {
if self.pending.is_empty() {
None
} else {
Some(self.pending.remove(0))
}
}
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
self.pending_messages.extend(msgs);
}
pub fn next_message(&mut self) -> Option<ChunkMessage> {
if self.pending_messages.is_empty() {
None
} else {
Some(self.pending_messages.remove(0))
}
}
pub fn mark_sent(&mut self, id: ChunkId, version: u32) {
self.known.insert(id, version);
}
}
impl Default for ClientChunkState {
fn default() -> Self {
Self::new()
}
}

38
demo3d/server/src/lib.rs Normal file
View file

@ -0,0 +1,38 @@
#![no_std]
extern crate alloc;
mod allocator;
pub mod bsp;
pub mod chunk;
pub mod log;
pub mod math;
pub mod procgen;
pub mod sim;
pub mod transport;
pub mod world;
use core::panic::PanicInfo;
#[global_allocator]
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
pub mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
pub use bsp::*;
pub use chunk::*;
pub use chunk::stream::*;
pub use math::*;
pub use procgen::generate_chunk;
pub use bindings::*;
pub use sim::*;
pub use transport::*;
pub use world::*;

139
demo3d/server/src/log.rs Normal file
View file

@ -0,0 +1,139 @@
use crate::bindings::{pxl8_log, pxl8_log_init};
use core::ffi::c_char;
static mut G_LOG: pxl8_log = pxl8_log {
handler: None,
level: crate::bindings::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG,
};
pub fn init() {
unsafe {
pxl8_log_init(&raw mut G_LOG);
}
}
#[macro_export]
macro_rules! pxl8_debug {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::bindings::pxl8_log_write_debug(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_error {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::bindings::pxl8_log_write_error(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_info {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::bindings::pxl8_log_write_info(
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_trace {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::bindings::pxl8_log_write_trace(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_warn {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::bindings::pxl8_log_write_warn(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
}
}};
}
pub struct LogBuffer {
buf: [u8; 1024],
pos: usize,
}
impl LogBuffer {
pub const fn new() -> Self {
Self {
buf: [0; 1024],
pos: 0,
}
}
pub fn as_ptr(&self) -> *const c_char {
self.buf.as_ptr() as *const c_char
}
pub fn push(&mut self, b: u8) {
if self.pos < self.buf.len() {
self.buf[self.pos] = b;
self.pos += 1;
}
}
}
impl core::fmt::Write for LogBuffer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for b in s.bytes() {
if self.pos >= self.buf.len() - 1 {
break;
}
self.buf[self.pos] = b;
self.pos += 1;
}
Ok(())
}
}

323
demo3d/server/src/main.rs Normal file
View file

@ -0,0 +1,323 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::vec::Vec;
use demo3d_server::*;
use demo3d_server::chunk::ChunkId;
use demo3d_server::chunk::stream::ClientChunkState;
use demo3d_server::bindings::demo3d_cmd_type::*;
use demo3d_server::bindings::demo3d_msg_type::*;
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(&raw 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 player_cx: i32 = 0;
let mut player_cz: i32 = 0;
let mut has_sent_initial_enter = false;
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
let mut entities_buf = [demo3d_server::demo3d_entity_state {
entity_id: 0,
userdata: [0u8; 56],
}; 64];
let mut inputs_buf: [demo3d_server::demo3d_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<demo3d_server::demo3d_input_msg> = None;
while let Some(msg_type) = transport.recv() {
match msg_type {
x if x == DEMO3D_MSG_INPUT as u8 => {
latest_input = Some(transport.get_input());
}
x if x == DEMO3D_MSG_COMMAND as u8 => {
let cmd = transport.get_command();
if cmd.cmd_type == DEMO3D_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);
}
}
_ => {}
}
}
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(|state| {
if count < entities_buf.len() {
entities_buf[count] = *state;
count += 1;
}
});
let header = demo3d_server::demo3d_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((px, _py, pz)) = sim.get_player_position(pid) {
let new_cx = libm::floorf(px / 1024.0) as i32;
let new_cz = libm::floorf(pz / 1024.0) as i32;
for dz in -2..=2_i32 {
for dx in -2..=2_i32 {
let cx = new_cx + dx;
let cz = new_cz + dz;
sim.world.generate_bsp(cx, cz);
client_chunks.request(ChunkId::Bsp { cx, cz });
}
}
if !has_sent_initial_enter || player_cx != new_cx || player_cz != new_cz {
player_cx = new_cx;
player_cz = new_cz;
sim.world.set_active(ChunkId::Bsp { cx: new_cx, cz: new_cz });
transport.send_chunk_enter(new_cx, new_cz, sequence);
sequence = sequence.wrapping_add(1);
has_sent_initial_enter = true;
}
let mut burst = false;
while let Some(chunk_id) = client_chunks.next_pending() {
match chunk_id {
ChunkId::Bsp { cx, cz } => {
if let Some(chunk) = sim.world.get(&chunk_id) {
if let Some(bsp) = chunk.as_bsp() {
let msgs = bsp_to_messages(bsp, cx, cz, chunk.version());
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, chunk.version());
burst = true;
}
}
}
}
}
let send_limit = if burst { 64 } else { 8 };
for _ in 0..send_limit {
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, cx: i32, cz: i32, version: u32) -> Vec<transport::ChunkMessage> {
let mut data = Vec::new();
data.extend_from_slice(&(bsp.vertices.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.edges.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.faces.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.planes.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.nodes.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.leafs.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.surfedges.len() 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());
data.extend_from_slice(&(bsp.heightfield.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());
}
if !bsp.heightfield.is_empty() {
for h in &bsp.heightfield {
data.extend_from_slice(&h.to_be_bytes());
}
let c_bsp = bsp.as_c_bsp();
data.extend_from_slice(&c_bsp.heightfield_w.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_h.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_ox.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_oz.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_cell_size.to_be_bytes());
}
let c_bsp = bsp.as_c_bsp();
data.extend_from_slice(&c_bsp.bounds_min_x.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_min_z.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_max_x.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_max_z.to_be_bytes());
transport::ChunkMessage::from_bsp(data, cx, cz, version)
}

106
demo3d/server/src/math.rs Normal file
View file

@ -0,0 +1,106 @@
use core::ops::{Add, Mul, Sub};
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct Vec4 {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Default)]
pub struct Mat4 {
pub m: [f32; 16],
}
#[allow(non_camel_case_types)]
pub type pxl8_vec2 = Vec2;
#[allow(non_camel_case_types)]
pub type pxl8_vec3 = Vec3;
#[allow(non_camel_case_types)]
pub type pxl8_vec4 = Vec4;
#[allow(non_camel_case_types)]
pub type pxl8_mat4 = Mat4;
impl Vec3 {
pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
pub const Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 };
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn dot(self, rhs: Self) -> f32 {
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
}
pub fn cross(self, rhs: Self) -> Self {
Self {
x: self.y * rhs.z - self.z * rhs.y,
y: self.z * rhs.x - self.x * rhs.z,
z: self.x * rhs.y - self.y * rhs.x,
}
}
pub fn length(self) -> f32 {
libm::sqrtf(self.dot(self))
}
pub fn normalize(self) -> Self {
let len_sq = self.dot(self);
if len_sq < 1e-12 {
return Self::ZERO;
}
self * (1.0 / libm::sqrtf(len_sq))
}
}
impl Add for Vec3 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Sub for Vec3 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Mul<f32> for Vec3 {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}

2231
demo3d/server/src/procgen.rs Normal file

File diff suppressed because it is too large Load diff

229
demo3d/server/src/sim.rs Normal file
View file

@ -0,0 +1,229 @@
use alloc::vec::Vec;
use crate::chunk::ChunkId;
use crate::math::Vec3;
use crate::bindings::*;
use crate::world::World;
pub type Entity = demo3d_sim_entity;
const ALIVE: u32 = DEMO3D_SIM_FLAG_ALIVE;
const PLAYER: u32 = DEMO3D_SIM_FLAG_PLAYER;
const MAX_ENTITIES: usize = 1024;
impl Default for Entity {
fn default() -> Self {
Self {
pos: Vec3::ZERO,
vel: Vec3::ZERO,
yaw: 0.0,
pitch: 0.0,
flags: 0,
kind: 0,
_pad: 0,
}
}
}
impl Clone for Entity {
fn clone(&self) -> Self {
Self {
pos: self.pos,
vel: self.vel,
yaw: self.yaw,
pitch: self.pitch,
flags: self.flags,
kind: self.kind,
_pad: self._pad,
}
}
}
pub struct Simulation {
pub entities: Vec<Entity>,
pub free_list: Vec<u32>,
pub player: Option<u32>,
pub tick: u64,
pub time: f32,
pub world: World,
}
impl Simulation {
pub fn new() -> Self {
let mut entities = Vec::with_capacity(MAX_ENTITIES);
entities.resize(MAX_ENTITIES, Entity::default());
let mut free_list = Vec::with_capacity(MAX_ENTITIES);
for i in (0..MAX_ENTITIES).rev() {
free_list.push(i as u32);
}
Self {
entities,
free_list,
player: None,
tick: 0,
time: 0.0,
world: World::new(12345),
}
}
pub fn spawn(&mut self) -> Option<u32> {
let idx = self.free_list.pop()?;
self.entities[idx as usize] = Entity {
flags: ALIVE,
..Default::default()
};
Some(idx)
}
pub fn despawn(&mut self, id: u32) {
if (id as usize) < self.entities.len() {
self.entities[id as usize].flags = 0;
self.free_list.push(id);
}
}
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> u32 {
let id = self.spawn().unwrap_or(0);
let ent = &mut self.entities[id as usize];
ent.flags = ALIVE | PLAYER;
ent.pos = Vec3::new(x, y, z);
self.player = Some(id);
id
}
pub fn step(&mut self, inputs: &[demo3d_input_msg], dt: f32) {
self.tick += 1;
self.time += dt;
for input in inputs {
self.move_player(input, dt);
}
self.integrate(dt);
}
fn sim_config() -> demo3d_sim_config {
demo3d_sim_config {
move_speed: 180.0,
ground_accel: 10.0,
air_accel: 1.0,
stop_speed: 100.0,
friction: 6.0,
gravity: 800.0,
jump_velocity: 200.0,
player_radius: 16.0,
player_height: 72.0,
max_pitch: 1.5,
}
}
fn make_sim_world(&self, player_pos: Vec3) -> demo3d_sim_world {
let chunk_size: f32 = 16.0 * 64.0;
let center_cx = libm::floorf(player_pos.x / chunk_size) as i32;
let center_cz = libm::floorf(player_pos.z / chunk_size) as i32;
let mut sim = demo3d_sim_world {
chunks: [core::ptr::null(); 9],
center_cx,
center_cz,
chunk_size,
};
for dz in -1..=1i32 {
for dx in -1..=1i32 {
let cx = center_cx + dx;
let cz = center_cz + dz;
let id = ChunkId::Bsp { cx, cz };
if let Some(chunk) = self.world.get(&id) {
if let Some(bsp) = chunk.as_bsp() {
let idx = ((dz + 1) * 3 + (dx + 1)) as usize;
sim.chunks[idx] = bsp.as_c_bsp();
}
}
}
}
sim
}
fn integrate(&mut self, dt: f32) {
let cfg = Self::sim_config();
for i in 0..self.entities.len() {
let ent = &self.entities[i];
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
continue;
}
let world = self.make_sim_world(ent.pos);
let ent = &mut self.entities[i];
unsafe {
demo3d_sim_integrate(ent, &world, &cfg, dt);
}
}
}
fn move_player(&mut self, input: &demo3d_input_msg, dt: f32) {
let cfg = Self::sim_config();
let Some(id) = self.player else { return };
let ent = &self.entities[id as usize];
if ent.flags & ALIVE == 0 {
return;
}
let world = self.make_sim_world(ent.pos);
let ent = &mut self.entities[id as usize];
unsafe {
demo3d_sim_move_player(ent, input, &world, &cfg, dt);
}
}
pub fn generate_snapshot<F>(&self, mut writer: F)
where
F: FnMut(&demo3d_entity_state),
{
for (i, ent) in self.entities.iter().enumerate() {
if ent.flags & ALIVE == 0 {
continue;
}
let mut state = demo3d_entity_state {
entity_id: i as u64,
userdata: [0u8; 56],
};
let bytes = &mut state.userdata;
bytes[0..4].copy_from_slice(&ent.pos.x.to_be_bytes());
bytes[4..8].copy_from_slice(&ent.pos.y.to_be_bytes());
bytes[8..12].copy_from_slice(&ent.pos.z.to_be_bytes());
bytes[12..16].copy_from_slice(&ent.yaw.to_be_bytes());
bytes[16..20].copy_from_slice(&ent.pitch.to_be_bytes());
bytes[20..24].copy_from_slice(&ent.vel.x.to_be_bytes());
bytes[24..28].copy_from_slice(&ent.vel.y.to_be_bytes());
bytes[28..32].copy_from_slice(&ent.vel.z.to_be_bytes());
bytes[32..36].copy_from_slice(&ent.flags.to_be_bytes());
bytes[36..38].copy_from_slice(&ent.kind.to_be_bytes());
writer(&state);
}
}
pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> {
let idx = player_id as usize;
if idx < self.entities.len() {
let ent = &self.entities[idx];
if ent.flags & ALIVE != 0 && ent.flags & PLAYER != 0 {
return Some((ent.pos.x, ent.pos.y, ent.pos.z));
}
}
None
}
}
impl Default for Simulation {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,488 @@
use alloc::vec;
use alloc::vec::Vec;
use crate::bindings::*;
use crate::bindings::demo3d_msg_type::*;
pub const DEFAULT_PORT: u16 = 7777;
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
pub const CHUNK_TYPE_BSP: u8 = 1;
pub fn chunk_hash(cx: i32, cz: i32) -> u32 {
let h = (cx as u32).wrapping_mul(374761393).wrapping_add((cz as u32).wrapping_mul(668265263));
h ^ (h >> 16)
}
pub struct ChunkMessage {
pub chunk_type: u8,
pub id: u32,
pub cx: i32,
pub cy: i32,
pub cz: i32,
pub version: u32,
pub flags: u8,
pub fragment_idx: u8,
pub fragment_count: u8,
pub payload: Vec<u8>,
}
impl ChunkMessage {
pub fn from_bsp(data: Vec<u8>, cx: i32, cz: i32, version: u32) -> Vec<ChunkMessage> {
let total_size = data.len();
let id = chunk_hash(cx, cz);
if total_size <= CHUNK_MAX_PAYLOAD {
return vec![ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id,
cx,
cy: 0,
cz,
version,
flags: CHUNK_FLAG_FINAL,
fragment_idx: 0,
fragment_count: 1,
payload: data,
}];
}
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
let mut messages = Vec::new();
for i in 0..fragment_count {
let start = i * CHUNK_MAX_PAYLOAD;
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
let is_final = i == fragment_count - 1;
messages.push(ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id,
cx,
cy: 0,
cz,
version,
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
fragment_idx: i as u8,
fragment_count: fragment_count as u8,
payload: data[start..end].to_vec(),
});
}
messages
}
}
#[cfg(all(unix, not(target_os = "macos")))]
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 init() {}
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: 0 },
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(target_os = "macos")]
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 init() {}
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_len: core::mem::size_of::<sockaddr_in>() as u8,
sin_family: libc::AF_INET as u8,
sin_port: port.to_be(),
sin_addr: libc::in_addr { s_addr: 0 },
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 as ws;
pub type RawSocket = ws::SOCKET;
pub const INVALID_SOCKET: RawSocket = ws::INVALID_SOCKET;
pub fn init() {
unsafe {
let mut wsa: ws::WSADATA = core::mem::zeroed();
ws::WSAStartup(0x0202, &mut wsa);
}
}
pub fn socket() -> RawSocket {
unsafe { ws::socket(ws::AF_INET as i32, ws::SOCK_DGRAM as i32, 0) }
}
pub fn bind(sock: RawSocket, port: u16) -> i32 {
let addr = ws::SOCKADDR_IN {
sin_family: ws::AF_INET,
sin_port: port.to_be(),
sin_addr: ws::IN_ADDR { S_un: ws::IN_ADDR_0 { S_addr: 0 } },
sin_zero: [0; 8],
};
unsafe { ws::bind(sock, &addr as *const _ as *const ws::SOCKADDR, core::mem::size_of::<ws::SOCKADDR_IN>() as i32) }
}
pub fn set_nonblocking(sock: RawSocket) -> i32 {
let mut nonblocking: u32 = 1;
unsafe { ws::ioctlsocket(sock, ws::FIONBIO as i32, &mut nonblocking) }
}
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut ws::SOCKADDR_IN) -> i32 {
let mut addr_len = core::mem::size_of::<ws::SOCKADDR_IN>() as i32;
unsafe {
ws::recvfrom(
sock,
buf.as_mut_ptr(),
buf.len() as i32,
0,
addr as *mut _ as *mut ws::SOCKADDR,
&mut addr_len,
)
}
}
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &ws::SOCKADDR_IN) -> i32 {
unsafe {
ws::sendto(
sock,
buf.as_ptr(),
buf.len() as i32,
0,
addr as *const _ as *const ws::SOCKADDR,
core::mem::size_of::<ws::SOCKADDR_IN>() as i32,
)
}
}
pub fn close(sock: RawSocket) {
unsafe { ws::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; 512],
send_buf: [u8; 2048],
socket: sys::RawSocket,
}
impl Transport {
pub fn bind(port: u16) -> Option<Self> {
sys::init();
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; 512],
send_buf: [0u8; 2048],
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::<demo3d_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) -> demo3d_input_msg {
self.deserialize_input()
}
pub fn get_command(&self) -> demo3d_command_msg {
self.deserialize_command()
}
pub fn send_snapshot(
&mut self,
header: &demo3d_snapshot_header,
entities: &[demo3d_entity_state],
sequence: u32,
) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = demo3d_msg_header {
sequence,
size: 0,
type_: DEMO3D_MSG_SNAPSHOT as u8,
version: DEMO3D_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);
}
pub fn send_chunk(&mut self, msg: &ChunkMessage, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = demo3d_msg_header {
sequence,
size: 0,
type_: DEMO3D_MSG_CHUNK as u8,
version: DEMO3D_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
offset += self.serialize_chunk_msg_header(msg, offset);
let payload_len = msg.payload.len().min(self.send_buf.len() - offset);
self.send_buf[offset..offset + payload_len].copy_from_slice(&msg.payload[..payload_len]);
offset += payload_len;
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = demo3d_msg_header {
sequence,
size: 0,
type_: DEMO3D_MSG_CHUNK_ENTER as u8,
version: DEMO3D_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
let buf = &mut self.send_buf[offset..];
buf[0..4].copy_from_slice(&cx.to_be_bytes());
buf[4..8].copy_from_slice(&cz.to_be_bytes());
offset += 8;
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0] = msg.chunk_type;
buf[1] = msg.flags;
buf[2] = msg.fragment_idx;
buf[3] = msg.fragment_count;
buf[4..8].copy_from_slice(&msg.id.to_be_bytes());
buf[8..12].copy_from_slice(&(msg.cx as u32).to_be_bytes());
buf[12..16].copy_from_slice(&(msg.cy as u32).to_be_bytes());
buf[16..20].copy_from_slice(&(msg.cz as u32).to_be_bytes());
buf[20..24].copy_from_slice(&msg.version.to_be_bytes());
let payload_size = msg.payload.len() as u16;
buf[24..26].copy_from_slice(&payload_size.to_be_bytes());
buf[26..28].copy_from_slice(&[0u8; 2]);
28
}
fn serialize_header(&mut self, h: &demo3d_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: &demo3d_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: &demo3d_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) -> demo3d_msg_header {
let buf = &self.recv_buf;
demo3d_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) -> demo3d_input_msg {
let buf = &self.recv_buf[8..];
demo3d_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) -> demo3d_command_msg {
let buf = &self.recv_buf[8..];
let mut cmd = demo3d_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);
}
}

View file

@ -0,0 +1,47 @@
use alloc::collections::BTreeMap;
use crate::chunk::{Chunk, ChunkId};
use crate::procgen::generate_chunk;
pub struct World {
active: Option<ChunkId>,
chunks: BTreeMap<ChunkId, Chunk>,
pub seed: u64,
}
impl World {
pub fn new(seed: u64) -> Self {
Self {
chunks: BTreeMap::new(),
active: None,
seed,
}
}
pub fn get(&self, id: &ChunkId) -> Option<&Chunk> {
self.chunks.get(id)
}
pub fn active(&self) -> Option<&Chunk> {
self.active.as_ref().and_then(|id| self.chunks.get(id))
}
pub fn set_active(&mut self, id: ChunkId) {
self.active = Some(id);
}
pub fn generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk {
let chunk_id = ChunkId::Bsp { cx, cz };
if !self.chunks.contains_key(&chunk_id) {
let bsp = generate_chunk(cx, cz, self.seed);
self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, bsp, version: 1 });
}
self.chunks.get(&chunk_id).unwrap()
}
}
impl Default for World {
fn default() -> Self {
Self::new(12345)
}
}