refactor separate framework from game code, add demo3d
This commit is contained in:
parent
19ae869769
commit
40f5cdcaa5
92 changed files with 2665 additions and 6547 deletions
2
demo3d/server/.cargo/config.toml
Normal file
2
demo3d/server/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
1
demo3d/server/.gitignore
vendored
Normal file
1
demo3d/server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
target/
|
||||
263
demo3d/server/Cargo.lock
generated
Normal file
263
demo3d/server/Cargo.lock
generated
Normal 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
40
demo3d/server/Cargo.toml
Normal 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
68
demo3d/server/build.rs
Normal 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");
|
||||
}
|
||||
2
demo3d/server/rust-toolchain.toml
Normal file
2
demo3d/server/rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
58
demo3d/server/src/allocator.rs
Normal file
58
demo3d/server/src/allocator.rs
Normal 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
270
demo3d/server/src/bsp.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
26
demo3d/server/src/chunk.rs
Normal file
26
demo3d/server/src/chunk.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
57
demo3d/server/src/chunk/stream.rs
Normal file
57
demo3d/server/src/chunk/stream.rs
Normal 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
38
demo3d/server/src/lib.rs
Normal 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
139
demo3d/server/src/log.rs
Normal 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
323
demo3d/server/src/main.rs
Normal 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
106
demo3d/server/src/math.rs
Normal 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
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
229
demo3d/server/src/sim.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
488
demo3d/server/src/transport.rs
Normal file
488
demo3d/server/src/transport.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
47
demo3d/server/src/world.rs
Normal file
47
demo3d/server/src/world.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue