Compare commits
74 Commits
7d59b1eed5
...
e8a79e112c
| Author | SHA1 | Date | |
|---|---|---|---|
| e8a79e112c | |||
| 63d5ae2c25 | |||
| d17732fcd5 | |||
| a7cd14f413 | |||
| 5496525a7f | |||
| 2f60ace70a | |||
| 83c97faed0 | |||
| 3897bb0510 | |||
| 038398962e | |||
| 586792b774 | |||
| 2cfb721359 | |||
| dbaff58a3f | |||
| 19db4dd390 | |||
| 87b9b7c1bd | |||
| 6c3ef334fb | |||
| cde271139f | |||
| 566990b7af | |||
| 4519c5c4a6 | |||
| e28690b24a | |||
| acaad86aee | |||
| 5d0fc9d8d1 | |||
| 49957435d7 | |||
| cddf9540dd | |||
| b8334ea361 | |||
| 6c9fc0cbf1 | |||
| 4debec43e7 | |||
| 6d30151be1 | |||
| 6c938999e4 | |||
| ba610f48dc | |||
| 643a329338 | |||
| a5c0179793 | |||
| b556cdd768 | |||
| 3a311e14af | |||
| e2424bf8c9 | |||
| 71045d8603 | |||
| 5198e8606f | |||
| 3248cd3529 | |||
| eea6194d86 | |||
| 3d0657885b | |||
| 3839aade62 | |||
| 49bfa31639 | |||
| a9b263bf57 | |||
| f6912be248 | |||
| 72fa401420 | |||
| 03b1419b17 | |||
| 2b3e3a6a5e | |||
| 0991f74275 | |||
| 26e4d7fa4f | |||
| 261e52a752 | |||
| 56c5aff483 | |||
| 7150924d5d | |||
| bd044c6653 | |||
| 4436382baf | |||
| 7ba3d5758b | |||
| a1a90ae4f8 | |||
| 6de5681707 | |||
| 4cda9d54f3 | |||
| f52186f732 | |||
| f0646c34eb | |||
| dc12715279 | |||
| 75ec3b308f | |||
| 67273834d6 | |||
| 3f12c4661c | |||
| d3eead53cf | |||
| 102304760e | |||
| b516984015 | |||
| b8c3b6422c | |||
| 9bdb502f8f | |||
| 7d91e204cc | |||
| c1c84cdbff | |||
| 0570d3c4ba | |||
| 74694315a6 | |||
| cc24b19f93 | |||
| 8d3855c1a7 |
53
.claude/settings.local.json
Normal file
53
.claude/settings.local.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(export PATH=\"$HOME/.cargo/bin:$PATH\")",
|
||||
"Bash(cargo test:*)",
|
||||
"Bash(where link.exe)",
|
||||
"Bash(rustup show:*)",
|
||||
"Bash(cargo clean:*)",
|
||||
"Read(//c/Program Files/Microsoft Visual Studio/**)",
|
||||
"Read(//c/Program Files \\(x86\\)/Microsoft Visual Studio/**)",
|
||||
"Read(//c//**)",
|
||||
"Read(//c/Users/SSAFY/miniforge3/Library/bin/**)",
|
||||
"Read(//c/Users/SSAFY/miniforge3/Library/mingw-w64/**)",
|
||||
"Read(//c/Users/SSAFY/miniforge3/**)",
|
||||
"Bash(ls C:/Users/SSAFY/miniforge3/Library/bin/ld*)",
|
||||
"Bash(ls C:/Users/SSAFY/miniforge3/Library/mingw-w64/bin/ld*)",
|
||||
"Bash(conda install:*)",
|
||||
"Bash(C:/Users/SSAFY/miniforge3/Scripts/conda.exe install:*)",
|
||||
"Bash(export PATH=$HOME/.cargo/bin:$PATH)",
|
||||
"Read(//c/Users/SSAFY/Desktop/projects/voltex/**)",
|
||||
"Bash(export PATH=\"$HOME/.cargo/bin:/c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.44.35207/bin/Hostx64/x64:$PATH\")",
|
||||
"Bash(cmd //C \"call \\\\\"C:\\\\\\\\Program Files\\\\\\\\Microsoft Visual Studio\\\\\\\\2022\\\\\\\\Community\\\\\\\\VC\\\\\\\\Auxiliary\\\\\\\\Build\\\\\\\\vcvarsall.bat\\\\\" x64 >nul 2>&1 && echo LIB=%LIB% && echo INCLUDE=%INCLUDE%\")",
|
||||
"Bash(ls '/c/Program Files \\(x86\\)/Windows Kits/')",
|
||||
"Bash(find '/c/Program Files \\(x86\\)/Windows Kits' -name kernel32.lib)",
|
||||
"Bash(find '/c/Program Files \\(x86\\)/Windows Kits/10' -name kernel32.lib)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(export PATH=\"$HOME/.cargo/bin:$PATH\" MSVC_LIB=\"/c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.44.35207/lib/x64\" SDK_UM=\"/c/Program Files \\(x86\\)/Windows Kits/10/Lib/10.0.26100.0/um/x64\" SDK_UCRT=\"/c/Program Files \\(x86\\)/Windows Kits/10/Lib/10.0.26100.0/ucrt/x64\" export LIB=\"$MSVC_LIB;$SDK_UM;$SDK_UCRT\" __NEW_LINE_dfaf9ba8c4cc16a9__ MSVC_INC=\"/c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.44.35207/include\" SDK_INC=\"/c/Program Files \\(x86\\)/Windows Kits/10/Include/10.0.26100.0/ucrt\" SDK_UM_INC=\"/c/Program Files \\(x86\\)/Windows Kits/10/Include/10.0.26100.0/um\" SDK_SHARED_INC=\"/c/Program Files \\(x86\\)/Windows Kits/10/Include/10.0.26100.0/shared\" export INCLUDE=\"$MSVC_INC;$SDK_INC;$SDK_UM_INC;$SDK_SHARED_INC\" __NEW_LINE_dfaf9ba8c4cc16a9__ export PATH=\"/c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.44.35207/bin/Hostx64/x64:$PATH\" __NEW_LINE_dfaf9ba8c4cc16a9__ cargo test --workspace)",
|
||||
"Bash(find '/c/Program Files \\(x86\\)/Windows Kits' -name dbghelp.lib)",
|
||||
"Bash(find /c/Users/SSAFY/Desktop/projects/voltex -name *.rs -path */examples*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(cargo build:*)",
|
||||
"Bash(cargo run:*)",
|
||||
"Bash(cargo doc:*)",
|
||||
"Bash(cargo metadata:*)",
|
||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); pkgs=[p for p in d[''''packages''''] if p[''''name'''']==''''wgpu'''']; feats=pkgs[0][''''features''''] if pkgs else {}; print\\([f for f in feats if ''''ray'''' in f.lower\\(\\)]\\)\")",
|
||||
"Read(//c/Users/SSAFY/Desktop/projects/voltex/$HOME/.cargo/registry/src/*/wgpu-28.*/**)",
|
||||
"Bash(find /c/Users/SSAFY/.cargo/registry/src -name *.rs -path *wgpu_types*)",
|
||||
"Bash(xargs grep:*)",
|
||||
"Bash(find /c/Users/SSAFY/.cargo/registry/src -name *.rs)",
|
||||
"Bash(find /c/Users/SSAFY/.cargo/registry/src -name *.rs -path *wgpu*)",
|
||||
"Bash(find /c/Users/SSAFY/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-28.0.0/src -name *.rs)",
|
||||
"Bash(grep -r ExperimentalFeatures C:/Users/SSAFY/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-core-*/src/)",
|
||||
"Bash(curl -sL https://www.lua.org/ftp/lua-5.4.7.tar.gz -o /tmp/lua-5.4.7.tar.gz)",
|
||||
"Read(//tmp/**)",
|
||||
"Bash(mkdir -p C:/Users/SSAFY/Desktop/projects/voltex/crates/voltex_script/lua)",
|
||||
"Read(//c/tmp/**)",
|
||||
"Bash(tar xzf:*)",
|
||||
"Bash(cp lua-5.4.7/src/*.c lua-5.4.7/src/*.h C:/Users/SSAFY/Desktop/projects/voltex/crates/voltex_script/lua/)",
|
||||
"Bash(cd:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
95
Cargo.lock
generated
95
Cargo.lock
generated
@@ -180,6 +180,13 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "audio_demo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"voltex_audio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
@@ -425,6 +432,21 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "deferred_demo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"log",
|
||||
"pollster",
|
||||
"voltex_math",
|
||||
"voltex_platform",
|
||||
"voltex_renderer",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@@ -461,6 +483,21 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
||||
|
||||
[[package]]
|
||||
name = "editor_demo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"log",
|
||||
"pollster",
|
||||
"voltex_editor",
|
||||
"voltex_platform",
|
||||
"voltex_renderer",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "1.0.1"
|
||||
@@ -1840,6 +1877,23 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
|
||||
[[package]]
|
||||
name = "survivor_game"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"log",
|
||||
"pollster",
|
||||
"voltex_audio",
|
||||
"voltex_editor",
|
||||
"voltex_math",
|
||||
"voltex_platform",
|
||||
"voltex_renderer",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -2022,10 +2076,24 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "voltex_ai"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"voltex_math",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "voltex_asset"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "voltex_audio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"voltex_math",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "voltex_ecs"
|
||||
version = "0.1.0"
|
||||
@@ -2033,10 +2101,30 @@ dependencies = [
|
||||
"voltex_math",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "voltex_editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "voltex_math"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "voltex_net"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "voltex_physics"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"voltex_ecs",
|
||||
"voltex_math",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "voltex_platform"
|
||||
version = "0.1.0"
|
||||
@@ -2059,6 +2147,13 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "voltex_script"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
||||
16
Cargo.toml
16
Cargo.toml
@@ -15,6 +15,16 @@ members = [
|
||||
"examples/multi_light_demo",
|
||||
"examples/shadow_demo",
|
||||
"examples/ibl_demo",
|
||||
"examples/deferred_demo",
|
||||
"crates/voltex_physics",
|
||||
"crates/voltex_audio",
|
||||
"examples/audio_demo",
|
||||
"crates/voltex_ai",
|
||||
"crates/voltex_net",
|
||||
"crates/voltex_script",
|
||||
"crates/voltex_editor",
|
||||
"examples/editor_demo",
|
||||
"examples/survivor_game",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
@@ -23,6 +33,12 @@ voltex_platform = { path = "crates/voltex_platform" }
|
||||
voltex_renderer = { path = "crates/voltex_renderer" }
|
||||
voltex_ecs = { path = "crates/voltex_ecs" }
|
||||
voltex_asset = { path = "crates/voltex_asset" }
|
||||
voltex_physics = { path = "crates/voltex_physics" }
|
||||
voltex_audio = { path = "crates/voltex_audio" }
|
||||
voltex_ai = { path = "crates/voltex_ai" }
|
||||
voltex_net = { path = "crates/voltex_net" }
|
||||
voltex_script = { path = "crates/voltex_script" }
|
||||
voltex_editor = { path = "crates/voltex_editor" }
|
||||
wgpu = "28.0"
|
||||
winit = "0.30"
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
|
||||
7
crates/voltex_ai/Cargo.toml
Normal file
7
crates/voltex_ai/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "voltex_ai"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
voltex_math.workspace = true
|
||||
7
crates/voltex_ai/src/lib.rs
Normal file
7
crates/voltex_ai/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod navmesh;
|
||||
pub mod pathfinding;
|
||||
pub mod steering;
|
||||
|
||||
pub use navmesh::{NavMesh, NavTriangle};
|
||||
pub use pathfinding::find_path;
|
||||
pub use steering::{SteeringAgent, seek, flee, arrive, wander, follow_path};
|
||||
158
crates/voltex_ai/src/navmesh.rs
Normal file
158
crates/voltex_ai/src/navmesh.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use voltex_math::Vec3;
|
||||
|
||||
/// A triangle in the navigation mesh, defined by vertex indices and neighbor triangle indices.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NavTriangle {
|
||||
/// Indices into NavMesh::vertices
|
||||
pub indices: [usize; 3],
|
||||
/// Neighboring triangle indices (None if no neighbor on that edge)
|
||||
pub neighbors: [Option<usize>; 3],
|
||||
}
|
||||
|
||||
/// Navigation mesh composed of triangles for pathfinding.
|
||||
pub struct NavMesh {
|
||||
pub vertices: Vec<Vec3>,
|
||||
pub triangles: Vec<NavTriangle>,
|
||||
}
|
||||
|
||||
impl NavMesh {
|
||||
pub fn new(vertices: Vec<Vec3>, triangles: Vec<NavTriangle>) -> Self {
|
||||
Self { vertices, triangles }
|
||||
}
|
||||
|
||||
/// Find the index of the triangle that contains `point` (XZ plane test).
|
||||
/// Returns None if the point is outside all triangles.
|
||||
pub fn find_triangle(&self, point: Vec3) -> Option<usize> {
|
||||
for (i, tri) in self.triangles.iter().enumerate() {
|
||||
let a = self.vertices[tri.indices[0]];
|
||||
let b = self.vertices[tri.indices[1]];
|
||||
let c = self.vertices[tri.indices[2]];
|
||||
if point_in_triangle_xz(point, a, b, c) {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Return the centroid of triangle `tri_idx` in 3D space.
|
||||
pub fn triangle_center(&self, tri_idx: usize) -> Vec3 {
|
||||
let tri = &self.triangles[tri_idx];
|
||||
let a = self.vertices[tri.indices[0]];
|
||||
let b = self.vertices[tri.indices[1]];
|
||||
let c = self.vertices[tri.indices[2]];
|
||||
Vec3::new(
|
||||
(a.x + b.x + c.x) / 3.0,
|
||||
(a.y + b.y + c.y) / 3.0,
|
||||
(a.z + b.z + c.z) / 3.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the midpoint of the shared edge between `tri_idx` and neighbor slot `edge_idx` (0, 1, or 2).
|
||||
/// Edge 0 is between vertex indices[0] and indices[1],
|
||||
/// Edge 1 is between indices[1] and indices[2],
|
||||
/// Edge 2 is between indices[2] and indices[0].
|
||||
pub fn edge_midpoint(&self, tri_idx: usize, edge_idx: usize) -> Vec3 {
|
||||
let tri = &self.triangles[tri_idx];
|
||||
let (i0, i1) = match edge_idx {
|
||||
0 => (tri.indices[0], tri.indices[1]),
|
||||
1 => (tri.indices[1], tri.indices[2]),
|
||||
2 => (tri.indices[2], tri.indices[0]),
|
||||
_ => panic!("edge_idx must be 0, 1, or 2"),
|
||||
};
|
||||
let a = self.vertices[i0];
|
||||
let b = self.vertices[i1];
|
||||
Vec3::new(
|
||||
(a.x + b.x) / 2.0,
|
||||
(a.y + b.y) / 2.0,
|
||||
(a.z + b.z) / 2.0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Test whether `point` lies inside or on the triangle (a, b, c) using XZ barycentric coordinates.
|
||||
pub fn point_in_triangle_xz(point: Vec3, a: Vec3, b: Vec3, c: Vec3) -> bool {
|
||||
// Compute barycentric coordinates using XZ plane
|
||||
let px = point.x;
|
||||
let pz = point.z;
|
||||
let ax = a.x; let az = a.z;
|
||||
let bx = b.x; let bz = b.z;
|
||||
let cx = c.x; let cz = c.z;
|
||||
|
||||
let denom = (bz - cz) * (ax - cx) + (cx - bx) * (az - cz);
|
||||
if denom.abs() < f32::EPSILON {
|
||||
return false; // degenerate triangle
|
||||
}
|
||||
|
||||
let u = ((bz - cz) * (px - cx) + (cx - bx) * (pz - cz)) / denom;
|
||||
let v = ((cz - az) * (px - cx) + (ax - cx) * (pz - cz)) / denom;
|
||||
let w = 1.0 - u - v;
|
||||
|
||||
u >= 0.0 && v >= 0.0 && w >= 0.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_simple_navmesh() -> NavMesh {
|
||||
// Two triangles sharing an edge:
|
||||
// v0=(0,0,0), v1=(2,0,0), v2=(1,0,2), v3=(3,0,2)
|
||||
// Tri 0: v0, v1, v2 (left triangle)
|
||||
// Tri 1: v1, v3, v2 (right triangle)
|
||||
let vertices = vec![
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(2.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 2.0),
|
||||
Vec3::new(3.0, 0.0, 2.0),
|
||||
];
|
||||
let triangles = vec![
|
||||
NavTriangle {
|
||||
indices: [0, 1, 2],
|
||||
neighbors: [None, Some(1), None],
|
||||
},
|
||||
NavTriangle {
|
||||
indices: [1, 3, 2],
|
||||
neighbors: [None, None, Some(0)],
|
||||
},
|
||||
];
|
||||
NavMesh::new(vertices, triangles)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_triangle_inside() {
|
||||
let nm = make_simple_navmesh();
|
||||
// Center of tri 0 is roughly (1, 0, 0.67)
|
||||
let p = Vec3::new(1.0, 0.0, 0.5);
|
||||
let result = nm.find_triangle(p);
|
||||
assert_eq!(result, Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_triangle_outside() {
|
||||
let nm = make_simple_navmesh();
|
||||
// Point far outside the mesh
|
||||
let p = Vec3::new(10.0, 0.0, 10.0);
|
||||
let result = nm.find_triangle(p);
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_triangle_center() {
|
||||
let nm = make_simple_navmesh();
|
||||
let center = nm.triangle_center(0);
|
||||
// (0+2+1)/3 = 1.0, (0+0+0)/3 = 0.0, (0+0+2)/3 = 0.667
|
||||
assert!((center.x - 1.0).abs() < 1e-5);
|
||||
assert!((center.y - 0.0).abs() < 1e-5);
|
||||
assert!((center.z - (2.0 / 3.0)).abs() < 1e-5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_midpoint() {
|
||||
let nm = make_simple_navmesh();
|
||||
// Edge 1 of tri 0 is between indices[1]=v1=(2,0,0) and indices[2]=v2=(1,0,2)
|
||||
let mid = nm.edge_midpoint(0, 1);
|
||||
assert!((mid.x - 1.5).abs() < 1e-5);
|
||||
assert!((mid.y - 0.0).abs() < 1e-5);
|
||||
assert!((mid.z - 1.0).abs() < 1e-5);
|
||||
}
|
||||
}
|
||||
241
crates/voltex_ai/src/pathfinding.rs
Normal file
241
crates/voltex_ai/src/pathfinding.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use std::collections::BinaryHeap;
|
||||
use std::cmp::Ordering;
|
||||
use voltex_math::Vec3;
|
||||
use crate::navmesh::NavMesh;
|
||||
|
||||
/// Node for A* priority queue (min-heap by f_cost, then g_cost).
|
||||
#[derive(Debug, Clone)]
|
||||
struct AStarNode {
|
||||
tri_idx: usize,
|
||||
g_cost: f32,
|
||||
f_cost: f32,
|
||||
parent: Option<usize>,
|
||||
}
|
||||
|
||||
impl PartialEq for AStarNode {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.f_cost == other.f_cost && self.g_cost == other.g_cost
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AStarNode {}
|
||||
|
||||
// BinaryHeap is a max-heap; we negate costs to get min-heap behavior.
|
||||
impl PartialOrd for AStarNode {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for AStarNode {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// Reverse ordering: lower f_cost = higher priority
|
||||
other.f_cost.partial_cmp(&self.f_cost)
|
||||
.unwrap_or(Ordering::Equal)
|
||||
.then_with(|| other.g_cost.partial_cmp(&self.g_cost).unwrap_or(Ordering::Equal))
|
||||
}
|
||||
}
|
||||
|
||||
/// XZ distance between two Vec3 points (ignoring Y).
|
||||
pub fn distance_xz(a: Vec3, b: Vec3) -> f32 {
|
||||
let dx = a.x - b.x;
|
||||
let dz = a.z - b.z;
|
||||
(dx * dx + dz * dz).sqrt()
|
||||
}
|
||||
|
||||
/// Find a path from `start` to `goal` on the given NavMesh using A*.
|
||||
///
|
||||
/// Returns Some(path) where path[0] == start, path[last] == goal, and
|
||||
/// intermediate points are triangle centers. Returns None if either
|
||||
/// point is outside the mesh or no path exists.
|
||||
pub fn find_path(navmesh: &NavMesh, start: Vec3, goal: Vec3) -> Option<Vec<Vec3>> {
|
||||
let start_tri = navmesh.find_triangle(start)?;
|
||||
let goal_tri = navmesh.find_triangle(goal)?;
|
||||
|
||||
// If start and goal are in the same triangle, path is direct.
|
||||
if start_tri == goal_tri {
|
||||
return Some(vec![start, goal]);
|
||||
}
|
||||
|
||||
let n = navmesh.triangles.len();
|
||||
// g_cost[i] = best known cost to reach triangle i
|
||||
let mut g_costs = vec![f32::INFINITY; n];
|
||||
// parent[i] = index of parent triangle in the A* tree
|
||||
let mut parents: Vec<Option<usize>> = vec![None; n];
|
||||
let mut visited = vec![false; n];
|
||||
|
||||
let goal_center = navmesh.triangle_center(goal_tri);
|
||||
|
||||
g_costs[start_tri] = 0.0;
|
||||
let start_center = navmesh.triangle_center(start_tri);
|
||||
let h = distance_xz(start_center, goal_center);
|
||||
|
||||
let mut open = BinaryHeap::new();
|
||||
open.push(AStarNode {
|
||||
tri_idx: start_tri,
|
||||
g_cost: 0.0,
|
||||
f_cost: h,
|
||||
parent: None,
|
||||
});
|
||||
|
||||
while let Some(node) = open.pop() {
|
||||
let idx = node.tri_idx;
|
||||
|
||||
if visited[idx] {
|
||||
continue;
|
||||
}
|
||||
visited[idx] = true;
|
||||
parents[idx] = node.parent;
|
||||
|
||||
if idx == goal_tri {
|
||||
// Reconstruct path
|
||||
let mut tri_path = Vec::new();
|
||||
let mut cur = idx;
|
||||
loop {
|
||||
tri_path.push(cur);
|
||||
match parents[cur] {
|
||||
Some(p) => cur = p,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
tri_path.reverse();
|
||||
|
||||
// Convert triangle path to Vec3 waypoints:
|
||||
// start point -> intermediate triangle centers -> goal point
|
||||
let mut path = Vec::new();
|
||||
path.push(start);
|
||||
// skip first (start_tri) and last (goal_tri) in intermediate centers
|
||||
for &ti in &tri_path[1..tri_path.len() - 1] {
|
||||
path.push(navmesh.triangle_center(ti));
|
||||
}
|
||||
path.push(goal);
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
let tri = &navmesh.triangles[idx];
|
||||
let current_center = navmesh.triangle_center(idx);
|
||||
|
||||
for neighbor_opt in &tri.neighbors {
|
||||
if let Some(nb_idx) = *neighbor_opt {
|
||||
if visited[nb_idx] {
|
||||
continue;
|
||||
}
|
||||
let nb_center = navmesh.triangle_center(nb_idx);
|
||||
let tentative_g = g_costs[idx] + distance_xz(current_center, nb_center);
|
||||
if tentative_g < g_costs[nb_idx] {
|
||||
g_costs[nb_idx] = tentative_g;
|
||||
let h_nb = distance_xz(nb_center, goal_center);
|
||||
open.push(AStarNode {
|
||||
tri_idx: nb_idx,
|
||||
g_cost: tentative_g,
|
||||
f_cost: tentative_g + h_nb,
|
||||
parent: Some(idx),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None // No path found
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::navmesh::{NavMesh, NavTriangle};
|
||||
|
||||
/// Build a 3-triangle strip along Z axis:
|
||||
/// v0=(0,0,0), v1=(2,0,0), v2=(0,0,2), v3=(2,0,2), v4=(0,0,4), v5=(2,0,4)
|
||||
/// Tri0: v0,v1,v2 — Tri1: v1,v3,v2 — Tri2: v2,v3,v4... wait, need a strip
|
||||
///
|
||||
/// Simpler strip:
|
||||
/// Tri0: (0,0,0),(2,0,0),(1,0,2) center ~(1,0,0.67)
|
||||
/// Tri1: (2,0,0),(3,0,2),(1,0,2) center ~(2,0,1.33)
|
||||
/// Tri2: (1,0,2),(3,0,2),(2,0,4) center ~(2,0,2.67)
|
||||
fn make_strip() -> NavMesh {
|
||||
let vertices = vec![
|
||||
Vec3::new(0.0, 0.0, 0.0), // 0
|
||||
Vec3::new(2.0, 0.0, 0.0), // 1
|
||||
Vec3::new(1.0, 0.0, 2.0), // 2
|
||||
Vec3::new(3.0, 0.0, 2.0), // 3
|
||||
Vec3::new(2.0, 0.0, 4.0), // 4
|
||||
];
|
||||
let triangles = vec![
|
||||
NavTriangle { indices: [0, 1, 2], neighbors: [None, Some(1), None] },
|
||||
NavTriangle { indices: [1, 3, 2], neighbors: [None, Some(2), Some(0)] },
|
||||
NavTriangle { indices: [2, 3, 4], neighbors: [Some(1), None, None] },
|
||||
];
|
||||
NavMesh::new(vertices, triangles)
|
||||
}
|
||||
|
||||
fn make_disconnected() -> NavMesh {
|
||||
// Two separate triangles not connected
|
||||
let vertices = vec![
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(2.0, 0.0, 0.0),
|
||||
Vec3::new(1.0, 0.0, 2.0),
|
||||
Vec3::new(10.0, 0.0, 10.0),
|
||||
Vec3::new(12.0, 0.0, 10.0),
|
||||
Vec3::new(11.0, 0.0, 12.0),
|
||||
];
|
||||
let triangles = vec![
|
||||
NavTriangle { indices: [0, 1, 2], neighbors: [None, None, None] },
|
||||
NavTriangle { indices: [3, 4, 5], neighbors: [None, None, None] },
|
||||
];
|
||||
NavMesh::new(vertices, triangles)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_same_triangle() {
|
||||
let nm = make_strip();
|
||||
let start = Vec3::new(0.5, 0.0, 0.5);
|
||||
let goal = Vec3::new(1.5, 0.0, 0.5);
|
||||
let path = find_path(&nm, start, goal).expect("should find path");
|
||||
assert_eq!(path.len(), 2);
|
||||
assert_eq!(path[0], start);
|
||||
assert_eq!(path[path.len() - 1], goal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_adjacent_triangles() {
|
||||
let nm = make_strip();
|
||||
// start in tri0, goal in tri1
|
||||
let start = Vec3::new(0.8, 0.0, 0.5);
|
||||
let goal = Vec3::new(2.5, 0.0, 1.5);
|
||||
let path = find_path(&nm, start, goal).expect("should find path");
|
||||
assert!(path.len() >= 2);
|
||||
assert_eq!(path[0], start);
|
||||
assert_eq!(path[path.len() - 1], goal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_three_triangle_strip() {
|
||||
let nm = make_strip();
|
||||
// start in tri0, goal in tri2
|
||||
let start = Vec3::new(0.8, 0.0, 0.5);
|
||||
let goal = Vec3::new(2.0, 0.0, 3.5);
|
||||
let path = find_path(&nm, start, goal).expect("should find path");
|
||||
assert!(path.len() >= 3, "path through 3 triangles should have at least 3 points");
|
||||
assert_eq!(path[0], start);
|
||||
assert_eq!(path[path.len() - 1], goal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_outside_returns_none() {
|
||||
let nm = make_strip();
|
||||
// start is outside the mesh
|
||||
let start = Vec3::new(100.0, 0.0, 100.0);
|
||||
let goal = Vec3::new(0.8, 0.0, 0.5);
|
||||
let result = find_path(&nm, start, goal);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_disconnected_returns_none() {
|
||||
let nm = make_disconnected();
|
||||
let start = Vec3::new(0.8, 0.0, 0.5);
|
||||
let goal = Vec3::new(11.0, 0.0, 10.5);
|
||||
let result = find_path(&nm, start, goal);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
}
|
||||
232
crates/voltex_ai/src/steering.rs
Normal file
232
crates/voltex_ai/src/steering.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use voltex_math::Vec3;
|
||||
|
||||
/// An agent that can be steered through the world.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SteeringAgent {
|
||||
pub position: Vec3,
|
||||
pub velocity: Vec3,
|
||||
pub max_speed: f32,
|
||||
pub max_force: f32,
|
||||
}
|
||||
|
||||
impl SteeringAgent {
|
||||
pub fn new(position: Vec3, max_speed: f32, max_force: f32) -> Self {
|
||||
Self {
|
||||
position,
|
||||
velocity: Vec3::ZERO,
|
||||
max_speed,
|
||||
max_force,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate vector `v` to at most `max_len` magnitude.
|
||||
pub fn truncate(v: Vec3, max_len: f32) -> Vec3 {
|
||||
let len_sq = v.length_squared();
|
||||
if len_sq > max_len * max_len {
|
||||
v.normalize() * max_len
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
/// Seek steering behavior: move toward `target` at max speed.
|
||||
/// Returns the steering force to apply.
|
||||
pub fn seek(agent: &SteeringAgent, target: Vec3) -> Vec3 {
|
||||
let to_target = target - agent.position;
|
||||
let len = to_target.length();
|
||||
if len < f32::EPSILON {
|
||||
return Vec3::ZERO;
|
||||
}
|
||||
let desired = to_target.normalize() * agent.max_speed;
|
||||
let steering = desired - agent.velocity;
|
||||
truncate(steering, agent.max_force)
|
||||
}
|
||||
|
||||
/// Flee steering behavior: move away from `threat` at max speed.
|
||||
/// Returns the steering force to apply.
|
||||
pub fn flee(agent: &SteeringAgent, threat: Vec3) -> Vec3 {
|
||||
let away = agent.position - threat;
|
||||
let len = away.length();
|
||||
if len < f32::EPSILON {
|
||||
return Vec3::ZERO;
|
||||
}
|
||||
let desired = away.normalize() * agent.max_speed;
|
||||
let steering = desired - agent.velocity;
|
||||
truncate(steering, agent.max_force)
|
||||
}
|
||||
|
||||
/// Arrive steering behavior: decelerate when within `slow_radius` of `target`.
|
||||
/// Returns the steering force to apply.
|
||||
pub fn arrive(agent: &SteeringAgent, target: Vec3, slow_radius: f32) -> Vec3 {
|
||||
let to_target = target - agent.position;
|
||||
let dist = to_target.length();
|
||||
if dist < f32::EPSILON {
|
||||
return Vec3::ZERO;
|
||||
}
|
||||
|
||||
let desired_speed = if dist < slow_radius {
|
||||
agent.max_speed * (dist / slow_radius)
|
||||
} else {
|
||||
agent.max_speed
|
||||
};
|
||||
|
||||
let desired = to_target.normalize() * desired_speed;
|
||||
let steering = desired - agent.velocity;
|
||||
truncate(steering, agent.max_force)
|
||||
}
|
||||
|
||||
/// Wander steering behavior: steer toward a point on a circle projected ahead of the agent.
|
||||
/// `wander_radius` is the radius of the wander circle,
|
||||
/// `wander_distance` is how far ahead the circle is projected,
|
||||
/// `angle` is the current angle on the wander circle (in radians).
|
||||
/// Returns the steering force to apply.
|
||||
pub fn wander(agent: &SteeringAgent, wander_radius: f32, wander_distance: f32, angle: f32) -> Vec3 {
|
||||
// Compute forward direction; use +Z if velocity is near zero
|
||||
let forward = {
|
||||
let len = agent.velocity.length();
|
||||
if len > f32::EPSILON {
|
||||
agent.velocity.normalize()
|
||||
} else {
|
||||
Vec3::Z
|
||||
}
|
||||
};
|
||||
|
||||
// Circle center is ahead of the agent
|
||||
let circle_center = agent.position + forward * wander_distance;
|
||||
|
||||
// Point on the circle at the given angle (in XZ plane)
|
||||
let wander_target = Vec3::new(
|
||||
circle_center.x + angle.cos() * wander_radius,
|
||||
circle_center.y,
|
||||
circle_center.z + angle.sin() * wander_radius,
|
||||
);
|
||||
|
||||
seek(agent, wander_target)
|
||||
}
|
||||
|
||||
/// Follow path steering behavior.
|
||||
/// Seeks toward the current waypoint; advances to the next when within `waypoint_radius`.
|
||||
/// Uses `arrive` for the last waypoint.
|
||||
///
|
||||
/// Returns (steering_force, updated_current_waypoint_index).
|
||||
pub fn follow_path(
|
||||
agent: &SteeringAgent,
|
||||
path: &[Vec3],
|
||||
current_waypoint: usize,
|
||||
waypoint_radius: f32,
|
||||
) -> (Vec3, usize) {
|
||||
if path.is_empty() {
|
||||
return (Vec3::ZERO, 0);
|
||||
}
|
||||
|
||||
let clamped = current_waypoint.min(path.len() - 1);
|
||||
let waypoint = path[clamped];
|
||||
let dist = (waypoint - agent.position).length();
|
||||
|
||||
// If we are close enough and there is a next waypoint, advance
|
||||
if dist < waypoint_radius && clamped + 1 < path.len() {
|
||||
let next = clamped + 1;
|
||||
let force = if next == path.len() - 1 {
|
||||
arrive(agent, path[next], waypoint_radius)
|
||||
} else {
|
||||
seek(agent, path[next])
|
||||
};
|
||||
return (force, next);
|
||||
}
|
||||
|
||||
// Use arrive for the last waypoint, seek for all others
|
||||
let force = if clamped == path.len() - 1 {
|
||||
arrive(agent, waypoint, waypoint_radius)
|
||||
} else {
|
||||
seek(agent, waypoint)
|
||||
};
|
||||
|
||||
(force, clamped)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn agent_at(x: f32, z: f32) -> SteeringAgent {
|
||||
SteeringAgent::new(Vec3::new(x, 0.0, z), 5.0, 10.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seek_direction() {
|
||||
let agent = agent_at(0.0, 0.0);
|
||||
let target = Vec3::new(1.0, 0.0, 0.0);
|
||||
let force = seek(&agent, target);
|
||||
// Force should be in the +X direction
|
||||
assert!(force.x > 0.0, "seek force x should be positive");
|
||||
assert!(force.z.abs() < 1e-4, "seek force z should be ~0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_flee_direction() {
|
||||
let agent = agent_at(0.0, 0.0);
|
||||
let threat = Vec3::new(1.0, 0.0, 0.0);
|
||||
let force = flee(&agent, threat);
|
||||
// Force should be in the -X direction (away from threat)
|
||||
assert!(force.x < 0.0, "flee force x should be negative");
|
||||
assert!(force.z.abs() < 1e-4, "flee force z should be ~0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arrive_deceleration() {
|
||||
let mut agent = agent_at(0.0, 0.0);
|
||||
// Give agent some velocity toward target
|
||||
agent.velocity = Vec3::new(5.0, 0.0, 0.0);
|
||||
let target = Vec3::new(2.0, 0.0, 0.0); // within slow_radius=5
|
||||
let slow_radius = 5.0;
|
||||
let force = arrive(&agent, target, slow_radius);
|
||||
// The desired speed is reduced (dist/slow_radius * max_speed = 2/5 * 5 = 2)
|
||||
// desired velocity = (1,0,0)*2, current velocity = (5,0,0)
|
||||
// steering = (2-5, 0, 0) = (-3, 0, 0) — a braking force
|
||||
assert!(force.x < 0.0, "arrive should produce braking force when inside slow_radius");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arrive_at_target() {
|
||||
let agent = agent_at(0.0, 0.0);
|
||||
let target = Vec3::new(0.0, 0.0, 0.0); // same position
|
||||
let force = arrive(&agent, target, 1.0);
|
||||
// At target, force should be zero
|
||||
assert!(force.length() < 1e-4, "force at target should be zero");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_follow_path_advance() {
|
||||
// Path: (0,0,0) -> (5,0,0) -> (10,0,0)
|
||||
let path = vec![
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(5.0, 0.0, 0.0),
|
||||
Vec3::new(10.0, 0.0, 0.0),
|
||||
];
|
||||
// Agent is very close to waypoint 1
|
||||
let agent = SteeringAgent::new(Vec3::new(4.9, 0.0, 0.0), 5.0, 10.0);
|
||||
let waypoint_radius = 0.5;
|
||||
let (_, next_wp) = follow_path(&agent, &path, 1, waypoint_radius);
|
||||
// Should advance to waypoint 2
|
||||
assert_eq!(next_wp, 2, "should advance to waypoint 2 when close to waypoint 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_follow_path_last_arrives() {
|
||||
// Path: single-segment, agent near last waypoint
|
||||
let path = vec![
|
||||
Vec3::new(0.0, 0.0, 0.0),
|
||||
Vec3::new(10.0, 0.0, 0.0),
|
||||
];
|
||||
let agent = SteeringAgent::new(Vec3::new(9.0, 0.0, 0.0), 5.0, 10.0);
|
||||
let waypoint_radius = 2.0;
|
||||
let (force, wp_idx) = follow_path(&agent, &path, 1, waypoint_radius);
|
||||
// Still at waypoint 1 (the last one); force should be arrive (decelerating)
|
||||
assert_eq!(wp_idx, 1);
|
||||
// arrive should produce a deceleration toward (10,0,0)
|
||||
// agent has zero velocity, dist=1, slow_radius=2 → desired_speed=1/2*5=2.5 in +X
|
||||
// steering = desired - velocity = (2.5,0,0) - (0,0,0) = (2.5,0,0)
|
||||
assert!(force.x > 0.0, "arrive force should be toward last waypoint");
|
||||
}
|
||||
}
|
||||
7
crates/voltex_audio/Cargo.toml
Normal file
7
crates/voltex_audio/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "voltex_audio"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
voltex_math.workspace = true
|
||||
55
crates/voltex_audio/src/audio_clip.rs
Normal file
55
crates/voltex_audio/src/audio_clip.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
/// A loaded audio clip stored as normalized f32 PCM samples.
|
||||
#[derive(Debug)]
|
||||
pub struct AudioClip {
|
||||
/// Interleaved PCM samples in the range [-1.0, 1.0].
|
||||
pub samples: Vec<f32>,
|
||||
/// Number of samples per second (e.g. 44100).
|
||||
pub sample_rate: u32,
|
||||
/// Number of audio channels (1 = mono, 2 = stereo).
|
||||
pub channels: u16,
|
||||
}
|
||||
|
||||
impl AudioClip {
|
||||
/// Create a new AudioClip from raw f32 samples.
|
||||
pub fn new(samples: Vec<f32>, sample_rate: u32, channels: u16) -> Self {
|
||||
Self {
|
||||
samples,
|
||||
sample_rate,
|
||||
channels,
|
||||
}
|
||||
}
|
||||
|
||||
/// Total number of multi-channel frames.
|
||||
/// A frame contains one sample per channel.
|
||||
pub fn frame_count(&self) -> usize {
|
||||
self.samples.len() / self.channels as usize
|
||||
}
|
||||
|
||||
/// Duration in seconds.
|
||||
pub fn duration(&self) -> f32 {
|
||||
self.frame_count() as f32 / self.sample_rate as f32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mono_clip() {
|
||||
// 44100 Hz, 1 channel, 1 second of silence
|
||||
let samples = vec![0.0f32; 44100];
|
||||
let clip = AudioClip::new(samples, 44100, 1);
|
||||
assert_eq!(clip.frame_count(), 44100);
|
||||
assert!((clip.duration() - 1.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stereo_clip() {
|
||||
// 44100 Hz, 2 channels, 0.5 seconds
|
||||
let samples = vec![0.0f32; 44100]; // 44100 interleaved samples = 22050 frames
|
||||
let clip = AudioClip::new(samples, 44100, 2);
|
||||
assert_eq!(clip.frame_count(), 22050);
|
||||
assert!((clip.duration() - 0.5).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
303
crates/voltex_audio/src/audio_system.rs
Normal file
303
crates/voltex_audio/src/audio_system.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
//! AudioSystem: high-level audio playback management with a dedicated audio thread.
|
||||
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::thread;
|
||||
|
||||
use crate::{AudioClip, PlayingSound, mix_sounds};
|
||||
use crate::spatial::{Listener, SpatialParams};
|
||||
use crate::mix_group::{MixGroup, MixerState};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioCommand
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Commands sent to the audio thread.
|
||||
pub enum AudioCommand {
|
||||
/// Start playing a clip.
|
||||
Play {
|
||||
clip_index: usize,
|
||||
volume: f32,
|
||||
looping: bool,
|
||||
},
|
||||
/// Start playing a clip with 3D spatial parameters.
|
||||
Play3d {
|
||||
clip_index: usize,
|
||||
volume: f32,
|
||||
looping: bool,
|
||||
spatial: SpatialParams,
|
||||
},
|
||||
/// Update the listener position and orientation.
|
||||
SetListener {
|
||||
position: voltex_math::Vec3,
|
||||
forward: voltex_math::Vec3,
|
||||
right: voltex_math::Vec3,
|
||||
},
|
||||
/// Stop all instances of a clip.
|
||||
Stop { clip_index: usize },
|
||||
/// Change volume of all playing instances of a clip.
|
||||
SetVolume { clip_index: usize, volume: f32 },
|
||||
/// Stop all currently playing sounds.
|
||||
StopAll,
|
||||
/// Set volume for a mix group immediately.
|
||||
SetGroupVolume { group: MixGroup, volume: f32 },
|
||||
/// Fade a mix group to a target volume over a duration (seconds).
|
||||
FadeGroup { group: MixGroup, target: f32, duration: f32 },
|
||||
/// Shut down the audio thread.
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AudioSystem
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Manages audio playback via a dedicated OS-level thread.
|
||||
pub struct AudioSystem {
|
||||
sender: mpsc::Sender<AudioCommand>,
|
||||
}
|
||||
|
||||
impl AudioSystem {
|
||||
/// Create an AudioSystem with the given pre-loaded clips.
|
||||
///
|
||||
/// Spawns a background audio thread that owns the clips and drives the
|
||||
/// WASAPI device (on Windows) or runs in silent mode (other platforms).
|
||||
pub fn new(clips: Vec<AudioClip>) -> Result<Self, String> {
|
||||
let clips = Arc::new(clips);
|
||||
let (tx, rx) = mpsc::channel::<AudioCommand>();
|
||||
|
||||
let clips_arc = Arc::clone(&clips);
|
||||
thread::Builder::new()
|
||||
.name("voltex_audio_thread".to_string())
|
||||
.spawn(move || {
|
||||
audio_thread(rx, clips_arc);
|
||||
})
|
||||
.map_err(|e| format!("Failed to spawn audio thread: {}", e))?;
|
||||
|
||||
Ok(AudioSystem { sender: tx })
|
||||
}
|
||||
|
||||
/// Start playing a clip at the given volume and looping setting.
|
||||
pub fn play(&self, clip_index: usize, volume: f32, looping: bool) {
|
||||
let _ = self.sender.send(AudioCommand::Play {
|
||||
clip_index,
|
||||
volume,
|
||||
looping,
|
||||
});
|
||||
}
|
||||
|
||||
/// Start playing a clip with 3D spatial audio parameters.
|
||||
pub fn play_3d(&self, clip_index: usize, volume: f32, looping: bool, spatial: SpatialParams) {
|
||||
let _ = self.sender.send(AudioCommand::Play3d { clip_index, volume, looping, spatial });
|
||||
}
|
||||
|
||||
/// Update the listener position and orientation for 3D audio.
|
||||
pub fn set_listener(&self, position: voltex_math::Vec3, forward: voltex_math::Vec3, right: voltex_math::Vec3) {
|
||||
let _ = self.sender.send(AudioCommand::SetListener { position, forward, right });
|
||||
}
|
||||
|
||||
/// Stop all instances of the specified clip.
|
||||
pub fn stop(&self, clip_index: usize) {
|
||||
let _ = self.sender.send(AudioCommand::Stop { clip_index });
|
||||
}
|
||||
|
||||
/// Set the volume for all playing instances of a clip.
|
||||
pub fn set_volume(&self, clip_index: usize, volume: f32) {
|
||||
let _ = self.sender.send(AudioCommand::SetVolume { clip_index, volume });
|
||||
}
|
||||
|
||||
/// Stop all currently playing sounds.
|
||||
pub fn stop_all(&self) {
|
||||
let _ = self.sender.send(AudioCommand::StopAll);
|
||||
}
|
||||
|
||||
/// Set volume for a mix group immediately.
|
||||
pub fn set_group_volume(&self, group: MixGroup, volume: f32) {
|
||||
let _ = self.sender.send(AudioCommand::SetGroupVolume { group, volume });
|
||||
}
|
||||
|
||||
/// Fade a mix group to a target volume over `duration` seconds.
|
||||
pub fn fade_group(&self, group: MixGroup, target: f32, duration: f32) {
|
||||
let _ = self.sender.send(AudioCommand::FadeGroup { group, target, duration });
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AudioSystem {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.sender.send(AudioCommand::Shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Audio thread implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Main audio thread function.
|
||||
///
|
||||
/// On Windows, initializes WasapiDevice and drives playback.
|
||||
/// On other platforms, runs in a silent "null" mode (processes commands only).
|
||||
fn audio_thread(rx: mpsc::Receiver<AudioCommand>, clips: Arc<Vec<AudioClip>>) {
|
||||
#[cfg(target_os = "windows")]
|
||||
audio_thread_windows(rx, clips);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
audio_thread_null(rx, clips);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Windows implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn audio_thread_windows(rx: mpsc::Receiver<AudioCommand>, clips: Arc<Vec<AudioClip>>) {
|
||||
use crate::wasapi::WasapiDevice;
|
||||
|
||||
let device = match WasapiDevice::new() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
eprintln!("[voltex_audio] WASAPI init failed: {}. Running in silent mode.", e);
|
||||
audio_thread_null(rx, clips);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let device_sample_rate = device.sample_rate;
|
||||
let device_channels = device.channels;
|
||||
let buffer_frames = device.buffer_frames() as usize;
|
||||
|
||||
let mut playing: Vec<PlayingSound> = Vec::new();
|
||||
let mut output: Vec<f32> = Vec::new();
|
||||
let mut listener = Listener::default();
|
||||
let mut mixer = MixerState::new();
|
||||
|
||||
loop {
|
||||
// Advance mixer fades (~5 ms per iteration)
|
||||
mixer.tick(0.005);
|
||||
|
||||
// Process all pending commands (non-blocking)
|
||||
loop {
|
||||
match rx.try_recv() {
|
||||
Ok(cmd) => {
|
||||
match cmd {
|
||||
AudioCommand::Play { clip_index, volume, looping } => {
|
||||
playing.push(PlayingSound::new(clip_index, volume, looping));
|
||||
}
|
||||
AudioCommand::Play3d { clip_index, volume, looping, spatial } => {
|
||||
playing.push(PlayingSound::new_3d(clip_index, volume, looping, spatial));
|
||||
}
|
||||
AudioCommand::SetListener { position, forward, right } => {
|
||||
listener = Listener { position, forward, right };
|
||||
}
|
||||
AudioCommand::Stop { clip_index } => {
|
||||
playing.retain(|s| s.clip_index != clip_index);
|
||||
}
|
||||
AudioCommand::SetVolume { clip_index, volume } => {
|
||||
for s in playing.iter_mut() {
|
||||
if s.clip_index == clip_index {
|
||||
s.volume = volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
AudioCommand::StopAll => {
|
||||
playing.clear();
|
||||
}
|
||||
AudioCommand::SetGroupVolume { group, volume } => {
|
||||
mixer.set_volume(group, volume);
|
||||
}
|
||||
AudioCommand::FadeGroup { group, target, duration } => {
|
||||
mixer.fade(group, target, duration);
|
||||
}
|
||||
AudioCommand::Shutdown => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(mpsc::TryRecvError::Empty) => break,
|
||||
Err(mpsc::TryRecvError::Disconnected) => return,
|
||||
}
|
||||
}
|
||||
|
||||
// Mix and write audio
|
||||
if !playing.is_empty() || !output.is_empty() {
|
||||
mix_sounds(
|
||||
&mut output,
|
||||
&mut playing,
|
||||
&clips,
|
||||
device_sample_rate,
|
||||
device_channels,
|
||||
buffer_frames,
|
||||
&listener,
|
||||
&mixer,
|
||||
);
|
||||
|
||||
if let Err(e) = device.write_samples(&output) {
|
||||
eprintln!("[voltex_audio] write_samples error: {}", e);
|
||||
}
|
||||
} else {
|
||||
// Write silence to keep the device happy
|
||||
let silence_len = buffer_frames * device_channels as usize;
|
||||
let silence = vec![0.0f32; silence_len];
|
||||
let _ = device.write_samples(&silence);
|
||||
}
|
||||
|
||||
// Sleep ~5 ms between iterations
|
||||
thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Null (non-Windows) implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn audio_thread_null(rx: mpsc::Receiver<AudioCommand>, clips: Arc<Vec<AudioClip>>) {
|
||||
audio_thread_null_impl(rx, clips);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn audio_thread_null(rx: mpsc::Receiver<AudioCommand>, _clips: Arc<Vec<AudioClip>>) {
|
||||
audio_thread_null_impl(rx, _clips);
|
||||
}
|
||||
|
||||
fn audio_thread_null_impl(rx: mpsc::Receiver<AudioCommand>, _clips: Arc<Vec<AudioClip>>) {
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(AudioCommand::Shutdown) | Err(_) => return,
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::AudioClip;
|
||||
|
||||
fn silence_clip() -> AudioClip {
|
||||
AudioClip::new(vec![0.0f32; 44100], 44100, 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_and_drop() {
|
||||
// AudioSystem should be created and dropped without panicking.
|
||||
// The audio thread is spawned but will run in null mode in CI.
|
||||
let _sys = AudioSystem::new(vec![silence_clip()])
|
||||
.expect("AudioSystem::new failed");
|
||||
// Drop happens here, Shutdown command is sent automatically.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_commands() {
|
||||
let sys = AudioSystem::new(vec![silence_clip(), silence_clip()])
|
||||
.expect("AudioSystem::new failed");
|
||||
|
||||
sys.play(0, 1.0, false);
|
||||
sys.play(1, 0.5, true);
|
||||
sys.set_volume(0, 0.8);
|
||||
sys.stop(0);
|
||||
sys.stop_all();
|
||||
// All sends must succeed without panic.
|
||||
}
|
||||
}
|
||||
15
crates/voltex_audio/src/lib.rs
Normal file
15
crates/voltex_audio/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub mod audio_clip;
|
||||
pub mod wav;
|
||||
pub mod mixing;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod wasapi;
|
||||
pub mod audio_system;
|
||||
pub mod spatial;
|
||||
pub mod mix_group;
|
||||
|
||||
pub use audio_clip::AudioClip;
|
||||
pub use wav::{parse_wav, generate_wav_bytes};
|
||||
pub use mixing::{PlayingSound, mix_sounds};
|
||||
pub use audio_system::AudioSystem;
|
||||
pub use spatial::{Listener, SpatialParams, distance_attenuation, stereo_pan, compute_spatial_gains};
|
||||
pub use mix_group::{MixGroup, MixerState};
|
||||
214
crates/voltex_audio/src/mix_group.rs
Normal file
214
crates/voltex_audio/src/mix_group.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
/// Mixer group identifiers.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MixGroup {
|
||||
Master = 0,
|
||||
Bgm = 1,
|
||||
Sfx = 2,
|
||||
Voice = 3,
|
||||
}
|
||||
|
||||
/// Per-group volume state with optional fade.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GroupState {
|
||||
pub volume: f32,
|
||||
pub fade_target: f32,
|
||||
pub fade_speed: f32,
|
||||
}
|
||||
|
||||
impl GroupState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
volume: 1.0,
|
||||
fade_target: 1.0,
|
||||
fade_speed: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance fade by `dt` seconds.
|
||||
pub fn tick(&mut self, dt: f32) {
|
||||
if self.fade_speed == 0.0 {
|
||||
return;
|
||||
}
|
||||
let delta = self.fade_speed * dt;
|
||||
if self.volume < self.fade_target {
|
||||
self.volume = (self.volume + delta).min(self.fade_target);
|
||||
} else {
|
||||
self.volume = (self.volume - delta).max(self.fade_target);
|
||||
}
|
||||
if (self.volume - self.fade_target).abs() < f32::EPSILON {
|
||||
self.volume = self.fade_target;
|
||||
self.fade_speed = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GroupState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Global mixer state holding one `GroupState` per `MixGroup`.
|
||||
pub struct MixerState {
|
||||
pub groups: [GroupState; 4],
|
||||
}
|
||||
|
||||
impl MixerState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
groups: [
|
||||
GroupState::new(),
|
||||
GroupState::new(),
|
||||
GroupState::new(),
|
||||
GroupState::new(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn idx(group: MixGroup) -> usize {
|
||||
group as usize
|
||||
}
|
||||
|
||||
/// Immediately set volume for `group`, cancelling any active fade.
|
||||
pub fn set_volume(&mut self, group: MixGroup, volume: f32) {
|
||||
let v = volume.clamp(0.0, 1.0);
|
||||
let g = &mut self.groups[Self::idx(group)];
|
||||
g.volume = v;
|
||||
g.fade_target = v;
|
||||
g.fade_speed = 0.0;
|
||||
}
|
||||
|
||||
/// Begin a fade from the current volume to `target` over `duration` seconds.
|
||||
/// If `duration` <= 0, set volume instantly.
|
||||
pub fn fade(&mut self, group: MixGroup, target: f32, duration: f32) {
|
||||
let target = target.clamp(0.0, 1.0);
|
||||
let g = &mut self.groups[Self::idx(group)];
|
||||
if duration <= 0.0 {
|
||||
g.volume = target;
|
||||
g.fade_target = target;
|
||||
g.fade_speed = 0.0;
|
||||
} else {
|
||||
let speed = (target - g.volume).abs() / duration;
|
||||
g.fade_target = target;
|
||||
g.fade_speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance all groups by `dt` seconds.
|
||||
pub fn tick(&mut self, dt: f32) {
|
||||
for g in &mut self.groups {
|
||||
g.tick(dt);
|
||||
}
|
||||
}
|
||||
|
||||
/// Current volume of `group`.
|
||||
pub fn volume(&self, group: MixGroup) -> f32 {
|
||||
self.groups[Self::idx(group)].volume
|
||||
}
|
||||
|
||||
/// Effective volume: group volume multiplied by master volume.
|
||||
/// For `Master`, returns its own volume only.
|
||||
pub fn effective_volume(&self, group: MixGroup) -> f32 {
|
||||
let master = self.groups[Self::idx(MixGroup::Master)].volume;
|
||||
if group == MixGroup::Master {
|
||||
master
|
||||
} else {
|
||||
self.groups[Self::idx(group)].volume * master
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MixerState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn group_state_tick_fade() {
|
||||
let mut g = GroupState::new();
|
||||
g.volume = 1.0;
|
||||
g.fade_target = 0.0;
|
||||
g.fade_speed = 2.0;
|
||||
|
||||
g.tick(0.25); // 0.25 * 2.0 = 0.5 moved
|
||||
assert!((g.volume - 0.5).abs() < 1e-5, "expected ~0.5, got {}", g.volume);
|
||||
|
||||
g.tick(0.25); // another 0.5 → reaches 0.0
|
||||
assert!((g.volume - 0.0).abs() < 1e-5, "expected 0.0, got {}", g.volume);
|
||||
assert_eq!(g.fade_speed, 0.0, "fade_speed should be 0 after reaching target");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_state_tick_no_overshoot() {
|
||||
let mut g = GroupState::new();
|
||||
g.volume = 1.0;
|
||||
g.fade_target = 0.0;
|
||||
g.fade_speed = 100.0; // very fast
|
||||
|
||||
g.tick(1.0);
|
||||
assert_eq!(g.volume, 0.0, "should not overshoot below 0");
|
||||
assert_eq!(g.fade_speed, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixer_set_volume() {
|
||||
let mut m = MixerState::new();
|
||||
// Start a fade, then override with set_volume
|
||||
m.fade(MixGroup::Sfx, 0.0, 5.0);
|
||||
m.set_volume(MixGroup::Sfx, 0.3);
|
||||
assert!((m.volume(MixGroup::Sfx) - 0.3).abs() < 1e-5);
|
||||
assert_eq!(m.groups[MixGroup::Sfx as usize].fade_speed, 0.0, "fade cancelled");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixer_fade() {
|
||||
let mut m = MixerState::new();
|
||||
m.fade(MixGroup::Sfx, 0.0, 1.0); // 1→0 over 1s, speed=1.0
|
||||
|
||||
m.tick(0.5);
|
||||
let v = m.volume(MixGroup::Sfx);
|
||||
assert!((v - 0.5).abs() < 1e-5, "at 0.5s expected ~0.5, got {}", v);
|
||||
|
||||
m.tick(0.5);
|
||||
let v = m.volume(MixGroup::Sfx);
|
||||
assert!((v - 0.0).abs() < 1e-5, "at 1.0s expected 0.0, got {}", v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_volume() {
|
||||
let mut m = MixerState::new();
|
||||
m.set_volume(MixGroup::Master, 0.5);
|
||||
m.set_volume(MixGroup::Sfx, 0.8);
|
||||
let ev = m.effective_volume(MixGroup::Sfx);
|
||||
assert!((ev - 0.4).abs() < 1e-5, "expected 0.4, got {}", ev);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn master_zero_mutes_all() {
|
||||
let mut m = MixerState::new();
|
||||
m.set_volume(MixGroup::Master, 0.0);
|
||||
for group in [MixGroup::Bgm, MixGroup::Sfx, MixGroup::Voice] {
|
||||
assert_eq!(m.effective_volume(group), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fade_up() {
|
||||
let mut m = MixerState::new();
|
||||
m.set_volume(MixGroup::Bgm, 0.0);
|
||||
m.fade(MixGroup::Bgm, 1.0, 2.0); // 0→1 over 2s, speed=0.5
|
||||
|
||||
m.tick(1.0);
|
||||
let v = m.volume(MixGroup::Bgm);
|
||||
assert!((v - 0.5).abs() < 1e-5, "at 1s expected ~0.5, got {}", v);
|
||||
|
||||
m.tick(1.0);
|
||||
let v = m.volume(MixGroup::Bgm);
|
||||
assert!((v - 1.0).abs() < 1e-5, "at 2s expected 1.0, got {}", v);
|
||||
}
|
||||
}
|
||||
373
crates/voltex_audio/src/mixing.rs
Normal file
373
crates/voltex_audio/src/mixing.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
use crate::AudioClip;
|
||||
use crate::spatial::{Listener, SpatialParams, compute_spatial_gains};
|
||||
use crate::mix_group::{MixGroup, MixerState};
|
||||
|
||||
/// Represents a sound currently being played back.
|
||||
pub struct PlayingSound {
|
||||
/// Index into the clips slice passed to `mix_sounds`.
|
||||
pub clip_index: usize,
|
||||
/// Current playback position in frames (not samples).
|
||||
pub position: usize,
|
||||
/// Linear volume multiplier [0.0 = silent, 1.0 = full].
|
||||
pub volume: f32,
|
||||
/// Whether the sound loops when it reaches the end.
|
||||
pub looping: bool,
|
||||
/// Optional 3D spatial parameters. None = 2D (no spatialization).
|
||||
pub spatial: Option<SpatialParams>,
|
||||
/// Mix group this sound belongs to.
|
||||
pub group: MixGroup,
|
||||
}
|
||||
|
||||
impl PlayingSound {
|
||||
pub fn new(clip_index: usize, volume: f32, looping: bool) -> Self {
|
||||
Self {
|
||||
clip_index,
|
||||
position: 0,
|
||||
volume,
|
||||
looping,
|
||||
spatial: None,
|
||||
group: MixGroup::Sfx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_3d(clip_index: usize, volume: f32, looping: bool, spatial: SpatialParams) -> Self {
|
||||
Self { clip_index, position: 0, volume, looping, spatial: Some(spatial), group: MixGroup::Sfx }
|
||||
}
|
||||
}
|
||||
|
||||
/// Mix all active sounds in `playing` into `output`.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `output`: interleaved output buffer (len = frames * device_channels).
|
||||
/// - `playing`: mutable list of active sounds; finished non-looping sounds are removed.
|
||||
/// - `clips`: the audio clip assets.
|
||||
/// - `device_sample_rate`: output device sample rate.
|
||||
/// - `device_channels`: output device channel count (1 or 2).
|
||||
/// - `frames`: number of output frames to fill.
|
||||
/// - `listener`: the listener for 3D spatialization.
|
||||
/// - `mixer`: the global mixer state providing per-group and master volumes.
|
||||
pub fn mix_sounds(
|
||||
output: &mut Vec<f32>,
|
||||
playing: &mut Vec<PlayingSound>,
|
||||
clips: &[AudioClip],
|
||||
device_sample_rate: u32,
|
||||
device_channels: u16,
|
||||
frames: usize,
|
||||
listener: &Listener,
|
||||
mixer: &MixerState,
|
||||
) {
|
||||
// Ensure output buffer is sized correctly and zeroed.
|
||||
let output_len = frames * device_channels as usize;
|
||||
output.clear();
|
||||
output.resize(output_len, 0.0);
|
||||
|
||||
let mut finished = Vec::new();
|
||||
|
||||
for (sound_idx, sound) in playing.iter_mut().enumerate() {
|
||||
let clip = &clips[sound.clip_index];
|
||||
|
||||
// Apply group and master volume multiplier.
|
||||
let group_vol = mixer.effective_volume(sound.group);
|
||||
let base_volume = sound.volume * group_vol;
|
||||
|
||||
// Compute per-channel effective volumes based on spatial params.
|
||||
let (vol_left, vol_right) = if let Some(ref spatial) = sound.spatial {
|
||||
let (atten, left_gain, right_gain) = compute_spatial_gains(listener, spatial);
|
||||
(base_volume * atten * left_gain, base_volume * atten * right_gain)
|
||||
} else {
|
||||
(base_volume, base_volume)
|
||||
};
|
||||
|
||||
// Compute integer rate ratio for naive resampling.
|
||||
// For same-rate clips ratio == 1, so we read every frame.
|
||||
let rate_ratio = if device_sample_rate > 0 {
|
||||
clip.sample_rate as f64 / device_sample_rate as f64
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
for frame_out in 0..frames {
|
||||
// Map the output frame index to a clip frame index.
|
||||
let clip_frame = sound.position + (frame_out as f64 * rate_ratio) as usize;
|
||||
|
||||
if clip_frame >= clip.frame_count() {
|
||||
// Sound exhausted within this render block.
|
||||
if sound.looping {
|
||||
// We'll reset after the loop; for now just stop filling.
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Fetch sample(s) from clip frame.
|
||||
let out_base = frame_out * device_channels as usize;
|
||||
|
||||
if device_channels == 2 {
|
||||
let (left, right) = if clip.channels == 1 {
|
||||
// Mono -> stereo: apply per-channel volumes
|
||||
let raw = clip.samples[clip_frame];
|
||||
(raw * vol_left, raw * vol_right)
|
||||
} else {
|
||||
// Stereo clip
|
||||
let l = clip.samples[clip_frame * 2] * vol_left;
|
||||
let r = clip.samples[clip_frame * 2 + 1] * vol_right;
|
||||
(l, r)
|
||||
};
|
||||
output[out_base] += left;
|
||||
output[out_base + 1] += right;
|
||||
} else {
|
||||
// Mono output: average left and right volumes
|
||||
let vol_mono = (vol_left + vol_right) * 0.5;
|
||||
let s = if clip.channels == 1 {
|
||||
clip.samples[clip_frame] * vol_mono
|
||||
} else {
|
||||
// Mix stereo clip to mono
|
||||
(clip.samples[clip_frame * 2] + clip.samples[clip_frame * 2 + 1])
|
||||
* 0.5
|
||||
* vol_mono
|
||||
};
|
||||
output[out_base] += s;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance position by the number of clip frames consumed this block.
|
||||
let frames_consumed = (frames as f64 * rate_ratio) as usize;
|
||||
sound.position += frames_consumed;
|
||||
|
||||
if sound.position >= clip.frame_count() {
|
||||
if sound.looping {
|
||||
sound.position = 0;
|
||||
} else {
|
||||
finished.push(sound_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove finished sounds in reverse order to preserve indices.
|
||||
for &idx in finished.iter().rev() {
|
||||
playing.remove(idx);
|
||||
}
|
||||
|
||||
// Clamp output to [-1.0, 1.0].
|
||||
for sample in output.iter_mut() {
|
||||
*sample = sample.clamp(-1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::AudioClip;
|
||||
use crate::spatial::{Listener, SpatialParams};
|
||||
use crate::mix_group::{MixGroup, MixerState};
|
||||
use voltex_math::Vec3;
|
||||
|
||||
fn make_mono_clip(value: f32, frames: usize, sample_rate: u32) -> AudioClip {
|
||||
AudioClip::new(vec![value; frames], sample_rate, 1)
|
||||
}
|
||||
|
||||
fn make_stereo_clip(left: f32, right: f32, frames: usize, sample_rate: u32) -> AudioClip {
|
||||
let mut samples = Vec::with_capacity(frames * 2);
|
||||
for _ in 0..frames {
|
||||
samples.push(left);
|
||||
samples.push(right);
|
||||
}
|
||||
AudioClip::new(samples, sample_rate, 2)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_sound_volume() {
|
||||
let clips = vec![make_mono_clip(1.0, 100, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 0.5, false)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
// All output frames should be 0.5 (1.0 * 0.5 volume)
|
||||
assert_eq!(output.len(), 10);
|
||||
for &s in &output {
|
||||
assert!((s - 0.5).abs() < 1e-6, "expected 0.5, got {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_sounds_sum() {
|
||||
// Two clips each at 0.3; sum is 0.6 (below clamp threshold)
|
||||
let clips = vec![
|
||||
make_mono_clip(0.3, 100, 44100),
|
||||
make_mono_clip(0.3, 100, 44100),
|
||||
];
|
||||
let mut playing = vec![
|
||||
PlayingSound::new(0, 1.0, false),
|
||||
PlayingSound::new(1, 1.0, false),
|
||||
];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
assert_eq!(output.len(), 10);
|
||||
for &s in &output {
|
||||
assert!((s - 0.6).abs() < 1e-5, "expected 0.6, got {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipping() {
|
||||
// Two clips at 1.0 each; sum would be 2.0 but must be clamped to 1.0
|
||||
let clips = vec![
|
||||
make_mono_clip(1.0, 100, 44100),
|
||||
make_mono_clip(1.0, 100, 44100),
|
||||
];
|
||||
let mut playing = vec![
|
||||
PlayingSound::new(0, 1.0, false),
|
||||
PlayingSound::new(1, 1.0, false),
|
||||
];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
for &s in &output {
|
||||
assert!(s <= 1.0, "output {} exceeds 1.0 (clamp failed)", s);
|
||||
assert!(s >= -1.0, "output {} below -1.0 (clamp failed)", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_looping_removal() {
|
||||
// Clip is only 5 frames; request 20 frames; sound should be removed after
|
||||
let clips = vec![make_mono_clip(0.5, 5, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 1.0, false)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 20, &Listener::default(), &MixerState::new());
|
||||
|
||||
// Sound should have been removed
|
||||
assert!(playing.is_empty(), "non-looping sound was not removed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looping_continues() {
|
||||
// Clip is 5 frames; request 20 frames; looping sound should remain
|
||||
let clips = vec![make_mono_clip(0.5, 5, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 1.0, true)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 20, &Listener::default(), &MixerState::new());
|
||||
|
||||
// Sound should still be in the list
|
||||
assert_eq!(playing.len(), 1, "looping sound was incorrectly removed");
|
||||
// Position should have been reset to 0
|
||||
assert_eq!(playing[0].position, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mono_to_stereo() {
|
||||
// Mono clip mixed to stereo output: both channels should have same value
|
||||
let clips = vec![make_mono_clip(0.7, 100, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 1.0, false)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 2, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
// output length = 10 frames * 2 channels = 20
|
||||
assert_eq!(output.len(), 20);
|
||||
for i in 0..10 {
|
||||
let l = output[i * 2];
|
||||
let r = output[i * 2 + 1];
|
||||
assert!((l - 0.7).abs() < 1e-6, "left={}", l);
|
||||
assert!((r - 0.7).abs() < 1e-6, "right={}", r);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Spatial audio tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn spatial_2d_unchanged() {
|
||||
// spatial=None should produce the same output as before (no spatialization)
|
||||
let clips = vec![make_mono_clip(1.0, 100, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 0.5, false)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
assert_eq!(output.len(), 10);
|
||||
for &s in &output {
|
||||
assert!((s - 0.5).abs() < 1e-6, "expected 0.5, got {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spatial_far_away_silent() {
|
||||
// Emitter beyond max_distance → attenuation = 0.0 → ~0 output
|
||||
let clips = vec![make_mono_clip(1.0, 100, 44100)];
|
||||
let spatial = SpatialParams::new(Vec3::new(200.0, 0.0, 0.0), 1.0, 100.0);
|
||||
let mut playing = vec![PlayingSound::new_3d(0, 1.0, false, spatial)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 2, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
assert_eq!(output.len(), 20);
|
||||
for &s in &output {
|
||||
assert!(s.abs() < 1e-6, "expected ~0 for far-away emitter, got {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spatial_right_panning() {
|
||||
// Emitter on +X → right channel should be louder than left channel (stereo output)
|
||||
let clips = vec![make_mono_clip(1.0, 100, 44100)];
|
||||
// Emitter close enough to be audible (within min_distance → atten=1)
|
||||
let spatial = SpatialParams::new(Vec3::new(0.5, 0.0, 0.0), 1.0, 100.0);
|
||||
let mut playing = vec![PlayingSound::new_3d(0, 1.0, false, spatial)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 2, 10, &Listener::default(), &MixerState::new());
|
||||
|
||||
assert_eq!(output.len(), 20);
|
||||
// Check first frame: left=output[0], right=output[1]
|
||||
let left = output[0];
|
||||
let right = output[1];
|
||||
assert!(right > left, "expected right > left for +X emitter, got left={}, right={}", left, right);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_volume_applied() {
|
||||
// Sfx group at 0.5: output should be halved compared to default (1.0)
|
||||
let clips = vec![make_mono_clip(1.0, 100, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 1.0, false)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
let mut mixer = MixerState::new();
|
||||
mixer.set_volume(MixGroup::Sfx, 0.5);
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 10, &Listener::default(), &mixer);
|
||||
|
||||
assert_eq!(output.len(), 10);
|
||||
for &s in &output {
|
||||
assert!((s - 0.5).abs() < 1e-6, "expected 0.5 (halved by Sfx group vol), got {}", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn master_zero_mutes_output() {
|
||||
// Master volume at 0: all output must be silence
|
||||
let clips = vec![make_mono_clip(1.0, 100, 44100)];
|
||||
let mut playing = vec![PlayingSound::new(0, 1.0, false)];
|
||||
let mut output = Vec::new();
|
||||
|
||||
let mut mixer = MixerState::new();
|
||||
mixer.set_volume(MixGroup::Master, 0.0);
|
||||
|
||||
mix_sounds(&mut output, &mut playing, &clips, 44100, 1, 10, &Listener::default(), &mixer);
|
||||
|
||||
assert_eq!(output.len(), 10);
|
||||
for &s in &output {
|
||||
assert!(s.abs() < 1e-6, "expected silence with master=0, got {}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
162
crates/voltex_audio/src/spatial.rs
Normal file
162
crates/voltex_audio/src/spatial.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use voltex_math::Vec3;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
/// Represents the listener in 3D space.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Listener {
|
||||
pub position: Vec3,
|
||||
pub forward: Vec3,
|
||||
pub right: Vec3,
|
||||
}
|
||||
|
||||
impl Default for Listener {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: Vec3::ZERO,
|
||||
forward: Vec3::new(0.0, 0.0, -1.0),
|
||||
right: Vec3::X,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for a 3D audio emitter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpatialParams {
|
||||
pub position: Vec3,
|
||||
pub min_distance: f32,
|
||||
pub max_distance: f32,
|
||||
}
|
||||
|
||||
impl SpatialParams {
|
||||
/// Create with explicit parameters.
|
||||
pub fn new(position: Vec3, min_distance: f32, max_distance: f32) -> Self {
|
||||
Self { position, min_distance, max_distance }
|
||||
}
|
||||
|
||||
/// Create at a position with default distances (1.0 min, 100.0 max).
|
||||
pub fn at(position: Vec3) -> Self {
|
||||
Self { position, min_distance: 1.0, max_distance: 100.0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverse-distance attenuation model.
|
||||
/// Returns 1.0 when distance <= min_dist, 0.0 when distance >= max_dist,
|
||||
/// otherwise min_dist / distance clamped to [0, 1].
|
||||
pub fn distance_attenuation(distance: f32, min_dist: f32, max_dist: f32) -> f32 {
|
||||
if distance <= min_dist {
|
||||
1.0
|
||||
} else if distance >= max_dist {
|
||||
0.0
|
||||
} else {
|
||||
(min_dist / distance).clamp(0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equal-power stereo panning based on listener orientation and emitter position.
|
||||
/// Returns (left_gain, right_gain).
|
||||
/// If the emitter is at the same position as the listener, returns (1.0, 1.0).
|
||||
pub fn stereo_pan(listener: &Listener, emitter_pos: Vec3) -> (f32, f32) {
|
||||
let diff = emitter_pos - listener.position;
|
||||
let distance = diff.length();
|
||||
const EPSILON: f32 = 1e-6;
|
||||
if distance < EPSILON {
|
||||
return (1.0, 1.0);
|
||||
}
|
||||
let direction = diff * (1.0 / distance);
|
||||
let pan = direction.dot(listener.right).clamp(-1.0, 1.0);
|
||||
// Map pan [-1, 1] to angle [0, PI/2] via angle = pan * PI/4 + PI/4
|
||||
let angle = pan * (PI / 4.0) + (PI / 4.0);
|
||||
let left = angle.cos();
|
||||
let right = angle.sin();
|
||||
(left, right)
|
||||
}
|
||||
|
||||
/// Convenience function combining distance attenuation and stereo panning.
|
||||
/// Returns (attenuation, left_gain, right_gain).
|
||||
pub fn compute_spatial_gains(listener: &Listener, spatial: &SpatialParams) -> (f32, f32, f32) {
|
||||
let diff = spatial.position - listener.position;
|
||||
let distance = diff.length();
|
||||
let attenuation = distance_attenuation(distance, spatial.min_distance, spatial.max_distance);
|
||||
let (left, right) = stereo_pan(listener, spatial.position);
|
||||
(attenuation, left, right)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use voltex_math::Vec3;
|
||||
|
||||
#[test]
|
||||
fn attenuation_at_min() {
|
||||
// distance <= min_distance should return 1.0
|
||||
assert_eq!(distance_attenuation(0.5, 1.0, 10.0), 1.0);
|
||||
assert_eq!(distance_attenuation(1.0, 1.0, 10.0), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attenuation_at_max() {
|
||||
// distance >= max_distance should return 0.0
|
||||
assert_eq!(distance_attenuation(10.0, 1.0, 10.0), 0.0);
|
||||
assert_eq!(distance_attenuation(50.0, 1.0, 10.0), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attenuation_between() {
|
||||
// inverse: min_dist / distance
|
||||
let result = distance_attenuation(5.0, 1.0, 10.0);
|
||||
let expected = 1.0_f32 / 5.0_f32;
|
||||
assert!((result - expected).abs() < 1e-6, "expected {expected}, got {result}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pan_right() {
|
||||
// Emitter to the right (+X) should give right > left
|
||||
let listener = Listener::default();
|
||||
let emitter = Vec3::new(10.0, 0.0, 0.0);
|
||||
let (left, right) = stereo_pan(&listener, emitter);
|
||||
assert!(right > left, "expected right > left for +X emitter, got left={left}, right={right}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pan_left() {
|
||||
// Emitter to the left (-X) should give left > right
|
||||
let listener = Listener::default();
|
||||
let emitter = Vec3::new(-10.0, 0.0, 0.0);
|
||||
let (left, right) = stereo_pan(&listener, emitter);
|
||||
assert!(left > right, "expected left > right for -X emitter, got left={left}, right={right}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pan_front() {
|
||||
// Emitter directly in front (-Z) should give roughly equal gains
|
||||
let listener = Listener::default();
|
||||
let emitter = Vec3::new(0.0, 0.0, -10.0);
|
||||
let (left, right) = stereo_pan(&listener, emitter);
|
||||
assert!((left - right).abs() < 0.01, "expected roughly equal for front emitter, got left={left}, right={right}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pan_same_position() {
|
||||
// Same position as listener should return (1.0, 1.0)
|
||||
let listener = Listener::default();
|
||||
let emitter = Vec3::ZERO;
|
||||
let (left, right) = stereo_pan(&listener, emitter);
|
||||
assert_eq!((left, right), (1.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_spatial_gains_combines_both() {
|
||||
let listener = Listener::default();
|
||||
// Emitter at (5, 0, 0) with min=1, max=10
|
||||
let spatial = SpatialParams::new(Vec3::new(5.0, 0.0, 0.0), 1.0, 10.0);
|
||||
let (attenuation, left, right) = compute_spatial_gains(&listener, &spatial);
|
||||
|
||||
// Check attenuation: distance=5, min=1, max=10 → 1/5 = 0.2
|
||||
let expected_atten = 1.0_f32 / 5.0_f32;
|
||||
assert!((attenuation - expected_atten).abs() < 1e-6,
|
||||
"attenuation mismatch: expected {expected_atten}, got {attenuation}");
|
||||
|
||||
// Emitter is to the right, so right > left
|
||||
assert!(right > left, "expected right > left, got left={left}, right={right}");
|
||||
}
|
||||
}
|
||||
521
crates/voltex_audio/src/wasapi.rs
Normal file
521
crates/voltex_audio/src/wasapi.rs
Normal file
@@ -0,0 +1,521 @@
|
||||
#![allow(non_snake_case, non_camel_case_types, dead_code)]
|
||||
//! WASAPI FFI bindings for Windows audio output.
|
||||
//!
|
||||
//! This module is only compiled on Windows (`#[cfg(target_os = "windows")]`).
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Basic Windows types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub type HRESULT = i32;
|
||||
pub type ULONG = u32;
|
||||
pub type DWORD = u32;
|
||||
pub type WORD = u16;
|
||||
pub type BOOL = i32;
|
||||
pub type UINT = u32;
|
||||
pub type UINT32 = u32;
|
||||
pub type BYTE = u8;
|
||||
pub type REFERENCE_TIME = i64;
|
||||
pub type HANDLE = *mut c_void;
|
||||
pub type LPVOID = *mut c_void;
|
||||
pub type LPCWSTR = *const u16;
|
||||
|
||||
pub const S_OK: HRESULT = 0;
|
||||
pub const AUDCLNT_SHAREMODE_SHARED: u32 = 0;
|
||||
pub const AUDCLNT_STREAMFLAGS_RATEADJUST: u32 = 0x00100000;
|
||||
pub const CLSCTX_ALL: DWORD = 0x17;
|
||||
pub const COINIT_APARTMENTTHREADED: DWORD = 0x2;
|
||||
pub const DEVICE_STATE_ACTIVE: DWORD = 0x1;
|
||||
pub const eRender: u32 = 0;
|
||||
pub const eConsole: u32 = 0;
|
||||
pub const WAVE_FORMAT_PCM: WORD = 1;
|
||||
pub const WAVE_FORMAT_IEEE_FLOAT: WORD = 3;
|
||||
pub const WAVE_FORMAT_EXTENSIBLE: WORD = 0xFFFE;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GUID
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GUID {
|
||||
pub Data1: u32,
|
||||
pub Data2: u16,
|
||||
pub Data3: u16,
|
||||
pub Data4: [u8; 8],
|
||||
}
|
||||
|
||||
/// CLSID_MMDeviceEnumerator: {BCDE0395-E52F-467C-8E3D-C4579291692E}
|
||||
pub const CLSID_MMDeviceEnumerator: GUID = GUID {
|
||||
Data1: 0xBCDE0395,
|
||||
Data2: 0xE52F,
|
||||
Data3: 0x467C,
|
||||
Data4: [0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E],
|
||||
};
|
||||
|
||||
/// IID_IMMDeviceEnumerator: {A95664D2-9614-4F35-A746-DE8DB63617E6}
|
||||
pub const IID_IMMDeviceEnumerator: GUID = GUID {
|
||||
Data1: 0xA95664D2,
|
||||
Data2: 0x9614,
|
||||
Data3: 0x4F35,
|
||||
Data4: [0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6],
|
||||
};
|
||||
|
||||
/// IID_IAudioClient: {1CB9AD4C-DBFA-4c32-B178-C2F568A703B2}
|
||||
pub const IID_IAudioClient: GUID = GUID {
|
||||
Data1: 0x1CB9AD4C,
|
||||
Data2: 0xDBFA,
|
||||
Data3: 0x4C32,
|
||||
Data4: [0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2],
|
||||
};
|
||||
|
||||
/// IID_IAudioRenderClient: {F294ACFC-3146-4483-A7BF-ADDCA7C260E2}
|
||||
pub const IID_IAudioRenderClient: GUID = GUID {
|
||||
Data1: 0xF294ACFC,
|
||||
Data2: 0x3146,
|
||||
Data3: 0x4483,
|
||||
Data4: [0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2],
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WAVEFORMATEX
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WAVEFORMATEX {
|
||||
pub wFormatTag: WORD,
|
||||
pub nChannels: WORD,
|
||||
pub nSamplesPerSec: DWORD,
|
||||
pub nAvgBytesPerSec: DWORD,
|
||||
pub nBlockAlign: WORD,
|
||||
pub wBitsPerSample: WORD,
|
||||
pub cbSize: WORD,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// COM vtable structs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// IUnknown vtable (base for all COM interfaces)
|
||||
#[repr(C)]
|
||||
pub struct IUnknownVtbl {
|
||||
pub QueryInterface: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
riid: *const GUID,
|
||||
ppvObject: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub AddRef: unsafe extern "system" fn(this: *mut c_void) -> ULONG,
|
||||
pub Release: unsafe extern "system" fn(this: *mut c_void) -> ULONG,
|
||||
}
|
||||
|
||||
/// IMMDeviceEnumerator vtable
|
||||
#[repr(C)]
|
||||
pub struct IMMDeviceEnumeratorVtbl {
|
||||
pub base: IUnknownVtbl,
|
||||
pub EnumAudioEndpoints: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
dataFlow: u32,
|
||||
dwStateMask: DWORD,
|
||||
ppDevices: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub GetDefaultAudioEndpoint: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
dataFlow: u32,
|
||||
role: u32,
|
||||
ppEndpoint: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub GetDevice: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
pwstrId: LPCWSTR,
|
||||
ppDevice: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub RegisterEndpointNotificationCallback: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
pClient: *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub UnregisterEndpointNotificationCallback: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
pClient: *mut c_void,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
/// IMMDevice vtable
|
||||
#[repr(C)]
|
||||
pub struct IMMDeviceVtbl {
|
||||
pub base: IUnknownVtbl,
|
||||
pub Activate: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
iid: *const GUID,
|
||||
dwClsCtx: DWORD,
|
||||
pActivationParams: *mut c_void,
|
||||
ppInterface: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub OpenPropertyStore: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
stgmAccess: DWORD,
|
||||
ppProperties: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub GetId: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
ppstrId: *mut LPCWSTR,
|
||||
) -> HRESULT,
|
||||
pub GetState: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
pdwState: *mut DWORD,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
/// IAudioClient vtable
|
||||
#[repr(C)]
|
||||
pub struct IAudioClientVtbl {
|
||||
pub base: IUnknownVtbl,
|
||||
pub Initialize: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
ShareMode: u32,
|
||||
StreamFlags: DWORD,
|
||||
hnsBufferDuration: REFERENCE_TIME,
|
||||
hnsPeriodicity: REFERENCE_TIME,
|
||||
pFormat: *const WAVEFORMATEX,
|
||||
AudioSessionGuid: *const GUID,
|
||||
) -> HRESULT,
|
||||
pub GetBufferSize: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
pNumBufferFrames: *mut UINT32,
|
||||
) -> HRESULT,
|
||||
pub GetStreamLatency: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
phnsLatency: *mut REFERENCE_TIME,
|
||||
) -> HRESULT,
|
||||
pub GetCurrentPadding: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
pNumPaddingFrames: *mut UINT32,
|
||||
) -> HRESULT,
|
||||
pub IsFormatSupported: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
ShareMode: u32,
|
||||
pFormat: *const WAVEFORMATEX,
|
||||
ppClosestMatch: *mut *mut WAVEFORMATEX,
|
||||
) -> HRESULT,
|
||||
pub GetMixFormat: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
ppDeviceFormat: *mut *mut WAVEFORMATEX,
|
||||
) -> HRESULT,
|
||||
pub GetDevicePeriod: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
phnsDefaultDevicePeriod: *mut REFERENCE_TIME,
|
||||
phnsMinimumDevicePeriod: *mut REFERENCE_TIME,
|
||||
) -> HRESULT,
|
||||
pub Start: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
|
||||
pub Stop: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
|
||||
pub Reset: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
|
||||
pub SetEventHandle: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
eventHandle: HANDLE,
|
||||
) -> HRESULT,
|
||||
pub GetService: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
riid: *const GUID,
|
||||
ppv: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
/// IAudioRenderClient vtable
|
||||
#[repr(C)]
|
||||
pub struct IAudioRenderClientVtbl {
|
||||
pub base: IUnknownVtbl,
|
||||
pub GetBuffer: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
NumFramesRequested: UINT32,
|
||||
ppData: *mut *mut BYTE,
|
||||
) -> HRESULT,
|
||||
pub ReleaseBuffer: unsafe extern "system" fn(
|
||||
this: *mut c_void,
|
||||
NumFramesWritten: UINT32,
|
||||
dwFlags: DWORD,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// extern "system" functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[link(name = "ole32")]
|
||||
extern "system" {
|
||||
pub fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) -> HRESULT;
|
||||
pub fn CoUninitialize();
|
||||
pub fn CoCreateInstance(
|
||||
rclsid: *const GUID,
|
||||
pUnkOuter: *mut c_void,
|
||||
dwClsContext: DWORD,
|
||||
riid: *const GUID,
|
||||
ppv: *mut *mut c_void,
|
||||
) -> HRESULT;
|
||||
pub fn CoTaskMemFree(pv: LPVOID);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WasapiDevice
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub struct WasapiDevice {
|
||||
client: *mut c_void,
|
||||
render_client: *mut c_void,
|
||||
buffer_size: u32,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u16,
|
||||
bits_per_sample: u16,
|
||||
is_float: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for WasapiDevice {}
|
||||
|
||||
impl WasapiDevice {
|
||||
/// Initialize WASAPI: COM, default endpoint, IAudioClient, mix format,
|
||||
/// Initialize shared mode (50 ms buffer), GetBufferSize, GetService, Start.
|
||||
pub fn new() -> Result<Self, String> {
|
||||
unsafe {
|
||||
// 1. CoInitializeEx
|
||||
let hr = CoInitializeEx(std::ptr::null_mut(), COINIT_APARTMENTTHREADED);
|
||||
if hr < 0 {
|
||||
return Err(format!("CoInitializeEx failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
|
||||
// 2. CoCreateInstance -> IMMDeviceEnumerator
|
||||
let mut enumerator: *mut c_void = std::ptr::null_mut();
|
||||
let hr = CoCreateInstance(
|
||||
&CLSID_MMDeviceEnumerator,
|
||||
std::ptr::null_mut(),
|
||||
CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator,
|
||||
&mut enumerator,
|
||||
);
|
||||
if hr < 0 || enumerator.is_null() {
|
||||
CoUninitialize();
|
||||
return Err(format!("CoCreateInstance(MMDeviceEnumerator) failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
|
||||
// 3. GetDefaultAudioEndpoint -> IMMDevice
|
||||
let mut device: *mut c_void = std::ptr::null_mut();
|
||||
{
|
||||
let vtbl = *(enumerator as *mut *const IMMDeviceEnumeratorVtbl);
|
||||
let hr = ((*vtbl).GetDefaultAudioEndpoint)(enumerator, eRender, eConsole, &mut device);
|
||||
((*vtbl).base.Release)(enumerator);
|
||||
if hr < 0 || device.is_null() {
|
||||
CoUninitialize();
|
||||
return Err(format!("GetDefaultAudioEndpoint failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. IMMDevice::Activate -> IAudioClient
|
||||
let mut client: *mut c_void = std::ptr::null_mut();
|
||||
{
|
||||
let vtbl = *(device as *mut *const IMMDeviceVtbl);
|
||||
let hr = ((*vtbl).Activate)(
|
||||
device,
|
||||
&IID_IAudioClient,
|
||||
CLSCTX_ALL,
|
||||
std::ptr::null_mut(),
|
||||
&mut client,
|
||||
);
|
||||
((*vtbl).base.Release)(device);
|
||||
if hr < 0 || client.is_null() {
|
||||
CoUninitialize();
|
||||
return Err(format!("IMMDevice::Activate(IAudioClient) failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
// 5. GetMixFormat
|
||||
let mut mix_format_ptr: *mut WAVEFORMATEX = std::ptr::null_mut();
|
||||
{
|
||||
let vtbl = *(client as *mut *const IAudioClientVtbl);
|
||||
let hr = ((*vtbl).GetMixFormat)(client, &mut mix_format_ptr);
|
||||
if hr < 0 || mix_format_ptr.is_null() {
|
||||
((*vtbl).base.Release)(client);
|
||||
CoUninitialize();
|
||||
return Err(format!("GetMixFormat failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
let mix_format = *mix_format_ptr;
|
||||
let sample_rate = mix_format.nSamplesPerSec;
|
||||
let channels = mix_format.nChannels;
|
||||
let bits_per_sample = mix_format.wBitsPerSample;
|
||||
|
||||
// Determine float vs int
|
||||
let is_float = match mix_format.wFormatTag {
|
||||
WAVE_FORMAT_IEEE_FLOAT => true,
|
||||
WAVE_FORMAT_EXTENSIBLE => bits_per_sample == 32,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// 6. IAudioClient::Initialize (shared mode, 50 ms = 500_000 REFERENCE_TIME)
|
||||
const BUFFER_DURATION: REFERENCE_TIME = 500_000; // 50 ms in 100-ns units
|
||||
let hr = {
|
||||
let vtbl = *(client as *mut *const IAudioClientVtbl);
|
||||
((*vtbl).Initialize)(
|
||||
client,
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
0,
|
||||
BUFFER_DURATION,
|
||||
0,
|
||||
mix_format_ptr,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
CoTaskMemFree(mix_format_ptr as LPVOID);
|
||||
if hr < 0 {
|
||||
let vtbl = *(client as *mut *const IAudioClientVtbl);
|
||||
((*vtbl).base.Release)(client);
|
||||
CoUninitialize();
|
||||
return Err(format!("IAudioClient::Initialize failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
|
||||
// 7. GetBufferSize
|
||||
let mut buffer_size: UINT32 = 0;
|
||||
{
|
||||
let vtbl = *(client as *mut *const IAudioClientVtbl);
|
||||
let hr = ((*vtbl).GetBufferSize)(client, &mut buffer_size);
|
||||
if hr < 0 {
|
||||
((*vtbl).base.Release)(client);
|
||||
CoUninitialize();
|
||||
return Err(format!("GetBufferSize failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
// 8. GetService -> IAudioRenderClient
|
||||
let mut render_client: *mut c_void = std::ptr::null_mut();
|
||||
{
|
||||
let vtbl = *(client as *mut *const IAudioClientVtbl);
|
||||
let hr = ((*vtbl).GetService)(client, &IID_IAudioRenderClient, &mut render_client);
|
||||
if hr < 0 || render_client.is_null() {
|
||||
((*vtbl).base.Release)(client);
|
||||
CoUninitialize();
|
||||
return Err(format!("GetService(IAudioRenderClient) failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Start
|
||||
{
|
||||
let vtbl = *(client as *mut *const IAudioClientVtbl);
|
||||
let hr = ((*vtbl).Start)(client);
|
||||
if hr < 0 {
|
||||
let rc_vtbl = *(render_client as *mut *const IAudioRenderClientVtbl);
|
||||
((*rc_vtbl).base.Release)(render_client);
|
||||
((*vtbl).base.Release)(client);
|
||||
CoUninitialize();
|
||||
return Err(format!("IAudioClient::Start failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(WasapiDevice {
|
||||
client,
|
||||
render_client,
|
||||
buffer_size,
|
||||
sample_rate,
|
||||
channels,
|
||||
bits_per_sample,
|
||||
is_float,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Write f32 samples to the audio device.
|
||||
/// Returns the number of frames actually written.
|
||||
pub fn write_samples(&self, samples: &[f32]) -> Result<usize, String> {
|
||||
unsafe {
|
||||
// GetCurrentPadding
|
||||
let mut padding: UINT32 = 0;
|
||||
{
|
||||
let vtbl = *(self.client as *mut *const IAudioClientVtbl);
|
||||
let hr = ((*vtbl).GetCurrentPadding)(self.client, &mut padding);
|
||||
if hr < 0 {
|
||||
return Err(format!("GetCurrentPadding failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
let available_frames = if self.buffer_size > padding {
|
||||
self.buffer_size - padding
|
||||
} else {
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
let samples_per_frame = self.channels as usize;
|
||||
let input_frames = samples.len() / samples_per_frame;
|
||||
let frames_to_write = available_frames.min(input_frames as u32);
|
||||
|
||||
if frames_to_write == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// GetBuffer
|
||||
let mut data_ptr: *mut BYTE = std::ptr::null_mut();
|
||||
{
|
||||
let vtbl = *(self.render_client as *mut *const IAudioRenderClientVtbl);
|
||||
let hr = ((*vtbl).GetBuffer)(self.render_client, frames_to_write, &mut data_ptr);
|
||||
if hr < 0 || data_ptr.is_null() {
|
||||
return Err(format!("GetBuffer failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
// Write samples
|
||||
let total_samples = frames_to_write as usize * samples_per_frame;
|
||||
if self.is_float {
|
||||
// Write f32 directly
|
||||
let dst = std::slice::from_raw_parts_mut(data_ptr as *mut f32, total_samples);
|
||||
let src_len = total_samples.min(samples.len());
|
||||
dst[..src_len].copy_from_slice(&samples[..src_len]);
|
||||
if src_len < total_samples {
|
||||
for s in &mut dst[src_len..] {
|
||||
*s = 0.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Convert f32 to i16
|
||||
let dst = std::slice::from_raw_parts_mut(data_ptr as *mut i16, total_samples);
|
||||
for i in 0..total_samples {
|
||||
let val = if i < samples.len() { samples[i] } else { 0.0 };
|
||||
dst[i] = (val.clamp(-1.0, 1.0) * 32767.0) as i16;
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseBuffer
|
||||
{
|
||||
let vtbl = *(self.render_client as *mut *const IAudioRenderClientVtbl);
|
||||
let hr = ((*vtbl).ReleaseBuffer)(self.render_client, frames_to_write, 0);
|
||||
if hr < 0 {
|
||||
return Err(format!("ReleaseBuffer failed: 0x{:08X}", hr as u32));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(frames_to_write as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the allocated buffer size in frames.
|
||||
pub fn buffer_frames(&self) -> u32 {
|
||||
self.buffer_size
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WasapiDevice {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Stop the audio stream
|
||||
let client_vtbl = *(self.client as *mut *const IAudioClientVtbl);
|
||||
((*client_vtbl).Stop)(self.client);
|
||||
|
||||
// Release IAudioRenderClient
|
||||
let rc_vtbl = *(self.render_client as *mut *const IAudioRenderClientVtbl);
|
||||
((*rc_vtbl).base.Release)(self.render_client);
|
||||
|
||||
// Release IAudioClient
|
||||
((*client_vtbl).base.Release)(self.client);
|
||||
|
||||
// Uninitialize COM
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
240
crates/voltex_audio/src/wav.rs
Normal file
240
crates/voltex_audio/src/wav.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use crate::AudioClip;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper readers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn read_u16_le(data: &[u8], offset: usize) -> Result<u16, String> {
|
||||
if offset + 2 > data.len() {
|
||||
return Err(format!("read_u16_le: offset {} out of bounds (len={})", offset, data.len()));
|
||||
}
|
||||
Ok(u16::from_le_bytes([data[offset], data[offset + 1]]))
|
||||
}
|
||||
|
||||
fn read_u32_le(data: &[u8], offset: usize) -> Result<u32, String> {
|
||||
if offset + 4 > data.len() {
|
||||
return Err(format!("read_u32_le: offset {} out of bounds (len={})", offset, data.len()));
|
||||
}
|
||||
Ok(u32::from_le_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
]))
|
||||
}
|
||||
|
||||
fn read_i16_le(data: &[u8], offset: usize) -> Result<i16, String> {
|
||||
if offset + 2 > data.len() {
|
||||
return Err(format!("read_i16_le: offset {} out of bounds (len={})", offset, data.len()));
|
||||
}
|
||||
Ok(i16::from_le_bytes([data[offset], data[offset + 1]]))
|
||||
}
|
||||
|
||||
/// Search for a four-byte chunk ID starting after `start` and return its
|
||||
/// (data_offset, data_size) pair, where data_offset is the first byte of the
|
||||
/// chunk's payload.
|
||||
fn find_chunk(data: &[u8], id: &[u8; 4], start: usize) -> Option<(usize, u32)> {
|
||||
let mut pos = start;
|
||||
while pos + 8 <= data.len() {
|
||||
if &data[pos..pos + 4] == id {
|
||||
let size = u32::from_le_bytes([
|
||||
data[pos + 4],
|
||||
data[pos + 5],
|
||||
data[pos + 6],
|
||||
data[pos + 7],
|
||||
]);
|
||||
return Some((pos + 8, size));
|
||||
}
|
||||
// Skip this chunk: header (8 bytes) + size, padded to even
|
||||
let size = u32::from_le_bytes([
|
||||
data[pos + 4],
|
||||
data[pos + 5],
|
||||
data[pos + 6],
|
||||
data[pos + 7],
|
||||
]) as usize;
|
||||
let padded = size + (size & 1); // RIFF chunks are word-aligned
|
||||
pos += 8 + padded;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Parse a PCM 16-bit WAV file from raw bytes into an [`AudioClip`].
|
||||
pub fn parse_wav(data: &[u8]) -> Result<AudioClip, String> {
|
||||
// Minimum viable WAV: RIFF(4) + size(4) + WAVE(4) = 12 bytes
|
||||
if data.len() < 12 {
|
||||
return Err("WAV data too short".to_string());
|
||||
}
|
||||
|
||||
// RIFF header
|
||||
if &data[0..4] != b"RIFF" {
|
||||
return Err("Missing RIFF header".to_string());
|
||||
}
|
||||
if &data[8..12] != b"WAVE" {
|
||||
return Err("Missing WAVE format identifier".to_string());
|
||||
}
|
||||
|
||||
// --- fmt chunk (search from byte 12) ---
|
||||
let (fmt_offset, fmt_size) =
|
||||
find_chunk(data, b"fmt ", 12).ok_or("Missing fmt chunk")?;
|
||||
|
||||
if fmt_size < 16 {
|
||||
return Err(format!("fmt chunk too small: {}", fmt_size));
|
||||
}
|
||||
|
||||
let format_tag = read_u16_le(data, fmt_offset)?;
|
||||
if format_tag != 1 {
|
||||
return Err(format!("Unsupported WAV format tag: {} (only PCM=1 is supported)", format_tag));
|
||||
}
|
||||
|
||||
let channels = read_u16_le(data, fmt_offset + 2)?;
|
||||
if channels != 1 && channels != 2 {
|
||||
return Err(format!("Unsupported channel count: {}", channels));
|
||||
}
|
||||
|
||||
let sample_rate = read_u32_le(data, fmt_offset + 4)?;
|
||||
// byte_rate = fmt_offset + 8 (skip)
|
||||
// block_align = fmt_offset + 12 (skip)
|
||||
let bits_per_sample = read_u16_le(data, fmt_offset + 14)?;
|
||||
if bits_per_sample != 16 {
|
||||
return Err(format!("Unsupported bits per sample: {} (only 16-bit is supported)", bits_per_sample));
|
||||
}
|
||||
|
||||
// --- data chunk ---
|
||||
let (data_offset, data_size) =
|
||||
find_chunk(data, b"data", 12).ok_or("Missing data chunk")?;
|
||||
|
||||
let data_end = data_offset + data_size as usize;
|
||||
if data_end > data.len() {
|
||||
return Err("data chunk extends beyond end of file".to_string());
|
||||
}
|
||||
|
||||
// Each sample is 2 bytes (16-bit PCM).
|
||||
let sample_count = data_size as usize / 2;
|
||||
let mut samples = Vec::with_capacity(sample_count);
|
||||
|
||||
for i in 0..sample_count {
|
||||
let raw = read_i16_le(data, data_offset + i * 2)?;
|
||||
// Convert i16 [-32768, 32767] to f32 [-1.0, ~1.0]
|
||||
samples.push(raw as f32 / 32768.0);
|
||||
}
|
||||
|
||||
Ok(AudioClip::new(samples, sample_rate, channels))
|
||||
}
|
||||
|
||||
/// Generate a minimal PCM 16-bit mono WAV file from f32 samples.
|
||||
/// Used for round-trip testing.
|
||||
pub fn generate_wav_bytes(samples_f32: &[f32], sample_rate: u32) -> Vec<u8> {
|
||||
let channels: u16 = 1;
|
||||
let bits_per_sample: u16 = 16;
|
||||
let byte_rate = sample_rate * channels as u32 * bits_per_sample as u32 / 8;
|
||||
let block_align: u16 = channels * bits_per_sample / 8;
|
||||
let data_size = (samples_f32.len() * 2) as u32; // 2 bytes per i16 sample
|
||||
let riff_size = 4 + 8 + 16 + 8 + data_size; // "WAVE" + fmt chunk + data chunk
|
||||
|
||||
let mut out: Vec<u8> = Vec::with_capacity(12 + 8 + 16 + 8 + data_size as usize);
|
||||
|
||||
// RIFF header
|
||||
out.extend_from_slice(b"RIFF");
|
||||
out.extend_from_slice(&riff_size.to_le_bytes());
|
||||
out.extend_from_slice(b"WAVE");
|
||||
|
||||
// fmt chunk
|
||||
out.extend_from_slice(b"fmt ");
|
||||
out.extend_from_slice(&16u32.to_le_bytes()); // chunk size
|
||||
out.extend_from_slice(&1u16.to_le_bytes()); // PCM format tag
|
||||
out.extend_from_slice(&channels.to_le_bytes());
|
||||
out.extend_from_slice(&sample_rate.to_le_bytes());
|
||||
out.extend_from_slice(&byte_rate.to_le_bytes());
|
||||
out.extend_from_slice(&block_align.to_le_bytes());
|
||||
out.extend_from_slice(&bits_per_sample.to_le_bytes());
|
||||
|
||||
// data chunk
|
||||
out.extend_from_slice(b"data");
|
||||
out.extend_from_slice(&data_size.to_le_bytes());
|
||||
|
||||
for &s in samples_f32 {
|
||||
let clamped = s.clamp(-1.0, 1.0);
|
||||
let raw = (clamped * 32767.0) as i16;
|
||||
out.extend_from_slice(&raw.to_le_bytes());
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_valid_wav() {
|
||||
// Generate a 440 Hz sine wave at 44100 Hz, 0.1 s
|
||||
let sample_rate = 44100u32;
|
||||
let num_samples = 4410usize;
|
||||
let samples: Vec<f32> = (0..num_samples)
|
||||
.map(|i| (2.0 * std::f32::consts::PI * 440.0 * i as f32 / sample_rate as f32).sin())
|
||||
.collect();
|
||||
|
||||
let wav_bytes = generate_wav_bytes(&samples, sample_rate);
|
||||
let clip = parse_wav(&wav_bytes).expect("parse_wav failed");
|
||||
|
||||
assert_eq!(clip.sample_rate, sample_rate);
|
||||
assert_eq!(clip.channels, 1);
|
||||
assert_eq!(clip.frame_count(), num_samples);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_conversion_accuracy() {
|
||||
// A single-sample WAV: value = 16384 (half of i16 max positive)
|
||||
// Expected f32: 16384 / 32768 = 0.5
|
||||
let sample_rate = 44100u32;
|
||||
let samples_f32 = vec![0.5f32];
|
||||
let wav_bytes = generate_wav_bytes(&samples_f32, sample_rate);
|
||||
let clip = parse_wav(&wav_bytes).expect("parse_wav failed");
|
||||
|
||||
assert_eq!(clip.samples.len(), 1);
|
||||
// 0.5 -> i16(16383) -> f32(16383/32768) ≈ 0.49997
|
||||
assert!((clip.samples[0] - 0.5f32).abs() < 0.001, "got {}", clip.samples[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_riff() {
|
||||
let bad_data = b"BADH\x00\x00\x00\x00WAVE";
|
||||
let result = parse_wav(bad_data);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("RIFF"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_short() {
|
||||
let short_data = b"RIF";
|
||||
let result = parse_wav(short_data);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("too short"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let original: Vec<f32> = vec![0.0, 0.25, 0.5, -0.25, -0.5, 1.0, -1.0];
|
||||
let wav_bytes = generate_wav_bytes(&original, 44100);
|
||||
let clip = parse_wav(&wav_bytes).expect("roundtrip parse failed");
|
||||
|
||||
assert_eq!(clip.samples.len(), original.len());
|
||||
for (orig, decoded) in original.iter().zip(clip.samples.iter()) {
|
||||
// i16 quantization error < 0.001
|
||||
assert!(
|
||||
(orig - decoded).abs() < 0.001,
|
||||
"orig={} decoded={}",
|
||||
orig,
|
||||
decoded
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
crates/voltex_editor/Cargo.toml
Normal file
8
crates/voltex_editor/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "voltex_editor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
123
crates/voltex_editor/src/draw_list.rs
Normal file
123
crates/voltex_editor/src/draw_list.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use crate::font::FontAtlas;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct DrawVertex {
|
||||
pub position: [f32; 2],
|
||||
pub uv: [f32; 2],
|
||||
pub color: [u8; 4],
|
||||
}
|
||||
|
||||
pub struct DrawCommand {
|
||||
pub index_offset: u32,
|
||||
pub index_count: u32,
|
||||
}
|
||||
|
||||
pub struct DrawList {
|
||||
pub vertices: Vec<DrawVertex>,
|
||||
pub indices: Vec<u16>,
|
||||
pub commands: Vec<DrawCommand>,
|
||||
}
|
||||
|
||||
impl DrawList {
|
||||
pub fn new() -> Self {
|
||||
DrawList {
|
||||
vertices: Vec::new(),
|
||||
indices: Vec::new(),
|
||||
commands: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.vertices.clear();
|
||||
self.indices.clear();
|
||||
self.commands.clear();
|
||||
}
|
||||
|
||||
/// Add a solid-color rectangle. UV is (0,0) for solid color rendering.
|
||||
pub fn add_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: [u8; 4]) {
|
||||
self.add_rect_uv(x, y, w, h, 0.0, 0.0, 0.0, 0.0, color);
|
||||
}
|
||||
|
||||
/// Add a textured quad with explicit UV coordinates.
|
||||
pub fn add_rect_uv(
|
||||
&mut self,
|
||||
x: f32, y: f32, w: f32, h: f32,
|
||||
u0: f32, v0: f32, u1: f32, v1: f32,
|
||||
color: [u8; 4],
|
||||
) {
|
||||
let index_offset = self.indices.len() as u32;
|
||||
let base_vertex = self.vertices.len() as u16;
|
||||
|
||||
// 4 vertices: top-left, top-right, bottom-right, bottom-left
|
||||
self.vertices.push(DrawVertex { position: [x, y ], uv: [u0, v0], color });
|
||||
self.vertices.push(DrawVertex { position: [x + w, y ], uv: [u1, v0], color });
|
||||
self.vertices.push(DrawVertex { position: [x + w, y + h], uv: [u1, v1], color });
|
||||
self.vertices.push(DrawVertex { position: [x, y + h], uv: [u0, v1], color });
|
||||
|
||||
// 2 triangles = 6 indices
|
||||
self.indices.push(base_vertex);
|
||||
self.indices.push(base_vertex + 1);
|
||||
self.indices.push(base_vertex + 2);
|
||||
self.indices.push(base_vertex);
|
||||
self.indices.push(base_vertex + 2);
|
||||
self.indices.push(base_vertex + 3);
|
||||
|
||||
self.commands.push(DrawCommand {
|
||||
index_offset,
|
||||
index_count: 6,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add text at the given position. One quad per character, using glyph UVs from the atlas.
|
||||
pub fn add_text(&mut self, font: &FontAtlas, text: &str, x: f32, y: f32, color: [u8; 4]) {
|
||||
let gw = font.glyph_width as f32;
|
||||
let gh = font.glyph_height as f32;
|
||||
let mut cx = x;
|
||||
for ch in text.chars() {
|
||||
let (u0, v0, u1, v1) = font.glyph_uv(ch);
|
||||
self.add_rect_uv(cx, y, gw, gh, u0, v0, u1, v1, color);
|
||||
cx += gw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::font::FontAtlas;
|
||||
|
||||
#[test]
|
||||
fn test_add_rect_vertex_index_count() {
|
||||
let mut dl = DrawList::new();
|
||||
dl.add_rect(0.0, 0.0, 100.0, 50.0, [255, 0, 0, 255]);
|
||||
assert_eq!(dl.vertices.len(), 4);
|
||||
assert_eq!(dl.indices.len(), 6);
|
||||
assert_eq!(dl.commands.len(), 1);
|
||||
assert_eq!(dl.commands[0].index_count, 6);
|
||||
assert_eq!(dl.commands[0].index_offset, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_text_char_count() {
|
||||
let font = FontAtlas::generate();
|
||||
let mut dl = DrawList::new();
|
||||
let text = "Hello";
|
||||
dl.add_text(&font, text, 0.0, 0.0, [255, 255, 255, 255]);
|
||||
// 5 chars => 5 quads => 5*4=20 vertices, 5*6=30 indices
|
||||
assert_eq!(dl.vertices.len(), 5 * 4);
|
||||
assert_eq!(dl.indices.len(), 5 * 6);
|
||||
assert_eq!(dl.commands.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let mut dl = DrawList::new();
|
||||
dl.add_rect(0.0, 0.0, 50.0, 50.0, [0, 0, 0, 255]);
|
||||
dl.clear();
|
||||
assert!(dl.vertices.is_empty());
|
||||
assert!(dl.indices.is_empty());
|
||||
assert!(dl.commands.is_empty());
|
||||
}
|
||||
}
|
||||
313
crates/voltex_editor/src/font.rs
Normal file
313
crates/voltex_editor/src/font.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
/// Bitmap font atlas for ASCII 32-126.
|
||||
/// 8x12 pixel glyphs arranged in 16 columns x 6 rows = 128x72 texture.
|
||||
pub struct FontAtlas {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub glyph_width: u32,
|
||||
pub glyph_height: u32,
|
||||
pub pixels: Vec<u8>, // R8 grayscale
|
||||
}
|
||||
|
||||
/// Classic 8x12 PC BIOS bitmap font data for ASCII 32-126 (95 characters).
|
||||
/// Each character is 12 bytes (one byte per row, MSB = leftmost pixel).
|
||||
#[rustfmt::skip]
|
||||
const FONT_DATA: [u8; 95 * 12] = [
|
||||
// 32: Space
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 33: !
|
||||
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
// 34: "
|
||||
0x00, 0x6C, 0x6C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 35: #
|
||||
0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 36: $
|
||||
0x00, 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 37: %
|
||||
0x00, 0x00, 0x62, 0x64, 0x08, 0x10, 0x26, 0x46, 0x00, 0x00, 0x00, 0x00,
|
||||
// 38: &
|
||||
0x00, 0x30, 0x48, 0x48, 0x30, 0x4A, 0x44, 0x3A, 0x00, 0x00, 0x00, 0x00,
|
||||
// 39: '
|
||||
0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 40: (
|
||||
0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 41: )
|
||||
0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
// 42: *
|
||||
0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 43: +
|
||||
0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 44: ,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
|
||||
// 45: -
|
||||
0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 46: .
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 47: /
|
||||
0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00,
|
||||
// 48: 0
|
||||
0x00, 0x3C, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 49: 1
|
||||
0x00, 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 50: 2
|
||||
0x00, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 51: 3
|
||||
0x00, 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 52: 4
|
||||
0x00, 0x0C, 0x1C, 0x2C, 0x4C, 0x7E, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 53: 5
|
||||
0x00, 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 54: 6
|
||||
0x00, 0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 55: 7
|
||||
0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
// 56: 8
|
||||
0x00, 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 57: 9
|
||||
0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00, 0x00, 0x00, 0x00,
|
||||
// 58: :
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 59: ;
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
// 60: <
|
||||
0x00, 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00,
|
||||
// 61: =
|
||||
0x00, 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 62: >
|
||||
0x00, 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
|
||||
// 63: ?
|
||||
0x00, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 64: @
|
||||
0x00, 0x3C, 0x66, 0x6E, 0x6A, 0x6E, 0x60, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 65: A
|
||||
0x00, 0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 66: B
|
||||
0x00, 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 67: C
|
||||
0x00, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 68: D
|
||||
0x00, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, 0x00, 0x00, 0x00,
|
||||
// 69: E
|
||||
0x00, 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 70: F
|
||||
0x00, 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00,
|
||||
// 71: G
|
||||
0x00, 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 72: H
|
||||
0x00, 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 73: I
|
||||
0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 74: J
|
||||
0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 75: K
|
||||
0x00, 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 76: L
|
||||
0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 77: M
|
||||
0x00, 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
||||
// 78: N
|
||||
0x00, 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 79: O
|
||||
0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 80: P
|
||||
0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00,
|
||||
// 81: Q
|
||||
0x00, 0x3C, 0x66, 0x66, 0x66, 0x6A, 0x6C, 0x36, 0x00, 0x00, 0x00, 0x00,
|
||||
// 82: R
|
||||
0x00, 0x7C, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 83: S
|
||||
0x00, 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 84: T
|
||||
0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 85: U
|
||||
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 86: V
|
||||
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 87: W
|
||||
0x00, 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
||||
// 88: X
|
||||
0x00, 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 89: Y
|
||||
0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 90: Z
|
||||
0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 91: [
|
||||
0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 92: backslash
|
||||
0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
// 93: ]
|
||||
0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 94: ^
|
||||
0x00, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 95: _
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
|
||||
// 96: `
|
||||
0x00, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 97: a
|
||||
0x00, 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 98: b
|
||||
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 99: c
|
||||
0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 100: d
|
||||
0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 101: e
|
||||
0x00, 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 102: f
|
||||
0x00, 0x1C, 0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
|
||||
// 103: g
|
||||
0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00, 0x00, 0x00,
|
||||
// 104: h
|
||||
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 105: i
|
||||
0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 106: j
|
||||
0x00, 0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x78, 0x00, 0x00, 0x00,
|
||||
// 107: k
|
||||
0x00, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 108: l
|
||||
0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 109: m
|
||||
0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
||||
// 110: n
|
||||
0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 111: o
|
||||
0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 112: p
|
||||
0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00, 0x00, 0x00,
|
||||
// 113: q
|
||||
0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x00, 0x00, 0x00,
|
||||
// 114: r
|
||||
0x00, 0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00,
|
||||
// 115: s
|
||||
0x00, 0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 116: t
|
||||
0x00, 0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x1C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 117: u
|
||||
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 118: v
|
||||
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 119: w
|
||||
0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00, 0x00,
|
||||
// 120: x
|
||||
0x00, 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// 121: y
|
||||
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00, 0x00, 0x00,
|
||||
// 122: z
|
||||
0x00, 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 123: {
|
||||
0x00, 0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00,
|
||||
// 124: |
|
||||
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// 125: }
|
||||
0x00, 0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
|
||||
// 126: ~
|
||||
0x00, 0x32, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
impl FontAtlas {
|
||||
/// Generate a bitmap font atlas with real 8x12 PC BIOS-style glyphs.
|
||||
/// Each glyph is 8x12 pixels. Atlas is 16 cols x 6 rows = 128x72.
|
||||
pub fn generate() -> Self {
|
||||
let glyph_width: u32 = 8;
|
||||
let glyph_height: u32 = 12;
|
||||
let cols: u32 = 16;
|
||||
let rows: u32 = 6;
|
||||
let width = cols * glyph_width; // 128
|
||||
let height = rows * glyph_height; // 72
|
||||
|
||||
let mut pixels = vec![0u8; (width * height) as usize];
|
||||
|
||||
// Unpack 1-bit-per-pixel FONT_DATA into R8 pixel atlas
|
||||
for code in 32u8..=126u8 {
|
||||
let index = (code - 32) as u32;
|
||||
let col = index % cols;
|
||||
let row = index / cols;
|
||||
let base_x = col * glyph_width;
|
||||
let base_y = row * glyph_height;
|
||||
let data_offset = (index as usize) * 12;
|
||||
|
||||
for py in 0..12u32 {
|
||||
let row_byte = FONT_DATA[data_offset + py as usize];
|
||||
for px in 0..8u32 {
|
||||
if (row_byte >> (7 - px)) & 1 != 0 {
|
||||
let pixel_idx = ((base_y + py) * width + (base_x + px)) as usize;
|
||||
pixels[pixel_idx] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure pixel (0,0) is white (255) so solid-color rects can sample
|
||||
// UV (0,0) and get full brightness for tinting.
|
||||
pixels[0] = 255;
|
||||
|
||||
FontAtlas {
|
||||
width,
|
||||
height,
|
||||
glyph_width,
|
||||
glyph_height,
|
||||
pixels,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns (u0, v0, u1, v1) UV coordinates for a given character.
|
||||
/// Returns coordinates for space if character is out of ASCII range.
|
||||
pub fn glyph_uv(&self, ch: char) -> (f32, f32, f32, f32) {
|
||||
let code = ch as u32;
|
||||
let index = if code >= 32 && code <= 126 {
|
||||
code - 32
|
||||
} else {
|
||||
0 // space
|
||||
};
|
||||
|
||||
let cols = self.width / self.glyph_width;
|
||||
let col = index % cols;
|
||||
let row = index / cols;
|
||||
|
||||
let u0 = (col * self.glyph_width) as f32 / self.width as f32;
|
||||
let v0 = (row * self.glyph_height) as f32 / self.height as f32;
|
||||
let u1 = u0 + self.glyph_width as f32 / self.width as f32;
|
||||
let v1 = v0 + self.glyph_height as f32 / self.height as f32;
|
||||
|
||||
(u0, v0, u1, v1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_size() {
|
||||
let atlas = FontAtlas::generate();
|
||||
assert_eq!(atlas.width, 128);
|
||||
assert_eq!(atlas.height, 72);
|
||||
assert_eq!(atlas.pixels.len(), (128 * 72) as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glyph_uv_space() {
|
||||
let atlas = FontAtlas::generate();
|
||||
let (u0, v0, u1, v1) = atlas.glyph_uv(' ');
|
||||
assert!((u0 - 0.0).abs() < 1e-6);
|
||||
assert!((v0 - 0.0).abs() < 1e-6);
|
||||
assert!((u1 - 8.0 / 128.0).abs() < 1e-6);
|
||||
assert!((v1 - 12.0 / 72.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glyph_uv_a() {
|
||||
let atlas = FontAtlas::generate();
|
||||
// 'A' = ASCII 65, index = 65 - 32 = 33
|
||||
// col = 33 % 16 = 1, row = 33 / 16 = 2
|
||||
let (u0, v0, u1, v1) = atlas.glyph_uv('A');
|
||||
let expected_u0 = 1.0 * 8.0 / 128.0;
|
||||
let expected_v0 = 2.0 * 12.0 / 72.0;
|
||||
let expected_u1 = expected_u0 + 8.0 / 128.0;
|
||||
let expected_v1 = expected_v0 + 12.0 / 72.0;
|
||||
assert!((u0 - expected_u0).abs() < 1e-6, "u0 mismatch: {} vs {}", u0, expected_u0);
|
||||
assert!((v0 - expected_v0).abs() < 1e-6, "v0 mismatch: {} vs {}", v0, expected_v0);
|
||||
assert!((u1 - expected_u1).abs() < 1e-6);
|
||||
assert!((v1 - expected_v1).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
32
crates/voltex_editor/src/layout.rs
Normal file
32
crates/voltex_editor/src/layout.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
/// Simple cursor-based layout state for immediate mode UI.
|
||||
pub struct LayoutState {
|
||||
pub cursor_x: f32,
|
||||
pub cursor_y: f32,
|
||||
pub indent: f32,
|
||||
pub line_height: f32,
|
||||
pub padding: f32,
|
||||
}
|
||||
|
||||
impl LayoutState {
|
||||
/// Create a new layout state starting at (x, y).
|
||||
pub fn new(x: f32, y: f32) -> Self {
|
||||
LayoutState {
|
||||
cursor_x: x,
|
||||
cursor_y: y,
|
||||
indent: x,
|
||||
line_height: 12.0,
|
||||
padding: 4.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance to the next line: cursor_y += line_height + padding, cursor_x = indent.
|
||||
pub fn advance_line(&mut self) {
|
||||
self.cursor_y += self.line_height + self.padding;
|
||||
self.cursor_x = self.indent;
|
||||
}
|
||||
|
||||
/// Advance horizontally: cursor_x += width + padding.
|
||||
pub fn advance_x(&mut self, width: f32) {
|
||||
self.cursor_x += width + self.padding;
|
||||
}
|
||||
}
|
||||
12
crates/voltex_editor/src/lib.rs
Normal file
12
crates/voltex_editor/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod font;
|
||||
pub mod draw_list;
|
||||
pub mod layout;
|
||||
pub mod renderer;
|
||||
pub mod ui_context;
|
||||
pub mod widgets;
|
||||
|
||||
pub use font::FontAtlas;
|
||||
pub use draw_list::{DrawVertex, DrawCommand, DrawList};
|
||||
pub use layout::LayoutState;
|
||||
pub use renderer::UiRenderer;
|
||||
pub use ui_context::UiContext;
|
||||
339
crates/voltex_editor/src/renderer.rs
Normal file
339
crates/voltex_editor/src/renderer.rs
Normal file
@@ -0,0 +1,339 @@
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::draw_list::{DrawList, DrawVertex};
|
||||
use crate::font::FontAtlas;
|
||||
|
||||
/// Vertex buffer layout for DrawVertex: position(Float32x2), uv(Float32x2), color(Unorm8x4).
|
||||
const VERTEX_LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<DrawVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
// position: vec2<f32>
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
// uv: vec2<f32>
|
||||
wgpu::VertexAttribute {
|
||||
offset: 8,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
// color: vec4<f32> (from [u8;4] via Unorm8x4)
|
||||
wgpu::VertexAttribute {
|
||||
offset: 16,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Unorm8x4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// GPU-side UI renderer that turns a DrawList into a rendered frame.
|
||||
pub struct UiRenderer {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
#[allow(dead_code)]
|
||||
bind_group_layout: wgpu::BindGroupLayout,
|
||||
font_bind_group: wgpu::BindGroup,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
projection: [f32; 16],
|
||||
last_screen_w: f32,
|
||||
last_screen_h: f32,
|
||||
}
|
||||
|
||||
impl UiRenderer {
|
||||
/// Create a new UI renderer.
|
||||
///
|
||||
/// `font` is used to build the font atlas GPU texture. Pixel (0,0) must be 255
|
||||
/// so solid-color rects can sample UV (0,0) for white.
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
font: &FontAtlas,
|
||||
) -> Self {
|
||||
// --- Shader ---
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("UI Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("ui_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
// --- Bind group layout: uniform + texture + sampler ---
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("UI Bind Group Layout"),
|
||||
entries: &[
|
||||
// @binding(0): uniform buffer (projection mat4x4)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// @binding(1): texture
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// @binding(2): sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// --- Pipeline layout ---
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("UI Pipeline Layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
// --- Render pipeline: alpha blend, no depth ---
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("UI Render Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[VERTEX_LAYOUT],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
unclipped_depth: false,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// --- Font atlas GPU texture (R8Unorm) ---
|
||||
let atlas_texture = device.create_texture_with_data(
|
||||
queue,
|
||||
&wgpu::TextureDescriptor {
|
||||
label: Some("UI Font Atlas"),
|
||||
size: wgpu::Extent3d {
|
||||
width: font.width,
|
||||
height: font.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::R8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
},
|
||||
wgpu::util::TextureDataOrder::LayerMajor,
|
||||
&font.pixels,
|
||||
);
|
||||
let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("UI Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// --- Uniform buffer (projection) ---
|
||||
let projection = ortho_projection(800.0, 600.0);
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("UI Uniform Buffer"),
|
||||
contents: bytemuck::cast_slice(&projection),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
// --- Bind group ---
|
||||
let font_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("UI Font Bind Group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&atlas_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
UiRenderer {
|
||||
pipeline,
|
||||
bind_group_layout: bind_group_layout,
|
||||
font_bind_group,
|
||||
uniform_buffer,
|
||||
projection,
|
||||
last_screen_w: 800.0,
|
||||
last_screen_h: 600.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the draw list onto the given target view.
|
||||
///
|
||||
/// Uses `LoadOp::Load` so the UI overlays whatever was previously rendered.
|
||||
/// For a standalone UI demo, clear the surface before calling this.
|
||||
pub fn render(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target_view: &wgpu::TextureView,
|
||||
draw_list: &DrawList,
|
||||
screen_w: f32,
|
||||
screen_h: f32,
|
||||
) {
|
||||
if draw_list.vertices.is_empty() || draw_list.indices.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update projection if screen size changed
|
||||
if (screen_w - self.last_screen_w).abs() > 0.5
|
||||
|| (screen_h - self.last_screen_h).abs() > 0.5
|
||||
{
|
||||
self.projection = ortho_projection(screen_w, screen_h);
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&self.projection),
|
||||
);
|
||||
self.last_screen_w = screen_w;
|
||||
self.last_screen_h = screen_h;
|
||||
}
|
||||
|
||||
// Create vertex and index buffers from DrawList
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("UI Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&draw_list.vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("UI Index Buffer"),
|
||||
contents: bytemuck::cast_slice(&draw_list.indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
// Begin render pass (Load to overlay on existing content)
|
||||
{
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("UI Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target_view,
|
||||
resolve_target: None,
|
||||
depth_slice: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.font_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
||||
|
||||
// Draw each command
|
||||
for cmd in &draw_list.commands {
|
||||
pass.draw_indexed(
|
||||
cmd.index_offset..cmd.index_offset + cmd.index_count,
|
||||
0,
|
||||
0..1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Orthographic projection: left=0, right=w, top=0, bottom=h (Y down), near=-1, far=1.
|
||||
/// Returns a column-major 4x4 matrix as [f32; 16].
|
||||
fn ortho_projection(w: f32, h: f32) -> [f32; 16] {
|
||||
let l = 0.0_f32;
|
||||
let r = w;
|
||||
let t = 0.0_f32;
|
||||
let b = h;
|
||||
let n = -1.0_f32;
|
||||
let f = 1.0_f32;
|
||||
|
||||
// Column-major layout for wgpu/WGSL mat4x4
|
||||
[
|
||||
2.0 / (r - l), 0.0, 0.0, 0.0,
|
||||
0.0, 2.0 / (t - b), 0.0, 0.0,
|
||||
0.0, 0.0, 1.0 / (f - n), 0.0,
|
||||
-(r + l) / (r - l), -(t + b) / (t - b), -n / (f - n), 1.0,
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ortho_projection_corners() {
|
||||
let proj = ortho_projection(800.0, 600.0);
|
||||
// Top-left (0,0) should map to NDC (-1, 1)
|
||||
let x = proj[0] * 0.0 + proj[4] * 0.0 + proj[8] * 0.0 + proj[12];
|
||||
let y = proj[1] * 0.0 + proj[5] * 0.0 + proj[9] * 0.0 + proj[13];
|
||||
assert!((x - (-1.0)).abs() < 1e-5, "top-left x: {}", x);
|
||||
assert!((y - 1.0).abs() < 1e-5, "top-left y: {}", y);
|
||||
|
||||
// Bottom-right (800, 600) should map to NDC (1, -1)
|
||||
let x2 = proj[0] * 800.0 + proj[4] * 600.0 + proj[8] * 0.0 + proj[12];
|
||||
let y2 = proj[1] * 800.0 + proj[5] * 600.0 + proj[9] * 0.0 + proj[13];
|
||||
assert!((x2 - 1.0).abs() < 1e-5, "bot-right x: {}", x2);
|
||||
assert!((y2 - (-1.0)).abs() < 1e-5, "bot-right y: {}", y2);
|
||||
}
|
||||
}
|
||||
81
crates/voltex_editor/src/ui_context.rs
Normal file
81
crates/voltex_editor/src/ui_context.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use crate::draw_list::DrawList;
|
||||
use crate::font::FontAtlas;
|
||||
use crate::layout::LayoutState;
|
||||
|
||||
pub struct UiContext {
|
||||
pub hot: Option<u64>,
|
||||
pub active: Option<u64>,
|
||||
pub draw_list: DrawList,
|
||||
pub layout: LayoutState,
|
||||
pub mouse_x: f32,
|
||||
pub mouse_y: f32,
|
||||
pub mouse_down: bool,
|
||||
pub mouse_clicked: bool,
|
||||
pub mouse_released: bool,
|
||||
pub screen_width: f32,
|
||||
pub screen_height: f32,
|
||||
pub font: FontAtlas,
|
||||
id_counter: u64,
|
||||
prev_mouse_down: bool,
|
||||
}
|
||||
|
||||
impl UiContext {
|
||||
/// Create a new UiContext for the given screen dimensions.
|
||||
pub fn new(screen_w: f32, screen_h: f32) -> Self {
|
||||
UiContext {
|
||||
hot: None,
|
||||
active: None,
|
||||
draw_list: DrawList::new(),
|
||||
layout: LayoutState::new(0.0, 0.0),
|
||||
mouse_x: 0.0,
|
||||
mouse_y: 0.0,
|
||||
mouse_down: false,
|
||||
mouse_clicked: false,
|
||||
mouse_released: false,
|
||||
screen_width: screen_w,
|
||||
screen_height: screen_h,
|
||||
font: FontAtlas::generate(),
|
||||
id_counter: 0,
|
||||
prev_mouse_down: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Begin a new frame: clear draw list, reset id counter, update mouse state.
|
||||
pub fn begin_frame(&mut self, mx: f32, my: f32, mouse_down: bool) {
|
||||
self.draw_list.clear();
|
||||
self.id_counter = 0;
|
||||
self.hot = None;
|
||||
|
||||
self.mouse_x = mx;
|
||||
self.mouse_y = my;
|
||||
|
||||
// Compute transitions
|
||||
self.mouse_clicked = !self.prev_mouse_down && mouse_down;
|
||||
self.mouse_released = self.prev_mouse_down && !mouse_down;
|
||||
|
||||
self.mouse_down = mouse_down;
|
||||
self.prev_mouse_down = mouse_down;
|
||||
|
||||
// Reset layout to top-left
|
||||
self.layout = LayoutState::new(0.0, 0.0);
|
||||
}
|
||||
|
||||
/// End the current frame.
|
||||
pub fn end_frame(&mut self) {
|
||||
// Nothing for now — GPU submission will hook in here later.
|
||||
}
|
||||
|
||||
/// Generate a new unique ID for this frame.
|
||||
pub fn gen_id(&mut self) -> u64 {
|
||||
self.id_counter += 1;
|
||||
self.id_counter
|
||||
}
|
||||
|
||||
/// Check if the mouse cursor is inside the given rectangle.
|
||||
pub fn mouse_in_rect(&self, x: f32, y: f32, w: f32, h: f32) -> bool {
|
||||
self.mouse_x >= x
|
||||
&& self.mouse_x < x + w
|
||||
&& self.mouse_y >= y
|
||||
&& self.mouse_y < y + h
|
||||
}
|
||||
}
|
||||
34
crates/voltex_editor/src/ui_shader.wgsl
Normal file
34
crates/voltex_editor/src/ui_shader.wgsl
Normal file
@@ -0,0 +1,34 @@
|
||||
struct UiUniform {
|
||||
projection: mat4x4<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> ui_uniform: UiUniform;
|
||||
@group(0) @binding(1) var t_atlas: texture_2d<f32>;
|
||||
@group(0) @binding(2) var s_atlas: sampler;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) uv: vec2<f32>,
|
||||
@location(2) color: vec4<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(in: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = ui_uniform.projection * vec4<f32>(in.position, 0.0, 1.0);
|
||||
out.uv = in.uv;
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let tex_alpha = textureSample(t_atlas, s_atlas, in.uv).r;
|
||||
return vec4<f32>(in.color.rgb * tex_alpha, in.color.a * tex_alpha);
|
||||
}
|
||||
281
crates/voltex_editor/src/widgets.rs
Normal file
281
crates/voltex_editor/src/widgets.rs
Normal file
@@ -0,0 +1,281 @@
|
||||
use crate::ui_context::UiContext;
|
||||
|
||||
// Color palette
|
||||
const COLOR_BG: [u8; 4] = [0x2B, 0x2B, 0x2B, 0xFF];
|
||||
const COLOR_BUTTON: [u8; 4] = [0x44, 0x44, 0x55, 0xFF];
|
||||
const COLOR_BUTTON_HOT: [u8; 4] = [0x55, 0x66, 0x88, 0xFF];
|
||||
const COLOR_BUTTON_ACTIVE: [u8; 4] = [0x44, 0x88, 0xFF, 0xFF];
|
||||
const COLOR_TEXT: [u8; 4] = [0xEE, 0xEE, 0xEE, 0xFF];
|
||||
const COLOR_PANEL: [u8; 4] = [0x33, 0x33, 0x33, 0xFF];
|
||||
const COLOR_SLIDER_BG: [u8; 4] = [0x44, 0x44, 0x44, 0xFF];
|
||||
const COLOR_SLIDER_HANDLE: [u8; 4] = [0x88, 0x88, 0xFF, 0xFF];
|
||||
const COLOR_CHECK_BG: [u8; 4] = [0x44, 0x44, 0x44, 0xFF];
|
||||
const COLOR_CHECK_MARK: [u8; 4] = [0x88, 0xFF, 0x88, 0xFF];
|
||||
|
||||
impl UiContext {
|
||||
/// Draw text at the current cursor position and advance to the next line.
|
||||
pub fn text(&mut self, text: &str) {
|
||||
let x = self.layout.cursor_x;
|
||||
let y = self.layout.cursor_y;
|
||||
// We need to clone font info for the borrow checker
|
||||
let gw = self.font.glyph_width as f32;
|
||||
let gh = self.font.glyph_height as f32;
|
||||
|
||||
// Draw each character
|
||||
let mut cx = x;
|
||||
for ch in text.chars() {
|
||||
let (u0, v0, u1, v1) = self.font.glyph_uv(ch);
|
||||
self.draw_list.add_rect_uv(cx, y, gw, gh, u0, v0, u1, v1, COLOR_TEXT);
|
||||
cx += gw;
|
||||
}
|
||||
|
||||
self.layout.advance_line();
|
||||
}
|
||||
|
||||
/// Draw a button with the given label. Returns true if clicked this frame.
|
||||
pub fn button(&mut self, label: &str) -> bool {
|
||||
let id = self.gen_id();
|
||||
let gw = self.font.glyph_width as f32;
|
||||
let gh = self.font.glyph_height as f32;
|
||||
let padding = self.layout.padding;
|
||||
|
||||
let text_w = label.len() as f32 * gw;
|
||||
let btn_w = text_w + padding * 2.0;
|
||||
let btn_h = gh + padding * 2.0;
|
||||
|
||||
let x = self.layout.cursor_x;
|
||||
let y = self.layout.cursor_y;
|
||||
|
||||
let hovered = self.mouse_in_rect(x, y, btn_w, btn_h);
|
||||
|
||||
if hovered {
|
||||
self.hot = Some(id);
|
||||
if self.mouse_down {
|
||||
self.active = Some(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine color
|
||||
let bg_color = if self.active == Some(id) && hovered {
|
||||
COLOR_BUTTON_ACTIVE
|
||||
} else if self.hot == Some(id) {
|
||||
COLOR_BUTTON_HOT
|
||||
} else {
|
||||
COLOR_BUTTON
|
||||
};
|
||||
|
||||
// Draw background rect
|
||||
self.draw_list.add_rect(x, y, btn_w, btn_h, bg_color);
|
||||
|
||||
// Draw text centered inside button
|
||||
let text_x = x + padding;
|
||||
let text_y = y + padding;
|
||||
let mut cx = text_x;
|
||||
for ch in label.chars() {
|
||||
let (u0, v0, u1, v1) = self.font.glyph_uv(ch);
|
||||
self.draw_list.add_rect_uv(cx, text_y, gw, gh, u0, v0, u1, v1, COLOR_TEXT);
|
||||
cx += gw;
|
||||
}
|
||||
|
||||
self.layout.advance_line();
|
||||
|
||||
// Return true if mouse was released over this button while it was active
|
||||
let clicked = hovered && self.mouse_released && self.active == Some(id);
|
||||
if self.mouse_released {
|
||||
if self.active == Some(id) {
|
||||
self.active = None;
|
||||
}
|
||||
}
|
||||
clicked
|
||||
}
|
||||
|
||||
/// Draw a horizontal slider. Returns the (possibly new) value after interaction.
|
||||
pub fn slider(&mut self, label: &str, value: f32, min: f32, max: f32) -> f32 {
|
||||
let id = self.gen_id();
|
||||
let gw = self.font.glyph_width as f32;
|
||||
let gh = self.font.glyph_height as f32;
|
||||
let padding = self.layout.padding;
|
||||
|
||||
let slider_w = 150.0_f32;
|
||||
let slider_h = gh + padding * 2.0;
|
||||
let handle_w = 10.0_f32;
|
||||
|
||||
let x = self.layout.cursor_x;
|
||||
let y = self.layout.cursor_y;
|
||||
|
||||
let hovered = self.mouse_in_rect(x, y, slider_w, slider_h);
|
||||
|
||||
if hovered && self.mouse_clicked {
|
||||
self.active = Some(id);
|
||||
}
|
||||
|
||||
let mut new_value = value;
|
||||
|
||||
if self.active == Some(id) {
|
||||
if self.mouse_down {
|
||||
// Map mouse_x to value
|
||||
let t = ((self.mouse_x - x - handle_w / 2.0) / (slider_w - handle_w)).clamp(0.0, 1.0);
|
||||
new_value = min + t * (max - min);
|
||||
} else if self.mouse_released {
|
||||
self.active = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp value
|
||||
new_value = new_value.clamp(min, max);
|
||||
|
||||
// Draw background bar
|
||||
self.draw_list.add_rect(x, y, slider_w, slider_h, COLOR_SLIDER_BG);
|
||||
|
||||
// Draw handle
|
||||
let t = if (max - min).abs() < 1e-6 {
|
||||
0.0
|
||||
} else {
|
||||
(new_value - min) / (max - min)
|
||||
};
|
||||
let handle_x = x + t * (slider_w - handle_w);
|
||||
self.draw_list.add_rect(handle_x, y, handle_w, slider_h, COLOR_SLIDER_HANDLE);
|
||||
|
||||
// Draw label to the right of the slider
|
||||
let label_x = x + slider_w + padding;
|
||||
let label_y = y + padding;
|
||||
let mut cx = label_x;
|
||||
for ch in label.chars() {
|
||||
let (u0, v0, u1, v1) = self.font.glyph_uv(ch);
|
||||
self.draw_list.add_rect_uv(cx, label_y, gw, gh, u0, v0, u1, v1, COLOR_TEXT);
|
||||
cx += gw;
|
||||
}
|
||||
|
||||
self.layout.advance_line();
|
||||
|
||||
new_value
|
||||
}
|
||||
|
||||
/// Draw a checkbox. Returns the new checked state (toggled on click).
|
||||
pub fn checkbox(&mut self, label: &str, checked: bool) -> bool {
|
||||
let id = self.gen_id();
|
||||
let gw = self.font.glyph_width as f32;
|
||||
let gh = self.font.glyph_height as f32;
|
||||
let padding = self.layout.padding;
|
||||
|
||||
let box_size = gh;
|
||||
let x = self.layout.cursor_x;
|
||||
let y = self.layout.cursor_y;
|
||||
|
||||
let hovered = self.mouse_in_rect(x, y, box_size + padding + label.len() as f32 * gw, gh + padding);
|
||||
|
||||
if hovered {
|
||||
self.hot = Some(id);
|
||||
}
|
||||
|
||||
let mut new_checked = checked;
|
||||
if hovered && self.mouse_clicked {
|
||||
new_checked = !checked;
|
||||
}
|
||||
|
||||
// Draw checkbox background
|
||||
self.draw_list.add_rect(x, y, box_size, box_size, COLOR_CHECK_BG);
|
||||
|
||||
// Draw check mark if checked
|
||||
if new_checked {
|
||||
let inner = 3.0;
|
||||
self.draw_list.add_rect(
|
||||
x + inner,
|
||||
y + inner,
|
||||
box_size - inner * 2.0,
|
||||
box_size - inner * 2.0,
|
||||
COLOR_CHECK_MARK,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw label
|
||||
let label_x = x + box_size + padding;
|
||||
let label_y = y;
|
||||
let mut cx = label_x;
|
||||
for ch in label.chars() {
|
||||
let (u0, v0, u1, v1) = self.font.glyph_uv(ch);
|
||||
self.draw_list.add_rect_uv(cx, label_y, gw, gh, u0, v0, u1, v1, COLOR_TEXT);
|
||||
cx += gw;
|
||||
}
|
||||
|
||||
self.layout.advance_line();
|
||||
|
||||
new_checked
|
||||
}
|
||||
|
||||
/// Begin a panel: draw background and title, set cursor inside panel.
|
||||
pub fn begin_panel(&mut self, title: &str, x: f32, y: f32, w: f32, h: f32) {
|
||||
let gh = self.font.glyph_height as f32;
|
||||
let padding = self.layout.padding;
|
||||
|
||||
// Draw panel background
|
||||
self.draw_list.add_rect(x, y, w, h, COLOR_PANEL);
|
||||
|
||||
// Draw title bar (slightly darker background handled by same panel color here)
|
||||
let title_bar_h = gh + padding * 2.0;
|
||||
self.draw_list.add_rect(x, y, w, title_bar_h, COLOR_BG);
|
||||
|
||||
// Draw title text
|
||||
let gw = self.font.glyph_width as f32;
|
||||
let mut cx = x + padding;
|
||||
let ty = y + padding;
|
||||
for ch in title.chars() {
|
||||
let (u0, v0, u1, v1) = self.font.glyph_uv(ch);
|
||||
self.draw_list.add_rect_uv(cx, ty, gw, gh, u0, v0, u1, v1, COLOR_TEXT);
|
||||
cx += gw;
|
||||
}
|
||||
|
||||
// Set cursor to inside the panel (below title bar)
|
||||
self.layout = crate::layout::LayoutState::new(x + padding, y + title_bar_h + padding);
|
||||
}
|
||||
|
||||
/// End a panel — currently a no-op; cursor remains where it was.
|
||||
pub fn end_panel(&mut self) {
|
||||
// Nothing for now; future could restore outer cursor state.
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui_context::UiContext;
|
||||
|
||||
#[test]
|
||||
fn test_button_returns_false_when_not_clicked() {
|
||||
let mut ctx = UiContext::new(800.0, 600.0);
|
||||
// Mouse is at (500, 500) — far from any button
|
||||
ctx.begin_frame(500.0, 500.0, false);
|
||||
let result = ctx.button("Click Me");
|
||||
assert!(!result, "button should return false when mouse is not over it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_button_returns_true_when_clicked() {
|
||||
let mut ctx = UiContext::new(800.0, 600.0);
|
||||
|
||||
// Frame 1: mouse over button, pressed down
|
||||
// Button will be at layout cursor (0, 0) with some width/height
|
||||
// glyph_width=8, "OK"=2 chars, btn_w = 2*8 + 4*2 = 24, btn_h = 12 + 4*2 = 20
|
||||
ctx.begin_frame(10.0, 5.0, true);
|
||||
let _ = ctx.button("OK");
|
||||
|
||||
// Frame 2: mouse still over button, released
|
||||
ctx.begin_frame(10.0, 5.0, false);
|
||||
let result = ctx.button("OK");
|
||||
assert!(result, "button should return true when clicked and released");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slider_returns_clamped_value() {
|
||||
let mut ctx = UiContext::new(800.0, 600.0);
|
||||
ctx.begin_frame(0.0, 0.0, false);
|
||||
|
||||
// Value above max should be clamped
|
||||
let v = ctx.slider("test", 150.0, 0.0, 100.0);
|
||||
assert!((v - 100.0).abs() < 1e-6, "slider should clamp to max: got {}", v);
|
||||
|
||||
ctx.begin_frame(0.0, 0.0, false);
|
||||
// Value below min should be clamped
|
||||
let v2 = ctx.slider("test", -10.0, 0.0, 100.0);
|
||||
assert!((v2 - 0.0).abs() < 1e-6, "slider should clamp to min: got {}", v2);
|
||||
}
|
||||
}
|
||||
122
crates/voltex_math/src/aabb.rs
Normal file
122
crates/voltex_math/src/aabb.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// crates/voltex_math/src/aabb.rs
|
||||
use crate::Vec3;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct AABB {
|
||||
pub min: Vec3,
|
||||
pub max: Vec3,
|
||||
}
|
||||
|
||||
impl AABB {
|
||||
pub fn new(min: Vec3, max: Vec3) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
|
||||
pub fn from_center_half_extents(center: Vec3, half: Vec3) -> Self {
|
||||
Self {
|
||||
min: center - half,
|
||||
max: center + half,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center(&self) -> Vec3 {
|
||||
(self.min + self.max) * 0.5
|
||||
}
|
||||
|
||||
pub fn half_extents(&self) -> Vec3 {
|
||||
(self.max - self.min) * 0.5
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, p: Vec3) -> bool {
|
||||
p.x >= self.min.x && p.x <= self.max.x
|
||||
&& p.y >= self.min.y && p.y <= self.max.y
|
||||
&& p.z >= self.min.z && p.z <= self.max.z
|
||||
}
|
||||
|
||||
pub fn intersects(&self, other: &AABB) -> bool {
|
||||
self.min.x <= other.max.x && self.max.x >= other.min.x
|
||||
&& self.min.y <= other.max.y && self.max.y >= other.min.y
|
||||
&& self.min.z <= other.max.z && self.max.z >= other.min.z
|
||||
}
|
||||
|
||||
pub fn merged(&self, other: &AABB) -> AABB {
|
||||
AABB {
|
||||
min: Vec3::new(
|
||||
self.min.x.min(other.min.x),
|
||||
self.min.y.min(other.min.y),
|
||||
self.min.z.min(other.min.z),
|
||||
),
|
||||
max: Vec3::new(
|
||||
self.max.x.max(other.max.x),
|
||||
self.max.y.max(other.max.y),
|
||||
self.max.z.max(other.max.z),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn surface_area(&self) -> f32 {
|
||||
let d = self.max - self.min;
|
||||
2.0 * (d.x * d.y + d.y * d.z + d.z * d.x)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new_and_accessors() {
|
||||
let a = AABB::new(Vec3::new(-1.0, -2.0, -3.0), Vec3::new(1.0, 2.0, 3.0));
|
||||
let c = a.center();
|
||||
assert!((c.x).abs() < 1e-6);
|
||||
assert!((c.y).abs() < 1e-6);
|
||||
assert!((c.z).abs() < 1e-6);
|
||||
let h = a.half_extents();
|
||||
assert!((h.x - 1.0).abs() < 1e-6);
|
||||
assert!((h.y - 2.0).abs() < 1e-6);
|
||||
assert!((h.z - 3.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_center_half_extents() {
|
||||
let a = AABB::from_center_half_extents(Vec3::new(5.0, 5.0, 5.0), Vec3::new(1.0, 1.0, 1.0));
|
||||
assert_eq!(a.min, Vec3::new(4.0, 4.0, 4.0));
|
||||
assert_eq!(a.max, Vec3::new(6.0, 6.0, 6.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contains_point() {
|
||||
let a = AABB::new(Vec3::ZERO, Vec3::new(2.0, 2.0, 2.0));
|
||||
assert!(a.contains_point(Vec3::new(1.0, 1.0, 1.0)));
|
||||
assert!(a.contains_point(Vec3::ZERO));
|
||||
assert!(!a.contains_point(Vec3::new(3.0, 1.0, 1.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intersects() {
|
||||
let a = AABB::new(Vec3::ZERO, Vec3::new(2.0, 2.0, 2.0));
|
||||
let b = AABB::new(Vec3::new(1.0, 1.0, 1.0), Vec3::new(3.0, 3.0, 3.0));
|
||||
assert!(a.intersects(&b));
|
||||
|
||||
let c = AABB::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(6.0, 6.0, 6.0));
|
||||
assert!(!a.intersects(&c));
|
||||
|
||||
let d = AABB::new(Vec3::new(2.0, 0.0, 0.0), Vec3::new(3.0, 2.0, 2.0));
|
||||
assert!(a.intersects(&d));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merged() {
|
||||
let a = AABB::new(Vec3::ZERO, Vec3::ONE);
|
||||
let b = AABB::new(Vec3::new(2.0, 2.0, 2.0), Vec3::new(3.0, 3.0, 3.0));
|
||||
let m = a.merged(&b);
|
||||
assert_eq!(m.min, Vec3::ZERO);
|
||||
assert_eq!(m.max, Vec3::new(3.0, 3.0, 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_surface_area() {
|
||||
let a = AABB::new(Vec3::ZERO, Vec3::new(2.0, 2.0, 2.0));
|
||||
assert!((a.surface_area() - 24.0).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,12 @@ pub mod vec2;
|
||||
pub mod vec3;
|
||||
pub mod vec4;
|
||||
pub mod mat4;
|
||||
pub mod aabb;
|
||||
pub mod ray;
|
||||
|
||||
pub use vec2::Vec2;
|
||||
pub use vec3::Vec3;
|
||||
pub use vec4::Vec4;
|
||||
pub use mat4::Mat4;
|
||||
pub use aabb::AABB;
|
||||
pub use ray::Ray;
|
||||
|
||||
46
crates/voltex_math/src/ray.rs
Normal file
46
crates/voltex_math/src/ray.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::Vec3;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Ray {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(origin: Vec3, direction: Vec3) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
direction: direction.normalize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at(&self, t: f32) -> Vec3 {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn approx(a: f32, b: f32) -> bool {
|
||||
(a - b).abs() < 1e-5
|
||||
}
|
||||
|
||||
fn approx_vec(a: Vec3, b: Vec3) -> bool {
|
||||
approx(a.x, b.x) && approx(a.y, b.y) && approx(a.z, b.z)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_normalizes_direction() {
|
||||
let r = Ray::new(Vec3::ZERO, Vec3::new(3.0, 0.0, 0.0));
|
||||
assert!(approx_vec(r.direction, Vec3::X));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_at() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::X);
|
||||
let p = r.at(5.0);
|
||||
assert!(approx_vec(p, Vec3::new(6.0, 2.0, 3.0)));
|
||||
}
|
||||
}
|
||||
6
crates/voltex_net/Cargo.toml
Normal file
6
crates/voltex_net/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "voltex_net"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
96
crates/voltex_net/src/client.rs
Normal file
96
crates/voltex_net/src/client.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::packet::Packet;
|
||||
use crate::socket::NetSocket;
|
||||
|
||||
/// Events produced by the client during polling.
|
||||
pub enum ClientEvent {
|
||||
Connected { client_id: u32 },
|
||||
Disconnected,
|
||||
PacketReceived { packet: Packet },
|
||||
}
|
||||
|
||||
/// A non-blocking UDP client.
|
||||
pub struct NetClient {
|
||||
socket: NetSocket,
|
||||
server_addr: SocketAddr,
|
||||
client_id: Option<u32>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl NetClient {
|
||||
/// Create and bind a new client socket.
|
||||
///
|
||||
/// - `local_addr`: local bind address (e.g. "127.0.0.1:0")
|
||||
/// - `server_addr`: the server's `SocketAddr`
|
||||
/// - `name`: client display name used in Connect packet
|
||||
pub fn new(local_addr: &str, server_addr: SocketAddr, name: &str) -> Result<Self, String> {
|
||||
let socket = NetSocket::bind(local_addr)?;
|
||||
Ok(NetClient {
|
||||
socket,
|
||||
server_addr,
|
||||
client_id: None,
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a Connect packet to the server.
|
||||
pub fn connect(&self) -> Result<(), String> {
|
||||
let packet = Packet::Connect {
|
||||
client_name: self.name.clone(),
|
||||
};
|
||||
self.socket.send_to(&packet, self.server_addr)
|
||||
}
|
||||
|
||||
/// Poll for incoming packets and return any resulting client events.
|
||||
pub fn poll(&mut self) -> Vec<ClientEvent> {
|
||||
let mut events = Vec::new();
|
||||
|
||||
while let Some((packet, _addr)) = self.socket.recv_from() {
|
||||
match &packet {
|
||||
Packet::Accept { client_id } => {
|
||||
self.client_id = Some(*client_id);
|
||||
events.push(ClientEvent::Connected {
|
||||
client_id: *client_id,
|
||||
});
|
||||
}
|
||||
Packet::Disconnect { .. } => {
|
||||
self.client_id = None;
|
||||
events.push(ClientEvent::Disconnected);
|
||||
}
|
||||
_ => {
|
||||
events.push(ClientEvent::PacketReceived {
|
||||
packet: packet.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
/// Send an arbitrary packet to the server.
|
||||
pub fn send(&self, packet: Packet) -> Result<(), String> {
|
||||
self.socket.send_to(&packet, self.server_addr)
|
||||
}
|
||||
|
||||
/// Returns true if the client has received an Accept from the server.
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.client_id.is_some()
|
||||
}
|
||||
|
||||
/// Returns the client id assigned by the server, or None if not yet connected.
|
||||
pub fn client_id(&self) -> Option<u32> {
|
||||
self.client_id
|
||||
}
|
||||
|
||||
/// Send a Disconnect packet to the server and clear local state.
|
||||
pub fn disconnect(&mut self) -> Result<(), String> {
|
||||
if let Some(id) = self.client_id {
|
||||
let packet = Packet::Disconnect { client_id: id };
|
||||
self.socket.send_to(&packet, self.server_addr)?;
|
||||
self.client_id = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
9
crates/voltex_net/src/lib.rs
Normal file
9
crates/voltex_net/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod packet;
|
||||
pub mod socket;
|
||||
pub mod server;
|
||||
pub mod client;
|
||||
|
||||
pub use packet::Packet;
|
||||
pub use socket::NetSocket;
|
||||
pub use server::{NetServer, ServerEvent, ClientInfo};
|
||||
pub use client::{NetClient, ClientEvent};
|
||||
210
crates/voltex_net/src/packet.rs
Normal file
210
crates/voltex_net/src/packet.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
/// Packet type IDs
|
||||
const TYPE_CONNECT: u8 = 1;
|
||||
const TYPE_ACCEPT: u8 = 2;
|
||||
const TYPE_DISCONNECT: u8 = 3;
|
||||
const TYPE_PING: u8 = 4;
|
||||
const TYPE_PONG: u8 = 5;
|
||||
const TYPE_USER_DATA: u8 = 6;
|
||||
|
||||
/// Header size: type_id(1) + payload_len(2 LE) + reserved(1) = 4 bytes
|
||||
const HEADER_SIZE: usize = 4;
|
||||
|
||||
/// All packet variants for the Voltex network protocol.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Packet {
|
||||
Connect { client_name: String },
|
||||
Accept { client_id: u32 },
|
||||
Disconnect { client_id: u32 },
|
||||
Ping { timestamp: u64 },
|
||||
Pong { timestamp: u64 },
|
||||
UserData { client_id: u32, data: Vec<u8> },
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
/// Serialize the packet into bytes: [type_id(1), payload_len(2 LE), reserved(1), payload...]
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let payload = self.encode_payload();
|
||||
let payload_len = payload.len() as u16;
|
||||
|
||||
let mut buf = Vec::with_capacity(HEADER_SIZE + payload.len());
|
||||
buf.push(self.type_id());
|
||||
buf.extend_from_slice(&payload_len.to_le_bytes());
|
||||
buf.push(0u8); // reserved
|
||||
buf.extend_from_slice(&payload);
|
||||
buf
|
||||
}
|
||||
|
||||
/// Deserialize a packet from bytes.
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Packet, String> {
|
||||
if data.len() < HEADER_SIZE {
|
||||
return Err(format!(
|
||||
"Buffer too short for header: {} bytes",
|
||||
data.len()
|
||||
));
|
||||
}
|
||||
|
||||
let type_id = data[0];
|
||||
let payload_len = u16::from_le_bytes([data[1], data[2]]) as usize;
|
||||
// data[3] is reserved, ignored
|
||||
|
||||
if data.len() < HEADER_SIZE + payload_len {
|
||||
return Err(format!(
|
||||
"Buffer too short: expected {} bytes, got {}",
|
||||
HEADER_SIZE + payload_len,
|
||||
data.len()
|
||||
));
|
||||
}
|
||||
|
||||
let payload = &data[HEADER_SIZE..HEADER_SIZE + payload_len];
|
||||
|
||||
match type_id {
|
||||
TYPE_CONNECT => {
|
||||
if payload.len() < 2 {
|
||||
return Err("Connect payload too short".to_string());
|
||||
}
|
||||
let name_len = u16::from_le_bytes([payload[0], payload[1]]) as usize;
|
||||
if payload.len() < 2 + name_len {
|
||||
return Err("Connect name bytes too short".to_string());
|
||||
}
|
||||
let client_name = String::from_utf8(payload[2..2 + name_len].to_vec())
|
||||
.map_err(|e| format!("Invalid UTF-8 in client_name: {}", e))?;
|
||||
Ok(Packet::Connect { client_name })
|
||||
}
|
||||
TYPE_ACCEPT => {
|
||||
if payload.len() < 4 {
|
||||
return Err("Accept payload too short".to_string());
|
||||
}
|
||||
let client_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
Ok(Packet::Accept { client_id })
|
||||
}
|
||||
TYPE_DISCONNECT => {
|
||||
if payload.len() < 4 {
|
||||
return Err("Disconnect payload too short".to_string());
|
||||
}
|
||||
let client_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
Ok(Packet::Disconnect { client_id })
|
||||
}
|
||||
TYPE_PING => {
|
||||
if payload.len() < 8 {
|
||||
return Err("Ping payload too short".to_string());
|
||||
}
|
||||
let timestamp = u64::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
payload[4], payload[5], payload[6], payload[7],
|
||||
]);
|
||||
Ok(Packet::Ping { timestamp })
|
||||
}
|
||||
TYPE_PONG => {
|
||||
if payload.len() < 8 {
|
||||
return Err("Pong payload too short".to_string());
|
||||
}
|
||||
let timestamp = u64::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
payload[4], payload[5], payload[6], payload[7],
|
||||
]);
|
||||
Ok(Packet::Pong { timestamp })
|
||||
}
|
||||
TYPE_USER_DATA => {
|
||||
if payload.len() < 4 {
|
||||
return Err("UserData payload too short".to_string());
|
||||
}
|
||||
let client_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let data = payload[4..].to_vec();
|
||||
Ok(Packet::UserData { client_id, data })
|
||||
}
|
||||
_ => Err(format!("Unknown packet type_id: {}", type_id)),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_id(&self) -> u8 {
|
||||
match self {
|
||||
Packet::Connect { .. } => TYPE_CONNECT,
|
||||
Packet::Accept { .. } => TYPE_ACCEPT,
|
||||
Packet::Disconnect { .. } => TYPE_DISCONNECT,
|
||||
Packet::Ping { .. } => TYPE_PING,
|
||||
Packet::Pong { .. } => TYPE_PONG,
|
||||
Packet::UserData { .. } => TYPE_USER_DATA,
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_payload(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Packet::Connect { client_name } => {
|
||||
let name_bytes = client_name.as_bytes();
|
||||
let name_len = name_bytes.len() as u16;
|
||||
let mut buf = Vec::with_capacity(2 + name_bytes.len());
|
||||
buf.extend_from_slice(&name_len.to_le_bytes());
|
||||
buf.extend_from_slice(name_bytes);
|
||||
buf
|
||||
}
|
||||
Packet::Accept { client_id } => client_id.to_le_bytes().to_vec(),
|
||||
Packet::Disconnect { client_id } => client_id.to_le_bytes().to_vec(),
|
||||
Packet::Ping { timestamp } => timestamp.to_le_bytes().to_vec(),
|
||||
Packet::Pong { timestamp } => timestamp.to_le_bytes().to_vec(),
|
||||
Packet::UserData { client_id, data } => {
|
||||
let mut buf = Vec::with_capacity(4 + data.len());
|
||||
buf.extend_from_slice(&client_id.to_le_bytes());
|
||||
buf.extend_from_slice(data);
|
||||
buf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn roundtrip(packet: Packet) {
|
||||
let bytes = packet.to_bytes();
|
||||
let decoded = Packet::from_bytes(&bytes).expect("roundtrip failed");
|
||||
assert_eq!(packet, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_roundtrip() {
|
||||
roundtrip(Packet::Connect {
|
||||
client_name: "Alice".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_accept_roundtrip() {
|
||||
roundtrip(Packet::Accept { client_id: 42 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnect_roundtrip() {
|
||||
roundtrip(Packet::Disconnect { client_id: 7 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ping_roundtrip() {
|
||||
roundtrip(Packet::Ping {
|
||||
timestamp: 1_234_567_890_u64,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pong_roundtrip() {
|
||||
roundtrip(Packet::Pong {
|
||||
timestamp: 9_876_543_210_u64,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user_data_roundtrip() {
|
||||
roundtrip(Packet::UserData {
|
||||
client_id: 3,
|
||||
data: vec![0xDE, 0xAD, 0xBE, 0xEF],
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_type_returns_error() {
|
||||
// Build a packet with type_id = 99 (unknown)
|
||||
let bytes = vec![99u8, 0, 0, 0]; // type=99, payload_len=0, reserved=0
|
||||
let result = Packet::from_bytes(&bytes);
|
||||
assert!(result.is_err(), "Expected error for unknown type_id");
|
||||
}
|
||||
}
|
||||
195
crates/voltex_net/src/server.rs
Normal file
195
crates/voltex_net/src/server.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::packet::Packet;
|
||||
use crate::socket::NetSocket;
|
||||
|
||||
/// Information about a connected client.
|
||||
pub struct ClientInfo {
|
||||
pub id: u32,
|
||||
pub addr: SocketAddr,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Events produced by the server during polling.
|
||||
pub enum ServerEvent {
|
||||
ClientConnected { client_id: u32, name: String },
|
||||
ClientDisconnected { client_id: u32 },
|
||||
PacketReceived { client_id: u32, packet: Packet },
|
||||
}
|
||||
|
||||
/// A non-blocking UDP server that manages multiple clients.
|
||||
pub struct NetServer {
|
||||
socket: NetSocket,
|
||||
clients: HashMap<u32, ClientInfo>,
|
||||
addr_to_id: HashMap<SocketAddr, u32>,
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl NetServer {
|
||||
/// Bind the server to the given address.
|
||||
pub fn new(addr: &str) -> Result<Self, String> {
|
||||
let socket = NetSocket::bind(addr)?;
|
||||
Ok(NetServer {
|
||||
socket,
|
||||
clients: HashMap::new(),
|
||||
addr_to_id: HashMap::new(),
|
||||
next_id: 1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the local address the server is listening on.
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.socket.local_addr()
|
||||
}
|
||||
|
||||
/// Poll for incoming packets and return any resulting server events.
|
||||
pub fn poll(&mut self) -> Vec<ServerEvent> {
|
||||
let mut events = Vec::new();
|
||||
|
||||
while let Some((packet, addr)) = self.socket.recv_from() {
|
||||
match &packet {
|
||||
Packet::Connect { client_name } => {
|
||||
// Assign a new id and send Accept
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
let name = client_name.clone();
|
||||
|
||||
let info = ClientInfo {
|
||||
id,
|
||||
addr,
|
||||
name: name.clone(),
|
||||
};
|
||||
self.clients.insert(id, info);
|
||||
self.addr_to_id.insert(addr, id);
|
||||
|
||||
let accept = Packet::Accept { client_id: id };
|
||||
if let Err(e) = self.socket.send_to(&accept, addr) {
|
||||
eprintln!("[NetServer] Failed to send Accept to {}: {}", addr, e);
|
||||
}
|
||||
|
||||
events.push(ServerEvent::ClientConnected { client_id: id, name });
|
||||
}
|
||||
Packet::Disconnect { client_id } => {
|
||||
let id = *client_id;
|
||||
if let Some(info) = self.clients.remove(&id) {
|
||||
self.addr_to_id.remove(&info.addr);
|
||||
events.push(ServerEvent::ClientDisconnected { client_id: id });
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Map address to client id
|
||||
if let Some(&client_id) = self.addr_to_id.get(&addr) {
|
||||
events.push(ServerEvent::PacketReceived {
|
||||
client_id,
|
||||
packet: packet.clone(),
|
||||
});
|
||||
} else {
|
||||
eprintln!("[NetServer] Packet from unknown addr {}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
/// Send a packet to every connected client.
|
||||
pub fn broadcast(&self, packet: &Packet) {
|
||||
for info in self.clients.values() {
|
||||
if let Err(e) = self.socket.send_to(packet, info.addr) {
|
||||
eprintln!("[NetServer] broadcast failed for client {}: {}", info.id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a packet to a specific client by id.
|
||||
pub fn send_to_client(&self, id: u32, packet: &Packet) {
|
||||
if let Some(info) = self.clients.get(&id) {
|
||||
if let Err(e) = self.socket.send_to(packet, info.addr) {
|
||||
eprintln!("[NetServer] send_to_client {} failed: {}", id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice of all connected clients.
|
||||
pub fn clients(&self) -> impl Iterator<Item = &ClientInfo> {
|
||||
self.clients.values()
|
||||
}
|
||||
|
||||
/// Returns the number of connected clients.
|
||||
pub fn client_count(&self) -> usize {
|
||||
self.clients.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::client::{ClientEvent, NetClient};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_integration_connect_and_userdata() {
|
||||
// Step 1: Start server on OS-assigned port
|
||||
let mut server = NetServer::new("127.0.0.1:0").expect("server bind failed");
|
||||
let server_addr = server.local_addr();
|
||||
|
||||
// Step 2: Create client on OS-assigned port, point at server
|
||||
let mut client = NetClient::new("127.0.0.1:0", server_addr, "TestClient")
|
||||
.expect("client bind failed");
|
||||
|
||||
// Step 3: Client sends Connect
|
||||
client.connect().expect("connect send failed");
|
||||
|
||||
// Step 4: Give the packet time to travel
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
|
||||
// Step 5: Server poll → should get ClientConnected
|
||||
let server_events = server.poll();
|
||||
let mut connected_id = None;
|
||||
for event in &server_events {
|
||||
if let ServerEvent::ClientConnected { client_id, name } = event {
|
||||
connected_id = Some(*client_id);
|
||||
assert_eq!(name, "TestClient");
|
||||
}
|
||||
}
|
||||
assert!(connected_id.is_some(), "Server did not receive ClientConnected");
|
||||
|
||||
// Step 6: Client poll → should get Connected
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
let client_events = client.poll();
|
||||
let mut got_connected = false;
|
||||
for event in &client_events {
|
||||
if let ClientEvent::Connected { client_id } = event {
|
||||
assert_eq!(Some(*client_id), connected_id);
|
||||
got_connected = true;
|
||||
}
|
||||
}
|
||||
assert!(got_connected, "Client did not receive Connected event");
|
||||
|
||||
// Step 7: Client sends UserData, server should receive it
|
||||
let cid = client.client_id().unwrap();
|
||||
let user_packet = Packet::UserData {
|
||||
client_id: cid,
|
||||
data: vec![1, 2, 3, 4],
|
||||
};
|
||||
client.send(user_packet.clone()).expect("send userdata failed");
|
||||
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
|
||||
let server_events2 = server.poll();
|
||||
let mut got_packet = false;
|
||||
for event in server_events2 {
|
||||
if let ServerEvent::PacketReceived { client_id, packet } = event {
|
||||
assert_eq!(client_id, cid);
|
||||
assert_eq!(packet, user_packet);
|
||||
got_packet = true;
|
||||
}
|
||||
}
|
||||
assert!(got_packet, "Server did not receive UserData packet");
|
||||
|
||||
// Cleanup: disconnect
|
||||
client.disconnect().expect("disconnect send failed");
|
||||
}
|
||||
}
|
||||
58
crates/voltex_net/src/socket.rs
Normal file
58
crates/voltex_net/src/socket.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
|
||||
use crate::Packet;
|
||||
|
||||
/// Maximum UDP datagram size we'll allocate for receiving.
|
||||
const MAX_PACKET_SIZE: usize = 65535;
|
||||
|
||||
/// A non-blocking UDP socket wrapper.
|
||||
pub struct NetSocket {
|
||||
inner: UdpSocket,
|
||||
}
|
||||
|
||||
impl NetSocket {
|
||||
/// Bind a new non-blocking UDP socket to the given address.
|
||||
pub fn bind(addr: &str) -> Result<Self, String> {
|
||||
let socket = UdpSocket::bind(addr)
|
||||
.map_err(|e| format!("UdpSocket::bind({}) failed: {}", addr, e))?;
|
||||
socket
|
||||
.set_nonblocking(true)
|
||||
.map_err(|e| format!("set_nonblocking failed: {}", e))?;
|
||||
Ok(NetSocket { inner: socket })
|
||||
}
|
||||
|
||||
/// Returns the local address this socket is bound to.
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.inner.local_addr().expect("local_addr unavailable")
|
||||
}
|
||||
|
||||
/// Serialize and send a packet to the given address.
|
||||
pub fn send_to(&self, packet: &Packet, addr: SocketAddr) -> Result<(), String> {
|
||||
let bytes = packet.to_bytes();
|
||||
self.inner
|
||||
.send_to(&bytes, addr)
|
||||
.map_err(|e| format!("send_to failed: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Try to receive one packet. Returns None on WouldBlock (no data available).
|
||||
pub fn recv_from(&self) -> Option<(Packet, SocketAddr)> {
|
||||
let mut buf = vec![0u8; MAX_PACKET_SIZE];
|
||||
match self.inner.recv_from(&mut buf) {
|
||||
Ok((len, addr)) => {
|
||||
match Packet::from_bytes(&buf[..len]) {
|
||||
Ok(packet) => Some((packet, addr)),
|
||||
Err(e) => {
|
||||
eprintln!("[NetSocket] Failed to parse packet from {}: {}", addr, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => None,
|
||||
Err(e) => {
|
||||
eprintln!("[NetSocket] recv_from error: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
crates/voltex_physics/Cargo.toml
Normal file
8
crates/voltex_physics/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "voltex_physics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
voltex_math.workspace = true
|
||||
voltex_ecs.workspace = true
|
||||
166
crates/voltex_physics/src/bvh.rs
Normal file
166
crates/voltex_physics/src/bvh.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use voltex_ecs::Entity;
|
||||
use voltex_math::AABB;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BvhNode {
|
||||
Leaf { entity: Entity, aabb: AABB },
|
||||
Internal { aabb: AABB, left: usize, right: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BvhTree {
|
||||
nodes: Vec<BvhNode>,
|
||||
}
|
||||
|
||||
impl BvhTree {
|
||||
pub fn build(entries: &[(Entity, AABB)]) -> Self {
|
||||
let mut tree = BvhTree { nodes: Vec::new() };
|
||||
if !entries.is_empty() {
|
||||
let mut sorted: Vec<(Entity, AABB)> = entries.to_vec();
|
||||
tree.build_recursive(&mut sorted);
|
||||
}
|
||||
tree
|
||||
}
|
||||
|
||||
fn build_recursive(&mut self, entries: &mut [(Entity, AABB)]) -> usize {
|
||||
if entries.len() == 1 {
|
||||
let idx = self.nodes.len();
|
||||
self.nodes.push(BvhNode::Leaf {
|
||||
entity: entries[0].0,
|
||||
aabb: entries[0].1,
|
||||
});
|
||||
return idx;
|
||||
}
|
||||
|
||||
// Compute bounding AABB
|
||||
let mut combined = entries[0].1;
|
||||
for e in entries.iter().skip(1) {
|
||||
combined = combined.merged(&e.1);
|
||||
}
|
||||
|
||||
// Find longest axis
|
||||
let extent = combined.max - combined.min;
|
||||
let axis = if extent.x >= extent.y && extent.x >= extent.z {
|
||||
0
|
||||
} else if extent.y >= extent.z {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
};
|
||||
|
||||
// Sort by axis center
|
||||
entries.sort_by(|a, b| {
|
||||
let ca = a.1.center();
|
||||
let cb = b.1.center();
|
||||
let va = match axis { 0 => ca.x, 1 => ca.y, _ => ca.z };
|
||||
let vb = match axis { 0 => cb.x, 1 => cb.y, _ => cb.z };
|
||||
va.partial_cmp(&vb).unwrap()
|
||||
});
|
||||
|
||||
let mid = entries.len() / 2;
|
||||
let (left_entries, right_entries) = entries.split_at_mut(mid);
|
||||
|
||||
let left = self.build_recursive(left_entries);
|
||||
let right = self.build_recursive(right_entries);
|
||||
|
||||
let idx = self.nodes.len();
|
||||
self.nodes.push(BvhNode::Internal {
|
||||
aabb: combined,
|
||||
left,
|
||||
right,
|
||||
});
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn query_pairs(&self) -> Vec<(Entity, Entity)> {
|
||||
let mut pairs = Vec::new();
|
||||
if self.nodes.is_empty() {
|
||||
return pairs;
|
||||
}
|
||||
let root = self.nodes.len() - 1;
|
||||
let mut leaves = Vec::new();
|
||||
self.collect_leaves(root, &mut leaves);
|
||||
for i in 0..leaves.len() {
|
||||
for j in (i + 1)..leaves.len() {
|
||||
let (ea, aabb_a) = leaves[i];
|
||||
let (eb, aabb_b) = leaves[j];
|
||||
if aabb_a.intersects(&aabb_b) {
|
||||
pairs.push((ea, eb));
|
||||
}
|
||||
}
|
||||
}
|
||||
pairs
|
||||
}
|
||||
|
||||
fn collect_leaves(&self, node_idx: usize, out: &mut Vec<(Entity, AABB)>) {
|
||||
match &self.nodes[node_idx] {
|
||||
BvhNode::Leaf { entity, aabb } => {
|
||||
out.push((*entity, *aabb));
|
||||
}
|
||||
BvhNode::Internal { left, right, .. } => {
|
||||
self.collect_leaves(*left, out);
|
||||
self.collect_leaves(*right, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use voltex_math::Vec3;
|
||||
|
||||
fn make_entity(id: u32) -> Entity {
|
||||
Entity { id, generation: 0 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_empty() {
|
||||
let tree = BvhTree::build(&[]);
|
||||
assert!(tree.query_pairs().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_single() {
|
||||
let entries = vec![
|
||||
(make_entity(0), AABB::new(Vec3::ZERO, Vec3::ONE)),
|
||||
];
|
||||
let tree = BvhTree::build(&entries);
|
||||
assert!(tree.query_pairs().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overlapping_pair() {
|
||||
let entries = vec![
|
||||
(make_entity(0), AABB::new(Vec3::ZERO, Vec3::new(2.0, 2.0, 2.0))),
|
||||
(make_entity(1), AABB::new(Vec3::ONE, Vec3::new(3.0, 3.0, 3.0))),
|
||||
];
|
||||
let tree = BvhTree::build(&entries);
|
||||
let pairs = tree.query_pairs();
|
||||
assert_eq!(pairs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_separated_pair() {
|
||||
let entries = vec![
|
||||
(make_entity(0), AABB::new(Vec3::ZERO, Vec3::ONE)),
|
||||
(make_entity(1), AABB::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(6.0, 6.0, 6.0))),
|
||||
];
|
||||
let tree = BvhTree::build(&entries);
|
||||
assert!(tree.query_pairs().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_entities() {
|
||||
let entries = vec![
|
||||
(make_entity(0), AABB::new(Vec3::ZERO, Vec3::new(2.0, 2.0, 2.0))),
|
||||
(make_entity(1), AABB::new(Vec3::ONE, Vec3::new(3.0, 3.0, 3.0))),
|
||||
(make_entity(2), AABB::new(Vec3::new(10.0, 10.0, 10.0), Vec3::new(11.0, 11.0, 11.0))),
|
||||
];
|
||||
let tree = BvhTree::build(&entries);
|
||||
let pairs = tree.query_pairs();
|
||||
assert_eq!(pairs.len(), 1);
|
||||
let (a, b) = pairs[0];
|
||||
assert!((a.id == 0 && b.id == 1) || (a.id == 1 && b.id == 0));
|
||||
}
|
||||
}
|
||||
42
crates/voltex_physics/src/collider.rs
Normal file
42
crates/voltex_physics/src/collider.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use voltex_math::{Vec3, AABB};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Collider {
|
||||
Sphere { radius: f32 },
|
||||
Box { half_extents: Vec3 },
|
||||
}
|
||||
|
||||
impl Collider {
|
||||
pub fn aabb(&self, position: Vec3) -> AABB {
|
||||
match self {
|
||||
Collider::Sphere { radius } => {
|
||||
let r = Vec3::new(*radius, *radius, *radius);
|
||||
AABB::new(position - r, position + r)
|
||||
}
|
||||
Collider::Box { half_extents } => {
|
||||
AABB::new(position - *half_extents, position + *half_extents)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_sphere_aabb() {
|
||||
let c = Collider::Sphere { radius: 2.0 };
|
||||
let aabb = c.aabb(Vec3::new(1.0, 0.0, 0.0));
|
||||
assert_eq!(aabb.min, Vec3::new(-1.0, -2.0, -2.0));
|
||||
assert_eq!(aabb.max, Vec3::new(3.0, 2.0, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_aabb() {
|
||||
let c = Collider::Box { half_extents: Vec3::new(1.0, 2.0, 3.0) };
|
||||
let aabb = c.aabb(Vec3::ZERO);
|
||||
assert_eq!(aabb.min, Vec3::new(-1.0, -2.0, -3.0));
|
||||
assert_eq!(aabb.max, Vec3::new(1.0, 2.0, 3.0));
|
||||
}
|
||||
}
|
||||
181
crates/voltex_physics/src/collision.rs
Normal file
181
crates/voltex_physics/src/collision.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use voltex_ecs::{World, Entity};
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
|
||||
use crate::collider::Collider;
|
||||
use crate::contact::ContactPoint;
|
||||
use crate::bvh::BvhTree;
|
||||
use crate::narrow;
|
||||
|
||||
pub fn detect_collisions(world: &World) -> Vec<ContactPoint> {
|
||||
// 1. Gather entities with Transform + Collider
|
||||
let pairs_data: Vec<(Entity, Vec3, Collider)> = world
|
||||
.query2::<Transform, Collider>()
|
||||
.into_iter()
|
||||
.map(|(e, t, c)| (e, t.position, *c))
|
||||
.collect();
|
||||
|
||||
if pairs_data.len() < 2 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// 2. Build AABBs
|
||||
let entries: Vec<(Entity, voltex_math::AABB)> = pairs_data
|
||||
.iter()
|
||||
.map(|(e, pos, col)| (*e, col.aabb(*pos)))
|
||||
.collect();
|
||||
|
||||
// 3. Broad phase
|
||||
let bvh = BvhTree::build(&entries);
|
||||
let broad_pairs = bvh.query_pairs();
|
||||
|
||||
// 4. Narrow phase
|
||||
let mut contacts = Vec::new();
|
||||
|
||||
let lookup = |entity: Entity| -> Option<(Vec3, Collider)> {
|
||||
pairs_data.iter().find(|(e, _, _)| *e == entity).map(|(_, p, c)| (*p, *c))
|
||||
};
|
||||
|
||||
for (ea, eb) in broad_pairs {
|
||||
let (pos_a, col_a) = match lookup(ea) { Some(v) => v, None => continue };
|
||||
let (pos_b, col_b) = match lookup(eb) { Some(v) => v, None => continue };
|
||||
|
||||
let result = match (&col_a, &col_b) {
|
||||
(Collider::Sphere { radius: ra }, Collider::Sphere { radius: rb }) => {
|
||||
narrow::sphere_vs_sphere(pos_a, *ra, pos_b, *rb)
|
||||
}
|
||||
(Collider::Sphere { radius }, Collider::Box { half_extents }) => {
|
||||
narrow::sphere_vs_box(pos_a, *radius, pos_b, *half_extents)
|
||||
}
|
||||
(Collider::Box { half_extents }, Collider::Sphere { radius }) => {
|
||||
narrow::sphere_vs_box(pos_b, *radius, pos_a, *half_extents)
|
||||
.map(|(n, d, pa, pb)| (-n, d, pb, pa))
|
||||
}
|
||||
(Collider::Box { half_extents: ha }, Collider::Box { half_extents: hb }) => {
|
||||
narrow::box_vs_box(pos_a, *ha, pos_b, *hb)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((normal, depth, point_on_a, point_on_b)) = result {
|
||||
contacts.push(ContactPoint {
|
||||
entity_a: ea,
|
||||
entity_b: eb,
|
||||
normal,
|
||||
depth,
|
||||
point_on_a,
|
||||
point_on_b,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
contacts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use voltex_ecs::World;
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
use crate::Collider;
|
||||
|
||||
#[test]
|
||||
fn test_no_colliders() {
|
||||
let world = World::new();
|
||||
let contacts = detect_collisions(&world);
|
||||
assert!(contacts.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_entity() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::ZERO));
|
||||
world.add(e, Collider::Sphere { radius: 1.0 });
|
||||
let contacts = detect_collisions(&world);
|
||||
assert!(contacts.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_spheres_colliding() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(1.5, 0.0, 0.0)));
|
||||
world.add(b, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert!((contacts[0].depth - 0.5).abs() < 1e-5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_spheres_separated() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(10.0, 0.0, 0.0)));
|
||||
world.add(b, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert!(contacts.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_vs_box_collision() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(1.5, 0.0, 0.0)));
|
||||
world.add(b, Collider::Box { half_extents: Vec3::ONE });
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert!(contacts[0].depth > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_vs_box_collision() {
|
||||
let mut world = World::new();
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Box { half_extents: Vec3::ONE });
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(1.5, 0.0, 0.0)));
|
||||
world.add(b, Collider::Box { half_extents: Vec3::ONE });
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert!((contacts[0].depth - 0.5).abs() < 1e-5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_entities_mixed() {
|
||||
let mut world = World::new();
|
||||
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(1.5, 0.0, 0.0)));
|
||||
world.add(b, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let c = world.spawn();
|
||||
world.add(c, Transform::from_position(Vec3::new(100.0, 0.0, 0.0)));
|
||||
world.add(c, Collider::Box { half_extents: Vec3::ONE });
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
}
|
||||
}
|
||||
12
crates/voltex_physics/src/contact.rs
Normal file
12
crates/voltex_physics/src/contact.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use voltex_ecs::Entity;
|
||||
use voltex_math::Vec3;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ContactPoint {
|
||||
pub entity_a: Entity,
|
||||
pub entity_b: Entity,
|
||||
pub normal: Vec3,
|
||||
pub depth: f32,
|
||||
pub point_on_a: Vec3,
|
||||
pub point_on_b: Vec3,
|
||||
}
|
||||
96
crates/voltex_physics/src/integrator.rs
Normal file
96
crates/voltex_physics/src/integrator.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use voltex_ecs::World;
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
use crate::rigid_body::{RigidBody, PhysicsConfig};
|
||||
|
||||
pub fn integrate(world: &mut World, config: &PhysicsConfig) {
|
||||
// 1. Collect
|
||||
let updates: Vec<(voltex_ecs::Entity, Vec3, Vec3)> = world
|
||||
.query2::<Transform, RigidBody>()
|
||||
.into_iter()
|
||||
.filter(|(_, _, rb)| !rb.is_static())
|
||||
.map(|(entity, transform, rb)| {
|
||||
let new_velocity = rb.velocity + config.gravity * rb.gravity_scale * config.fixed_dt;
|
||||
let new_position = transform.position + new_velocity * config.fixed_dt;
|
||||
(entity, new_velocity, new_position)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 2. Apply
|
||||
for (entity, new_velocity, new_position) in updates {
|
||||
if let Some(rb) = world.get_mut::<RigidBody>(entity) {
|
||||
rb.velocity = new_velocity;
|
||||
}
|
||||
if let Some(t) = world.get_mut::<Transform>(entity) {
|
||||
t.position = new_position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use voltex_ecs::World;
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
use crate::RigidBody;
|
||||
|
||||
fn approx(a: f32, b: f32) -> bool {
|
||||
(a - b).abs() < 1e-4
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gravity_fall() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::new(0.0, 10.0, 0.0)));
|
||||
world.add(e, RigidBody::dynamic(1.0));
|
||||
|
||||
let config = PhysicsConfig::default();
|
||||
integrate(&mut world, &config);
|
||||
|
||||
let rb = world.get::<RigidBody>(e).unwrap();
|
||||
let t = world.get::<Transform>(e).unwrap();
|
||||
|
||||
let expected_vy = -9.81 * config.fixed_dt;
|
||||
assert!(approx(rb.velocity.y, expected_vy));
|
||||
|
||||
let expected_py = 10.0 + expected_vy * config.fixed_dt;
|
||||
assert!(approx(t.position.y, expected_py));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_unchanged() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::new(0.0, 5.0, 0.0)));
|
||||
world.add(e, RigidBody::statik());
|
||||
|
||||
let config = PhysicsConfig::default();
|
||||
integrate(&mut world, &config);
|
||||
|
||||
let t = world.get::<Transform>(e).unwrap();
|
||||
assert!(approx(t.position.y, 5.0));
|
||||
|
||||
let rb = world.get::<RigidBody>(e).unwrap();
|
||||
assert!(approx(rb.velocity.y, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initial_velocity() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::ZERO));
|
||||
let mut rb = RigidBody::dynamic(1.0);
|
||||
rb.velocity = Vec3::new(5.0, 0.0, 0.0);
|
||||
rb.gravity_scale = 0.0;
|
||||
world.add(e, rb);
|
||||
|
||||
let config = PhysicsConfig::default();
|
||||
integrate(&mut world, &config);
|
||||
|
||||
let t = world.get::<Transform>(e).unwrap();
|
||||
let expected_x = 5.0 * config.fixed_dt;
|
||||
assert!(approx(t.position.x, expected_x));
|
||||
}
|
||||
}
|
||||
19
crates/voltex_physics/src/lib.rs
Normal file
19
crates/voltex_physics/src/lib.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
pub mod bvh;
|
||||
pub mod ray;
|
||||
pub mod collider;
|
||||
pub mod contact;
|
||||
pub mod narrow;
|
||||
pub mod collision;
|
||||
pub mod rigid_body;
|
||||
pub mod integrator;
|
||||
pub mod solver;
|
||||
pub mod raycast;
|
||||
|
||||
pub use bvh::BvhTree;
|
||||
pub use collider::Collider;
|
||||
pub use contact::ContactPoint;
|
||||
pub use collision::detect_collisions;
|
||||
pub use rigid_body::{RigidBody, PhysicsConfig};
|
||||
pub use integrator::integrate;
|
||||
pub use solver::{resolve_collisions, physics_step};
|
||||
pub use raycast::{RayHit, raycast};
|
||||
236
crates/voltex_physics/src/narrow.rs
Normal file
236
crates/voltex_physics/src/narrow.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use voltex_math::Vec3;
|
||||
|
||||
/// Returns (normal A→B, depth, point_on_a, point_on_b) or None if no collision.
|
||||
pub fn sphere_vs_sphere(
|
||||
pos_a: Vec3, radius_a: f32,
|
||||
pos_b: Vec3, radius_b: f32,
|
||||
) -> Option<(Vec3, f32, Vec3, Vec3)> {
|
||||
let diff = pos_b - pos_a;
|
||||
let dist_sq = diff.length_squared();
|
||||
let sum_r = radius_a + radius_b;
|
||||
|
||||
if dist_sq > sum_r * sum_r {
|
||||
return None;
|
||||
}
|
||||
|
||||
let dist = dist_sq.sqrt();
|
||||
let normal = if dist > 1e-8 {
|
||||
diff * (1.0 / dist)
|
||||
} else {
|
||||
Vec3::Y
|
||||
};
|
||||
|
||||
let depth = sum_r - dist;
|
||||
let point_on_a = pos_a + normal * radius_a;
|
||||
let point_on_b = pos_b - normal * radius_b;
|
||||
|
||||
Some((normal, depth, point_on_a, point_on_b))
|
||||
}
|
||||
|
||||
pub fn sphere_vs_box(
|
||||
sphere_pos: Vec3, radius: f32,
|
||||
box_pos: Vec3, half_extents: Vec3,
|
||||
) -> Option<(Vec3, f32, Vec3, Vec3)> {
|
||||
let bmin = box_pos - half_extents;
|
||||
let bmax = box_pos + half_extents;
|
||||
|
||||
let closest = Vec3::new(
|
||||
sphere_pos.x.clamp(bmin.x, bmax.x),
|
||||
sphere_pos.y.clamp(bmin.y, bmax.y),
|
||||
sphere_pos.z.clamp(bmin.z, bmax.z),
|
||||
);
|
||||
|
||||
let diff = sphere_pos - closest;
|
||||
let dist_sq = diff.length_squared();
|
||||
|
||||
// Sphere center outside box
|
||||
if dist_sq > 1e-8 {
|
||||
let dist = dist_sq.sqrt();
|
||||
if dist > radius {
|
||||
return None;
|
||||
}
|
||||
let normal = diff * (-1.0 / dist); // sphere→box direction
|
||||
let depth = radius - dist;
|
||||
let point_on_a = sphere_pos + normal * radius; // sphere surface toward box
|
||||
let point_on_b = closest;
|
||||
return Some((normal, depth, point_on_a, point_on_b));
|
||||
}
|
||||
|
||||
// Sphere center inside box — find nearest face
|
||||
let dx_min = sphere_pos.x - bmin.x;
|
||||
let dx_max = bmax.x - sphere_pos.x;
|
||||
let dy_min = sphere_pos.y - bmin.y;
|
||||
let dy_max = bmax.y - sphere_pos.y;
|
||||
let dz_min = sphere_pos.z - bmin.z;
|
||||
let dz_max = bmax.z - sphere_pos.z;
|
||||
|
||||
let mut min_dist = dx_min;
|
||||
let mut normal = Vec3::new(-1.0, 0.0, 0.0);
|
||||
let mut closest_face = Vec3::new(bmin.x, sphere_pos.y, sphere_pos.z);
|
||||
|
||||
if dx_max < min_dist {
|
||||
min_dist = dx_max;
|
||||
normal = Vec3::new(1.0, 0.0, 0.0);
|
||||
closest_face = Vec3::new(bmax.x, sphere_pos.y, sphere_pos.z);
|
||||
}
|
||||
if dy_min < min_dist {
|
||||
min_dist = dy_min;
|
||||
normal = Vec3::new(0.0, -1.0, 0.0);
|
||||
closest_face = Vec3::new(sphere_pos.x, bmin.y, sphere_pos.z);
|
||||
}
|
||||
if dy_max < min_dist {
|
||||
min_dist = dy_max;
|
||||
normal = Vec3::new(0.0, 1.0, 0.0);
|
||||
closest_face = Vec3::new(sphere_pos.x, bmax.y, sphere_pos.z);
|
||||
}
|
||||
if dz_min < min_dist {
|
||||
min_dist = dz_min;
|
||||
normal = Vec3::new(0.0, 0.0, -1.0);
|
||||
closest_face = Vec3::new(sphere_pos.x, sphere_pos.y, bmin.z);
|
||||
}
|
||||
if dz_max < min_dist {
|
||||
min_dist = dz_max;
|
||||
normal = Vec3::new(0.0, 0.0, 1.0);
|
||||
closest_face = Vec3::new(sphere_pos.x, sphere_pos.y, bmax.z);
|
||||
}
|
||||
|
||||
let depth = min_dist + radius;
|
||||
let point_on_a = sphere_pos + normal * radius;
|
||||
let point_on_b = closest_face;
|
||||
|
||||
Some((normal, depth, point_on_a, point_on_b))
|
||||
}
|
||||
|
||||
pub fn box_vs_box(
|
||||
pos_a: Vec3, half_a: Vec3,
|
||||
pos_b: Vec3, half_b: Vec3,
|
||||
) -> Option<(Vec3, f32, Vec3, Vec3)> {
|
||||
let diff = pos_b - pos_a;
|
||||
|
||||
let overlap_x = (half_a.x + half_b.x) - diff.x.abs();
|
||||
if overlap_x < 0.0 { return None; }
|
||||
|
||||
let overlap_y = (half_a.y + half_b.y) - diff.y.abs();
|
||||
if overlap_y < 0.0 { return None; }
|
||||
|
||||
let overlap_z = (half_a.z + half_b.z) - diff.z.abs();
|
||||
if overlap_z < 0.0 { return None; }
|
||||
|
||||
let (normal, depth) = if overlap_x <= overlap_y && overlap_x <= overlap_z {
|
||||
let sign = if diff.x >= 0.0 { 1.0 } else { -1.0 };
|
||||
(Vec3::new(sign, 0.0, 0.0), overlap_x)
|
||||
} else if overlap_y <= overlap_z {
|
||||
let sign = if diff.y >= 0.0 { 1.0 } else { -1.0 };
|
||||
(Vec3::new(0.0, sign, 0.0), overlap_y)
|
||||
} else {
|
||||
let sign = if diff.z >= 0.0 { 1.0 } else { -1.0 };
|
||||
(Vec3::new(0.0, 0.0, sign), overlap_z)
|
||||
};
|
||||
|
||||
let point_on_a = pos_a + Vec3::new(
|
||||
normal.x * half_a.x,
|
||||
normal.y * half_a.y,
|
||||
normal.z * half_a.z,
|
||||
);
|
||||
let point_on_b = pos_b - Vec3::new(
|
||||
normal.x * half_b.x,
|
||||
normal.y * half_b.y,
|
||||
normal.z * half_b.z,
|
||||
);
|
||||
|
||||
Some((normal, depth, point_on_a, point_on_b))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn approx(a: f32, b: f32) -> bool { (a - b).abs() < 1e-5 }
|
||||
fn approx_vec(a: Vec3, b: Vec3) -> bool { approx(a.x, b.x) && approx(a.y, b.y) && approx(a.z, b.z) }
|
||||
|
||||
// sphere_vs_sphere tests
|
||||
#[test]
|
||||
fn test_sphere_sphere_separated() {
|
||||
let r = sphere_vs_sphere(Vec3::ZERO, 1.0, Vec3::new(5.0, 0.0, 0.0), 1.0);
|
||||
assert!(r.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_sphere_overlapping() {
|
||||
let r = sphere_vs_sphere(Vec3::ZERO, 1.0, Vec3::new(1.5, 0.0, 0.0), 1.0);
|
||||
let (normal, depth, pa, pb) = r.unwrap();
|
||||
assert!(approx_vec(normal, Vec3::X));
|
||||
assert!(approx(depth, 0.5));
|
||||
assert!(approx_vec(pa, Vec3::new(1.0, 0.0, 0.0)));
|
||||
assert!(approx_vec(pb, Vec3::new(0.5, 0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_sphere_touching() {
|
||||
let r = sphere_vs_sphere(Vec3::ZERO, 1.0, Vec3::new(2.0, 0.0, 0.0), 1.0);
|
||||
let (normal, depth, _pa, _pb) = r.unwrap();
|
||||
assert!(approx_vec(normal, Vec3::X));
|
||||
assert!(approx(depth, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_sphere_coincident() {
|
||||
let r = sphere_vs_sphere(Vec3::ZERO, 1.0, Vec3::ZERO, 1.0);
|
||||
let (_normal, depth, _pa, _pb) = r.unwrap();
|
||||
assert!(approx(depth, 2.0));
|
||||
}
|
||||
|
||||
// sphere_vs_box tests
|
||||
#[test]
|
||||
fn test_sphere_box_separated() {
|
||||
let r = sphere_vs_box(Vec3::new(5.0, 0.0, 0.0), 1.0, Vec3::ZERO, Vec3::ONE);
|
||||
assert!(r.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_box_face_overlap() {
|
||||
let r = sphere_vs_box(Vec3::new(1.5, 0.0, 0.0), 1.0, Vec3::ZERO, Vec3::ONE);
|
||||
let (normal, depth, _pa, pb) = r.unwrap();
|
||||
assert!(approx(normal.x, -1.0));
|
||||
assert!(approx(depth, 0.5));
|
||||
assert!(approx(pb.x, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_box_center_inside() {
|
||||
let r = sphere_vs_box(Vec3::ZERO, 0.5, Vec3::ZERO, Vec3::ONE);
|
||||
assert!(r.is_some());
|
||||
let (_normal, depth, _pa, _pb) = r.unwrap();
|
||||
assert!(depth > 0.0);
|
||||
}
|
||||
|
||||
// box_vs_box tests
|
||||
#[test]
|
||||
fn test_box_box_separated() {
|
||||
let r = box_vs_box(Vec3::ZERO, Vec3::ONE, Vec3::new(5.0, 0.0, 0.0), Vec3::ONE);
|
||||
assert!(r.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_box_overlapping() {
|
||||
let r = box_vs_box(Vec3::ZERO, Vec3::ONE, Vec3::new(1.5, 0.0, 0.0), Vec3::ONE);
|
||||
let (normal, depth, _pa, _pb) = r.unwrap();
|
||||
assert!(approx_vec(normal, Vec3::X));
|
||||
assert!(approx(depth, 0.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_box_touching() {
|
||||
let r = box_vs_box(Vec3::ZERO, Vec3::ONE, Vec3::new(2.0, 0.0, 0.0), Vec3::ONE);
|
||||
let (_normal, depth, _pa, _pb) = r.unwrap();
|
||||
assert!(approx(depth, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_box_y_axis() {
|
||||
let r = box_vs_box(Vec3::ZERO, Vec3::ONE, Vec3::new(0.0, 1.5, 0.0), Vec3::ONE);
|
||||
let (normal, depth, _pa, _pb) = r.unwrap();
|
||||
assert!(approx_vec(normal, Vec3::Y));
|
||||
assert!(approx(depth, 0.5));
|
||||
}
|
||||
}
|
||||
204
crates/voltex_physics/src/ray.rs
Normal file
204
crates/voltex_physics/src/ray.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use voltex_math::{Vec3, Ray, AABB};
|
||||
|
||||
/// Ray vs AABB (slab method). Returns t of nearest intersection, or None.
|
||||
/// If ray starts inside AABB, returns Some(0.0).
|
||||
pub fn ray_vs_aabb(ray: &Ray, aabb: &AABB) -> Option<f32> {
|
||||
let mut t_min = f32::NEG_INFINITY;
|
||||
let mut t_max = f32::INFINITY;
|
||||
|
||||
let o = [ray.origin.x, ray.origin.y, ray.origin.z];
|
||||
let d = [ray.direction.x, ray.direction.y, ray.direction.z];
|
||||
let bmin = [aabb.min.x, aabb.min.y, aabb.min.z];
|
||||
let bmax = [aabb.max.x, aabb.max.y, aabb.max.z];
|
||||
|
||||
for i in 0..3 {
|
||||
if d[i].abs() < 1e-8 {
|
||||
if o[i] < bmin[i] || o[i] > bmax[i] {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
let inv_d = 1.0 / d[i];
|
||||
let mut t1 = (bmin[i] - o[i]) * inv_d;
|
||||
let mut t2 = (bmax[i] - o[i]) * inv_d;
|
||||
if t1 > t2 {
|
||||
std::mem::swap(&mut t1, &mut t2);
|
||||
}
|
||||
t_min = t_min.max(t1);
|
||||
t_max = t_max.min(t2);
|
||||
if t_min > t_max {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t_max < 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(t_min.max(0.0))
|
||||
}
|
||||
|
||||
/// Ray vs Sphere. Returns (t, normal) or None.
|
||||
pub fn ray_vs_sphere(ray: &Ray, center: Vec3, radius: f32) -> Option<(f32, Vec3)> {
|
||||
let oc = ray.origin - center;
|
||||
let a = ray.direction.dot(ray.direction);
|
||||
let b = 2.0 * oc.dot(ray.direction);
|
||||
let c = oc.dot(oc) - radius * radius;
|
||||
let discriminant = b * b - 4.0 * a * c;
|
||||
|
||||
if discriminant < 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sqrt_d = discriminant.sqrt();
|
||||
let mut t = (-b - sqrt_d) / (2.0 * a);
|
||||
|
||||
if t < 0.0 {
|
||||
t = (-b + sqrt_d) / (2.0 * a);
|
||||
if t < 0.0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let point = ray.at(t);
|
||||
let normal = (point - center).normalize();
|
||||
Some((t, normal))
|
||||
}
|
||||
|
||||
/// Ray vs axis-aligned Box. Returns (t, normal) or None.
|
||||
pub fn ray_vs_box(ray: &Ray, center: Vec3, half_extents: Vec3) -> Option<(f32, Vec3)> {
|
||||
let aabb = AABB::from_center_half_extents(center, half_extents);
|
||||
let t = ray_vs_aabb(ray, &aabb)?;
|
||||
|
||||
if t == 0.0 {
|
||||
// Ray starts inside box
|
||||
let bmin = aabb.min;
|
||||
let bmax = aabb.max;
|
||||
let p = ray.origin;
|
||||
|
||||
let faces: [(f32, Vec3); 6] = [
|
||||
(p.x - bmin.x, Vec3::new(-1.0, 0.0, 0.0)),
|
||||
(bmax.x - p.x, Vec3::new(1.0, 0.0, 0.0)),
|
||||
(p.y - bmin.y, Vec3::new(0.0, -1.0, 0.0)),
|
||||
(bmax.y - p.y, Vec3::new(0.0, 1.0, 0.0)),
|
||||
(p.z - bmin.z, Vec3::new(0.0, 0.0, -1.0)),
|
||||
(bmax.z - p.z, Vec3::new(0.0, 0.0, 1.0)),
|
||||
];
|
||||
|
||||
let mut min_dist = f32::INFINITY;
|
||||
let mut normal = Vec3::Y;
|
||||
for (dist, n) in &faces {
|
||||
if *dist < min_dist {
|
||||
min_dist = *dist;
|
||||
normal = *n;
|
||||
}
|
||||
}
|
||||
return Some((0.0, normal));
|
||||
}
|
||||
|
||||
let hit = ray.at(t);
|
||||
let rel = hit - center;
|
||||
let hx = half_extents.x;
|
||||
let hy = half_extents.y;
|
||||
let hz = half_extents.z;
|
||||
|
||||
let normal = if (rel.x - hx).abs() < 1e-4 {
|
||||
Vec3::X
|
||||
} else if (rel.x + hx).abs() < 1e-4 {
|
||||
Vec3::new(-1.0, 0.0, 0.0)
|
||||
} else if (rel.y - hy).abs() < 1e-4 {
|
||||
Vec3::Y
|
||||
} else if (rel.y + hy).abs() < 1e-4 {
|
||||
Vec3::new(0.0, -1.0, 0.0)
|
||||
} else if (rel.z - hz).abs() < 1e-4 {
|
||||
Vec3::Z
|
||||
} else {
|
||||
Vec3::new(0.0, 0.0, -1.0)
|
||||
};
|
||||
|
||||
Some((t, normal))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn approx(a: f32, b: f32) -> bool {
|
||||
(a - b).abs() < 1e-4
|
||||
}
|
||||
|
||||
fn approx_vec(a: Vec3, b: Vec3) -> bool {
|
||||
approx(a.x, b.x) && approx(a.y, b.y) && approx(a.z, b.z)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aabb_hit() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 0.0, 0.0), Vec3::X);
|
||||
let aabb = AABB::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::ONE);
|
||||
let t = ray_vs_aabb(&ray, &aabb).unwrap();
|
||||
assert!(approx(t, 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aabb_miss() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 5.0, 0.0), Vec3::X);
|
||||
let aabb = AABB::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::ONE);
|
||||
assert!(ray_vs_aabb(&ray, &aabb).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aabb_inside() {
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let aabb = AABB::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::ONE);
|
||||
let t = ray_vs_aabb(&ray, &aabb).unwrap();
|
||||
assert!(approx(t, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_hit() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 0.0, 0.0), Vec3::X);
|
||||
let (t, normal) = ray_vs_sphere(&ray, Vec3::ZERO, 1.0).unwrap();
|
||||
assert!(approx(t, 4.0));
|
||||
assert!(approx_vec(normal, Vec3::new(-1.0, 0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_miss() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 5.0, 0.0), Vec3::X);
|
||||
assert!(ray_vs_sphere(&ray, Vec3::ZERO, 1.0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_tangent() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 1.0, 0.0), Vec3::X);
|
||||
assert!(ray_vs_sphere(&ray, Vec3::ZERO, 1.0).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sphere_inside() {
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let (t, _normal) = ray_vs_sphere(&ray, Vec3::ZERO, 2.0).unwrap();
|
||||
assert!(approx(t, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_hit_face() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 0.0, 0.0), Vec3::X);
|
||||
let (t, normal) = ray_vs_box(&ray, Vec3::ZERO, Vec3::ONE).unwrap();
|
||||
assert!(approx(t, 4.0));
|
||||
assert!(approx_vec(normal, Vec3::new(-1.0, 0.0, 0.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_miss() {
|
||||
let ray = Ray::new(Vec3::new(-5.0, 5.0, 0.0), Vec3::X);
|
||||
assert!(ray_vs_box(&ray, Vec3::ZERO, Vec3::ONE).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box_inside() {
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let (t, _normal) = ray_vs_box(&ray, Vec3::ZERO, Vec3::ONE).unwrap();
|
||||
assert!(approx(t, 0.0));
|
||||
}
|
||||
}
|
||||
168
crates/voltex_physics/src/raycast.rs
Normal file
168
crates/voltex_physics/src/raycast.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use voltex_ecs::{World, Entity};
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::{Vec3, Ray};
|
||||
|
||||
use crate::collider::Collider;
|
||||
use crate::ray as ray_tests;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RayHit {
|
||||
pub entity: Entity,
|
||||
pub t: f32,
|
||||
pub point: Vec3,
|
||||
pub normal: Vec3,
|
||||
}
|
||||
|
||||
pub fn raycast(world: &World, ray: &Ray, max_dist: f32) -> Option<RayHit> {
|
||||
let entities: Vec<(Entity, Vec3, Collider)> = world
|
||||
.query2::<Transform, Collider>()
|
||||
.into_iter()
|
||||
.map(|(e, t, c)| (e, t.position, *c))
|
||||
.collect();
|
||||
|
||||
if entities.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut closest: Option<RayHit> = None;
|
||||
|
||||
for (entity, pos, collider) in &entities {
|
||||
let aabb = collider.aabb(*pos);
|
||||
|
||||
// Broad phase: ray vs AABB
|
||||
let aabb_t = match ray_tests::ray_vs_aabb(ray, &aabb) {
|
||||
Some(t) if t <= max_dist => t,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Early skip if we already have a closer hit
|
||||
if let Some(ref hit) = closest {
|
||||
if aabb_t >= hit.t {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Narrow phase
|
||||
let result = match collider {
|
||||
Collider::Sphere { radius } => {
|
||||
ray_tests::ray_vs_sphere(ray, *pos, *radius)
|
||||
}
|
||||
Collider::Box { half_extents } => {
|
||||
ray_tests::ray_vs_box(ray, *pos, *half_extents)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((t, normal)) = result {
|
||||
if t <= max_dist {
|
||||
if closest.is_none() || t < closest.as_ref().unwrap().t {
|
||||
closest = Some(RayHit {
|
||||
entity: *entity,
|
||||
t,
|
||||
point: ray.at(t),
|
||||
normal,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use voltex_ecs::World;
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
use crate::Collider;
|
||||
|
||||
fn approx(a: f32, b: f32) -> bool {
|
||||
(a - b).abs() < 1e-3
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_world() {
|
||||
let world = World::new();
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
assert!(raycast(&world, &ray, 100.0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hit_sphere() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::new(5.0, 0.0, 0.0)));
|
||||
world.add(e, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let hit = raycast(&world, &ray, 100.0).unwrap();
|
||||
|
||||
assert_eq!(hit.entity, e);
|
||||
assert!(approx(hit.t, 4.0));
|
||||
assert!(approx(hit.point.x, 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closest_of_multiple() {
|
||||
let mut world = World::new();
|
||||
|
||||
let far = world.spawn();
|
||||
world.add(far, Transform::from_position(Vec3::new(10.0, 0.0, 0.0)));
|
||||
world.add(far, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let near = world.spawn();
|
||||
world.add(near, Transform::from_position(Vec3::new(3.0, 0.0, 0.0)));
|
||||
world.add(near, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let hit = raycast(&world, &ray, 100.0).unwrap();
|
||||
|
||||
assert_eq!(hit.entity, near);
|
||||
assert!(approx(hit.t, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_dist() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::new(50.0, 0.0, 0.0)));
|
||||
world.add(e, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
assert!(raycast(&world, &ray, 10.0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hit_box() {
|
||||
let mut world = World::new();
|
||||
let e = world.spawn();
|
||||
world.add(e, Transform::from_position(Vec3::new(5.0, 0.0, 0.0)));
|
||||
world.add(e, Collider::Box { half_extents: Vec3::ONE });
|
||||
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let hit = raycast(&world, &ray, 100.0).unwrap();
|
||||
|
||||
assert_eq!(hit.entity, e);
|
||||
assert!(approx(hit.t, 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_sphere_box() {
|
||||
let mut world = World::new();
|
||||
|
||||
let sphere = world.spawn();
|
||||
world.add(sphere, Transform::from_position(Vec3::new(10.0, 0.0, 0.0)));
|
||||
world.add(sphere, Collider::Sphere { radius: 1.0 });
|
||||
|
||||
let box_e = world.spawn();
|
||||
world.add(box_e, Transform::from_position(Vec3::new(3.0, 0.0, 0.0)));
|
||||
world.add(box_e, Collider::Box { half_extents: Vec3::ONE });
|
||||
|
||||
let ray = Ray::new(Vec3::ZERO, Vec3::X);
|
||||
let hit = raycast(&world, &ray, 100.0).unwrap();
|
||||
|
||||
assert_eq!(hit.entity, box_e);
|
||||
assert!(approx(hit.t, 2.0));
|
||||
}
|
||||
}
|
||||
86
crates/voltex_physics/src/rigid_body.rs
Normal file
86
crates/voltex_physics/src/rigid_body.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use voltex_math::Vec3;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RigidBody {
|
||||
pub velocity: Vec3,
|
||||
pub angular_velocity: Vec3,
|
||||
pub mass: f32,
|
||||
pub restitution: f32,
|
||||
pub gravity_scale: f32,
|
||||
}
|
||||
|
||||
impl RigidBody {
|
||||
pub fn dynamic(mass: f32) -> Self {
|
||||
Self {
|
||||
velocity: Vec3::ZERO,
|
||||
angular_velocity: Vec3::ZERO,
|
||||
mass,
|
||||
restitution: 0.3,
|
||||
gravity_scale: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn statik() -> Self {
|
||||
Self {
|
||||
velocity: Vec3::ZERO,
|
||||
angular_velocity: Vec3::ZERO,
|
||||
mass: 0.0,
|
||||
restitution: 0.3,
|
||||
gravity_scale: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inv_mass(&self) -> f32 {
|
||||
if self.mass == 0.0 { 0.0 } else { 1.0 / self.mass }
|
||||
}
|
||||
|
||||
pub fn is_static(&self) -> bool {
|
||||
self.mass == 0.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhysicsConfig {
|
||||
pub gravity: Vec3,
|
||||
pub fixed_dt: f32,
|
||||
}
|
||||
|
||||
impl Default for PhysicsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gravity: Vec3::new(0.0, -9.81, 0.0),
|
||||
fixed_dt: 1.0 / 60.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dynamic_body() {
|
||||
let rb = RigidBody::dynamic(2.0);
|
||||
assert_eq!(rb.mass, 2.0);
|
||||
assert!(!rb.is_static());
|
||||
assert!((rb.inv_mass() - 0.5).abs() < 1e-6);
|
||||
assert_eq!(rb.velocity, Vec3::ZERO);
|
||||
assert_eq!(rb.restitution, 0.3);
|
||||
assert_eq!(rb.gravity_scale, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_body() {
|
||||
let rb = RigidBody::statik();
|
||||
assert_eq!(rb.mass, 0.0);
|
||||
assert!(rb.is_static());
|
||||
assert_eq!(rb.inv_mass(), 0.0);
|
||||
assert_eq!(rb.gravity_scale, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_physics_config_default() {
|
||||
let cfg = PhysicsConfig::default();
|
||||
assert!((cfg.gravity.y - (-9.81)).abs() < 1e-6);
|
||||
assert!((cfg.fixed_dt - 1.0 / 60.0).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
227
crates/voltex_physics/src/solver.rs
Normal file
227
crates/voltex_physics/src/solver.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
use voltex_ecs::{World, Entity};
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
|
||||
use crate::contact::ContactPoint;
|
||||
use crate::rigid_body::{RigidBody, PhysicsConfig};
|
||||
use crate::collision::detect_collisions;
|
||||
use crate::integrator::integrate;
|
||||
|
||||
const POSITION_SLOP: f32 = 0.01;
|
||||
const POSITION_PERCENT: f32 = 0.4;
|
||||
|
||||
pub fn resolve_collisions(world: &mut World, contacts: &[ContactPoint]) {
|
||||
let mut velocity_changes: Vec<(Entity, Vec3)> = Vec::new();
|
||||
let mut position_changes: Vec<(Entity, Vec3)> = Vec::new();
|
||||
|
||||
for contact in contacts {
|
||||
let rb_a = world.get::<RigidBody>(contact.entity_a).copied();
|
||||
let rb_b = world.get::<RigidBody>(contact.entity_b).copied();
|
||||
|
||||
let (rb_a, rb_b) = match (rb_a, rb_b) {
|
||||
(Some(a), Some(b)) => (a, b),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let inv_mass_a = rb_a.inv_mass();
|
||||
let inv_mass_b = rb_b.inv_mass();
|
||||
let inv_mass_sum = inv_mass_a + inv_mass_b;
|
||||
|
||||
if inv_mass_sum == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let v_rel = rb_a.velocity - rb_b.velocity;
|
||||
let v_rel_n = v_rel.dot(contact.normal);
|
||||
|
||||
// normal points A→B; v_rel_n > 0 means A approaches B → apply impulse
|
||||
if v_rel_n > 0.0 {
|
||||
let e = rb_a.restitution.min(rb_b.restitution);
|
||||
let j = (1.0 + e) * v_rel_n / inv_mass_sum;
|
||||
|
||||
velocity_changes.push((contact.entity_a, contact.normal * (-j * inv_mass_a)));
|
||||
velocity_changes.push((contact.entity_b, contact.normal * (j * inv_mass_b)));
|
||||
}
|
||||
|
||||
let correction_mag = (contact.depth - POSITION_SLOP).max(0.0) * POSITION_PERCENT / inv_mass_sum;
|
||||
if correction_mag > 0.0 {
|
||||
let correction = contact.normal * correction_mag;
|
||||
position_changes.push((contact.entity_a, correction * (-inv_mass_a)));
|
||||
position_changes.push((contact.entity_b, correction * inv_mass_b));
|
||||
}
|
||||
}
|
||||
|
||||
for (entity, dv) in velocity_changes {
|
||||
if let Some(rb) = world.get_mut::<RigidBody>(entity) {
|
||||
rb.velocity = rb.velocity + dv;
|
||||
}
|
||||
}
|
||||
|
||||
for (entity, dp) in position_changes {
|
||||
if let Some(t) = world.get_mut::<Transform>(entity) {
|
||||
t.position = t.position + dp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn physics_step(world: &mut World, config: &PhysicsConfig) {
|
||||
integrate(world, config);
|
||||
let contacts = detect_collisions(world);
|
||||
resolve_collisions(world, &contacts);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use voltex_ecs::World;
|
||||
use voltex_ecs::Transform;
|
||||
use voltex_math::Vec3;
|
||||
use crate::{Collider, RigidBody};
|
||||
use crate::collision::detect_collisions;
|
||||
|
||||
fn approx(a: f32, b: f32) -> bool {
|
||||
(a - b).abs() < 1e-3
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_dynamic_spheres_head_on() {
|
||||
let mut world = World::new();
|
||||
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::new(-0.5, 0.0, 0.0)));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
let mut rb_a = RigidBody::dynamic(1.0);
|
||||
rb_a.velocity = Vec3::new(1.0, 0.0, 0.0);
|
||||
rb_a.restitution = 1.0;
|
||||
rb_a.gravity_scale = 0.0;
|
||||
world.add(a, rb_a);
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(0.5, 0.0, 0.0)));
|
||||
world.add(b, Collider::Sphere { radius: 1.0 });
|
||||
let mut rb_b = RigidBody::dynamic(1.0);
|
||||
rb_b.velocity = Vec3::new(-1.0, 0.0, 0.0);
|
||||
rb_b.restitution = 1.0;
|
||||
rb_b.gravity_scale = 0.0;
|
||||
world.add(b, rb_b);
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
|
||||
resolve_collisions(&mut world, &contacts);
|
||||
|
||||
let va = world.get::<RigidBody>(a).unwrap().velocity;
|
||||
let vb = world.get::<RigidBody>(b).unwrap().velocity;
|
||||
|
||||
assert!(approx(va.x, -1.0));
|
||||
assert!(approx(vb.x, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dynamic_vs_static_floor() {
|
||||
let mut world = World::new();
|
||||
|
||||
let ball = world.spawn();
|
||||
world.add(ball, Transform::from_position(Vec3::new(0.0, 0.5, 0.0)));
|
||||
world.add(ball, Collider::Sphere { radius: 1.0 });
|
||||
let mut rb = RigidBody::dynamic(1.0);
|
||||
rb.velocity = Vec3::new(0.0, -2.0, 0.0);
|
||||
rb.restitution = 1.0;
|
||||
rb.gravity_scale = 0.0;
|
||||
world.add(ball, rb);
|
||||
|
||||
let floor = world.spawn();
|
||||
world.add(floor, Transform::from_position(Vec3::new(0.0, -1.0, 0.0)));
|
||||
world.add(floor, Collider::Box { half_extents: Vec3::new(10.0, 1.0, 10.0) });
|
||||
world.add(floor, RigidBody::statik());
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
|
||||
resolve_collisions(&mut world, &contacts);
|
||||
|
||||
let ball_rb = world.get::<RigidBody>(ball).unwrap();
|
||||
let floor_rb = world.get::<RigidBody>(floor).unwrap();
|
||||
|
||||
assert!(ball_rb.velocity.y > 0.0);
|
||||
assert!(approx(floor_rb.velocity.y, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_position_correction() {
|
||||
let mut world = World::new();
|
||||
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
let mut rb_a = RigidBody::dynamic(1.0);
|
||||
rb_a.gravity_scale = 0.0;
|
||||
world.add(a, rb_a);
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(1.0, 0.0, 0.0)));
|
||||
world.add(b, Collider::Sphere { radius: 1.0 });
|
||||
let mut rb_b = RigidBody::dynamic(1.0);
|
||||
rb_b.gravity_scale = 0.0;
|
||||
world.add(b, rb_b);
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
assert_eq!(contacts.len(), 1);
|
||||
|
||||
resolve_collisions(&mut world, &contacts);
|
||||
|
||||
let pa = world.get::<Transform>(a).unwrap().position;
|
||||
let pb = world.get::<Transform>(b).unwrap().position;
|
||||
|
||||
let dist = (pb - pa).length();
|
||||
assert!(dist > 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_physics_step_ball_drop() {
|
||||
let mut world = World::new();
|
||||
|
||||
let ball = world.spawn();
|
||||
world.add(ball, Transform::from_position(Vec3::new(0.0, 5.0, 0.0)));
|
||||
world.add(ball, Collider::Sphere { radius: 0.5 });
|
||||
world.add(ball, RigidBody::dynamic(1.0));
|
||||
|
||||
let floor = world.spawn();
|
||||
world.add(floor, Transform::from_position(Vec3::new(0.0, -1.0, 0.0)));
|
||||
world.add(floor, Collider::Box { half_extents: Vec3::new(10.0, 1.0, 10.0) });
|
||||
world.add(floor, RigidBody::statik());
|
||||
|
||||
let config = PhysicsConfig::default();
|
||||
|
||||
for _ in 0..10 {
|
||||
physics_step(&mut world, &config);
|
||||
}
|
||||
|
||||
let t = world.get::<Transform>(ball).unwrap();
|
||||
assert!(t.position.y < 5.0);
|
||||
assert!(t.position.y > -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_both_static_no_response() {
|
||||
let mut world = World::new();
|
||||
|
||||
let a = world.spawn();
|
||||
world.add(a, Transform::from_position(Vec3::ZERO));
|
||||
world.add(a, Collider::Sphere { radius: 1.0 });
|
||||
world.add(a, RigidBody::statik());
|
||||
|
||||
let b = world.spawn();
|
||||
world.add(b, Transform::from_position(Vec3::new(0.5, 0.0, 0.0)));
|
||||
world.add(b, Collider::Sphere { radius: 1.0 });
|
||||
world.add(b, RigidBody::statik());
|
||||
|
||||
let contacts = detect_collisions(&world);
|
||||
resolve_collisions(&mut world, &contacts);
|
||||
|
||||
let pa = world.get::<Transform>(a).unwrap().position;
|
||||
let pb = world.get::<Transform>(b).unwrap().position;
|
||||
assert!(approx(pa.x, 0.0));
|
||||
assert!(approx(pb.x, 0.5));
|
||||
}
|
||||
}
|
||||
145
crates/voltex_renderer/src/bloom.rs
Normal file
145
crates/voltex_renderer/src/bloom.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use crate::hdr::HDR_FORMAT;
|
||||
|
||||
/// Number of bloom mip levels (downsample + upsample chain).
|
||||
pub const BLOOM_MIP_COUNT: usize = 5;
|
||||
|
||||
/// Uniform buffer for the bloom pass.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct BloomUniform {
|
||||
/// Luminance threshold above which a pixel contributes to bloom.
|
||||
pub threshold: f32,
|
||||
/// Soft knee for the threshold.
|
||||
pub soft_threshold: f32,
|
||||
pub _padding: [f32; 2],
|
||||
}
|
||||
|
||||
impl Default for BloomUniform {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
threshold: 1.0,
|
||||
soft_threshold: 0.5,
|
||||
_padding: [0.0; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GPU resources for the bloom pass (mip chain + uniform buffer).
|
||||
pub struct BloomResources {
|
||||
/// One `TextureView` per mip level (5 levels).
|
||||
pub mip_views: Vec<wgpu::TextureView>,
|
||||
pub uniform_buffer: wgpu::Buffer,
|
||||
/// Blend intensity applied during the tonemap pass.
|
||||
pub intensity: f32,
|
||||
}
|
||||
|
||||
impl BloomResources {
|
||||
pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
|
||||
let mip_views = create_mip_views(device, width, height);
|
||||
|
||||
let _uniform = BloomUniform::default();
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Bloom Uniform Buffer"),
|
||||
size: std::mem::size_of::<BloomUniform>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
// Initialize with defaults via a write at construction time would require a queue;
|
||||
// callers may write the buffer themselves. The buffer is left zero-initialised here.
|
||||
|
||||
Self {
|
||||
mip_views,
|
||||
uniform_buffer,
|
||||
intensity: 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
/// Recreate the mip-chain textures when the window is resized.
|
||||
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
||||
self.mip_views = create_mip_views(device, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
/// Return the (width, height) of each bloom mip level.
|
||||
///
|
||||
/// Level 0 = width/2 × height/2; each subsequent level halves again.
|
||||
/// Sizes are clamped to a minimum of 1.
|
||||
pub fn mip_sizes(width: u32, height: u32) -> Vec<(u32, u32)> {
|
||||
let mut sizes = Vec::with_capacity(BLOOM_MIP_COUNT);
|
||||
let mut w = (width / 2).max(1);
|
||||
let mut h = (height / 2).max(1);
|
||||
for _ in 0..BLOOM_MIP_COUNT {
|
||||
sizes.push((w, h));
|
||||
w = (w / 2).max(1);
|
||||
h = (h / 2).max(1);
|
||||
}
|
||||
sizes
|
||||
}
|
||||
|
||||
// ── Internal helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
fn create_mip_views(device: &wgpu::Device, width: u32, height: u32) -> Vec<wgpu::TextureView> {
|
||||
let sizes = mip_sizes(width, height);
|
||||
sizes
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (w, h))| {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some(&format!("Bloom Mip {} Texture", i)),
|
||||
size: wgpu::Extent3d {
|
||||
width: w,
|
||||
height: h,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: HDR_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mip_sizes_1920_1080() {
|
||||
let sizes = mip_sizes(1920, 1080);
|
||||
assert_eq!(sizes.len(), BLOOM_MIP_COUNT);
|
||||
assert_eq!(sizes[0], (960, 540));
|
||||
assert_eq!(sizes[1], (480, 270));
|
||||
assert_eq!(sizes[2], (240, 135));
|
||||
assert_eq!(sizes[3], (120, 67));
|
||||
assert_eq!(sizes[4], (60, 33));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mip_sizes_64_64() {
|
||||
let sizes = mip_sizes(64, 64);
|
||||
assert_eq!(sizes.len(), BLOOM_MIP_COUNT);
|
||||
assert_eq!(sizes[0], (32, 32));
|
||||
assert_eq!(sizes[1], (16, 16));
|
||||
assert_eq!(sizes[2], (8, 8));
|
||||
assert_eq!(sizes[3], (4, 4));
|
||||
assert_eq!(sizes[4], (2, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bloom_uniform_default() {
|
||||
let u = BloomUniform::default();
|
||||
assert!((u.threshold - 1.0).abs() < f32::EPSILON);
|
||||
assert!((u.soft_threshold - 0.5).abs() < f32::EPSILON);
|
||||
assert_eq!(u._padding, [0.0f32; 2]);
|
||||
}
|
||||
}
|
||||
112
crates/voltex_renderer/src/bloom_shader.wgsl
Normal file
112
crates/voltex_renderer/src/bloom_shader.wgsl
Normal file
@@ -0,0 +1,112 @@
|
||||
// Bloom pass shader.
|
||||
// Two fragment entry points:
|
||||
// fs_downsample — 5-tap box filter with optional bright extraction
|
||||
// fs_upsample — 9-tap tent filter for the upsample chain
|
||||
|
||||
// ── Group 0 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@group(0) @binding(0) var t_input: texture_2d<f32>;
|
||||
@group(0) @binding(1) var s_input: sampler;
|
||||
|
||||
struct BloomUniform {
|
||||
threshold: f32,
|
||||
soft_threshold: f32,
|
||||
_padding: vec2<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(2) var<uniform> bloom: BloomUniform;
|
||||
|
||||
// ── Vertex stage ──────────────────────────────────────────────────────────────
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(v: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = vec4<f32>(v.position, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(v.position.x * 0.5 + 0.5, 1.0 - (v.position.y * 0.5 + 0.5));
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── Shared helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
fn luminance(c: vec3<f32>) -> f32 {
|
||||
return dot(c, vec3<f32>(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
/// Soft-knee bright-pass: returns the colour with sub-threshold values attenuated.
|
||||
fn bright_extract(color: vec3<f32>, threshold: f32, soft: f32) -> vec3<f32> {
|
||||
let lum = luminance(color);
|
||||
let knee = threshold * soft;
|
||||
// Quadratic soft knee
|
||||
let ramp = clamp(lum - threshold + knee, 0.0, 2.0 * knee);
|
||||
let weight = select(
|
||||
select(0.0, 1.0, lum >= threshold),
|
||||
ramp * ramp / (4.0 * knee + 0.00001),
|
||||
lum >= threshold - knee && lum < threshold,
|
||||
);
|
||||
// When lum >= threshold we pass through, otherwise fade via weight
|
||||
if lum >= threshold {
|
||||
return color;
|
||||
}
|
||||
return color * (weight / max(lum, 0.00001));
|
||||
}
|
||||
|
||||
// ── Downsample pass ───────────────────────────────────────────────────────────
|
||||
|
||||
/// 5-sample box filter: centre × 4 + 4 diagonal neighbours × 1, divided by 8.
|
||||
/// When bloom.threshold > 0 a bright-pass is applied after filtering.
|
||||
@fragment
|
||||
fn fs_downsample(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let uv = in.uv;
|
||||
let texel = 1.0 / vec2<f32>(textureDimensions(t_input));
|
||||
|
||||
let center = textureSample(t_input, s_input, uv).rgb;
|
||||
let tl = textureSample(t_input, s_input, uv + vec2<f32>(-1.0, -1.0) * texel).rgb;
|
||||
let tr = textureSample(t_input, s_input, uv + vec2<f32>( 1.0, -1.0) * texel).rgb;
|
||||
let bl = textureSample(t_input, s_input, uv + vec2<f32>(-1.0, 1.0) * texel).rgb;
|
||||
let br = textureSample(t_input, s_input, uv + vec2<f32>( 1.0, 1.0) * texel).rgb;
|
||||
|
||||
var color = (center * 4.0 + tl + tr + bl + br) / 8.0;
|
||||
|
||||
// Bright extraction on the first downsample level (threshold guard)
|
||||
if bloom.threshold > 0.0 {
|
||||
color = bright_extract(color, bloom.threshold, bloom.soft_threshold);
|
||||
}
|
||||
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
|
||||
// ── Upsample pass ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// 9-tap tent filter (3×3):
|
||||
/// corners × 1, axis-aligned edges × 2, centre × 4 → /16
|
||||
@fragment
|
||||
fn fs_upsample(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let uv = in.uv;
|
||||
let texel = 1.0 / vec2<f32>(textureDimensions(t_input));
|
||||
|
||||
let tl = textureSample(t_input, s_input, uv + vec2<f32>(-1.0, -1.0) * texel).rgb;
|
||||
let tc = textureSample(t_input, s_input, uv + vec2<f32>( 0.0, -1.0) * texel).rgb;
|
||||
let tr = textureSample(t_input, s_input, uv + vec2<f32>( 1.0, -1.0) * texel).rgb;
|
||||
let ml = textureSample(t_input, s_input, uv + vec2<f32>(-1.0, 0.0) * texel).rgb;
|
||||
let mc = textureSample(t_input, s_input, uv).rgb;
|
||||
let mr = textureSample(t_input, s_input, uv + vec2<f32>( 1.0, 0.0) * texel).rgb;
|
||||
let bl = textureSample(t_input, s_input, uv + vec2<f32>(-1.0, 1.0) * texel).rgb;
|
||||
let bc = textureSample(t_input, s_input, uv + vec2<f32>( 0.0, 1.0) * texel).rgb;
|
||||
let br = textureSample(t_input, s_input, uv + vec2<f32>( 1.0, 1.0) * texel).rgb;
|
||||
|
||||
// Tent weights: corners×1 + edges×2 + centre×4 → sum=16
|
||||
let color = (tl + tr + bl + br
|
||||
+ (tc + ml + mr + bc) * 2.0
|
||||
+ mc * 4.0) / 16.0;
|
||||
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
94
crates/voltex_renderer/src/deferred_gbuffer.wgsl
Normal file
94
crates/voltex_renderer/src/deferred_gbuffer.wgsl
Normal file
@@ -0,0 +1,94 @@
|
||||
// G-Buffer pass shader for deferred rendering.
|
||||
// Writes geometry data to multiple render targets.
|
||||
|
||||
struct CameraUniform {
|
||||
view_proj: mat4x4<f32>,
|
||||
model: mat4x4<f32>,
|
||||
camera_pos: vec3<f32>,
|
||||
};
|
||||
|
||||
struct MaterialUniform {
|
||||
base_color: vec4<f32>,
|
||||
metallic: f32,
|
||||
roughness: f32,
|
||||
ao: f32,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> camera: CameraUniform;
|
||||
|
||||
@group(1) @binding(0) var t_albedo: texture_2d<f32>;
|
||||
@group(1) @binding(1) var s_albedo: sampler;
|
||||
@group(1) @binding(2) var t_normal: texture_2d<f32>;
|
||||
@group(1) @binding(3) var s_normal: sampler;
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: MaterialUniform;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) normal: vec3<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) tangent: vec4<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_pos: vec3<f32>,
|
||||
@location(1) world_normal: vec3<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
@location(3) world_tangent: vec3<f32>,
|
||||
@location(4) world_bitangent: vec3<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(v: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
let world_pos4 = camera.model * vec4<f32>(v.position, 1.0);
|
||||
out.world_pos = world_pos4.xyz;
|
||||
out.clip_position = camera.view_proj * world_pos4;
|
||||
out.uv = v.uv;
|
||||
|
||||
let N = normalize((camera.model * vec4<f32>(v.normal, 0.0)).xyz);
|
||||
let T = normalize((camera.model * vec4<f32>(v.tangent.xyz, 0.0)).xyz);
|
||||
let B = cross(N, T) * v.tangent.w;
|
||||
|
||||
out.world_normal = N;
|
||||
out.world_tangent = T;
|
||||
out.world_bitangent = B;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
struct GBufferOutput {
|
||||
@location(0) position: vec4<f32>,
|
||||
@location(1) normal: vec4<f32>,
|
||||
@location(2) albedo: vec4<f32>,
|
||||
@location(3) material_data: vec4<f32>,
|
||||
};
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> GBufferOutput {
|
||||
// Sample albedo texture
|
||||
let tex_color = textureSample(t_albedo, s_albedo, in.uv);
|
||||
let albedo = material.base_color.rgb * tex_color.rgb;
|
||||
|
||||
// Normal mapping via TBN matrix
|
||||
let T = normalize(in.world_tangent);
|
||||
let B = normalize(in.world_bitangent);
|
||||
let N_geom = normalize(in.world_normal);
|
||||
|
||||
let normal_sample = textureSample(t_normal, s_normal, in.uv).rgb;
|
||||
let tangent_normal = normal_sample * 2.0 - 1.0;
|
||||
|
||||
// TBN: tangent space → world space
|
||||
let TBN = mat3x3<f32>(T, B, N_geom);
|
||||
let N = normalize(TBN * tangent_normal);
|
||||
|
||||
var out: GBufferOutput;
|
||||
out.position = vec4<f32>(in.world_pos, 1.0);
|
||||
out.normal = vec4<f32>(N * 0.5 + 0.5, 1.0);
|
||||
out.albedo = vec4<f32>(albedo, material.base_color.a * tex_color.a);
|
||||
out.material_data = vec4<f32>(material.metallic, material.roughness, material.ao, 1.0);
|
||||
|
||||
return out;
|
||||
}
|
||||
312
crates/voltex_renderer/src/deferred_lighting.wgsl
Normal file
312
crates/voltex_renderer/src/deferred_lighting.wgsl
Normal file
@@ -0,0 +1,312 @@
|
||||
// Deferred lighting pass shader.
|
||||
// Reads G-Buffer textures and computes Cook-Torrance PBR shading.
|
||||
|
||||
// ── G-Buffer inputs ──────────────────────────────────────────────────────────
|
||||
|
||||
@group(0) @binding(0) var t_position: texture_2d<f32>;
|
||||
@group(0) @binding(1) var t_normal: texture_2d<f32>;
|
||||
@group(0) @binding(2) var t_albedo: texture_2d<f32>;
|
||||
@group(0) @binding(3) var t_material: texture_2d<f32>;
|
||||
@group(0) @binding(4) var s_gbuffer: sampler;
|
||||
|
||||
// ── Lights ───────────────────────────────────────────────────────────────────
|
||||
|
||||
struct LightData {
|
||||
position: vec3<f32>,
|
||||
light_type: u32,
|
||||
direction: vec3<f32>,
|
||||
range: f32,
|
||||
color: vec3<f32>,
|
||||
intensity: f32,
|
||||
inner_cone: f32,
|
||||
outer_cone: f32,
|
||||
_padding: vec2<f32>,
|
||||
};
|
||||
|
||||
struct LightsUniform {
|
||||
lights: array<LightData, 16>,
|
||||
count: u32,
|
||||
ambient_color: vec3<f32>,
|
||||
};
|
||||
|
||||
struct CameraPositionUniform {
|
||||
camera_pos: vec3<f32>,
|
||||
};
|
||||
|
||||
@group(1) @binding(0) var<uniform> lights_uniform: LightsUniform;
|
||||
@group(1) @binding(1) var<uniform> camera_uniform: CameraPositionUniform;
|
||||
|
||||
// ── Shadow + IBL ─────────────────────────────────────────────────────────────
|
||||
|
||||
struct ShadowUniform {
|
||||
light_view_proj: mat4x4<f32>,
|
||||
shadow_map_size: f32,
|
||||
shadow_bias: f32,
|
||||
};
|
||||
|
||||
@group(2) @binding(0) var t_shadow: texture_depth_2d;
|
||||
@group(2) @binding(1) var s_shadow: sampler_comparison;
|
||||
@group(2) @binding(2) var<uniform> shadow: ShadowUniform;
|
||||
@group(2) @binding(3) var t_brdf_lut: texture_2d<f32>;
|
||||
@group(2) @binding(4) var s_brdf_lut: sampler;
|
||||
@group(2) @binding(5) var t_ssgi: texture_2d<f32>;
|
||||
@group(2) @binding(6) var s_ssgi: sampler;
|
||||
@group(2) @binding(7) var t_rt_shadow: texture_2d<f32>;
|
||||
@group(2) @binding(8) var s_rt_shadow: sampler;
|
||||
|
||||
// ── Vertex / Fragment structs ─────────────────────────────────────────────────
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(v: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = vec4<f32>(v.position, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(v.position.x * 0.5 + 0.5, 1.0 - (v.position.y * 0.5 + 0.5));
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── BRDF functions (identical to pbr_shader.wgsl) ────────────────────────────
|
||||
|
||||
// GGX Normal Distribution Function
|
||||
fn distribution_ggx(N: vec3<f32>, H: vec3<f32>, roughness: f32) -> f32 {
|
||||
let a = roughness * roughness;
|
||||
let a2 = a * a;
|
||||
let NdotH = max(dot(N, H), 0.0);
|
||||
let NdotH2 = NdotH * NdotH;
|
||||
let denom_inner = NdotH2 * (a2 - 1.0) + 1.0;
|
||||
let denom = 3.14159265358979 * denom_inner * denom_inner;
|
||||
return a2 / denom;
|
||||
}
|
||||
|
||||
// Schlick-GGX geometry function (single direction)
|
||||
fn geometry_schlick_ggx(NdotV: f32, roughness: f32) -> f32 {
|
||||
let r = roughness + 1.0;
|
||||
let k = (r * r) / 8.0;
|
||||
return NdotV / (NdotV * (1.0 - k) + k);
|
||||
}
|
||||
|
||||
// Smith geometry function (both directions)
|
||||
fn geometry_smith(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>, roughness: f32) -> f32 {
|
||||
let NdotV = max(dot(N, V), 0.0);
|
||||
let NdotL = max(dot(N, L), 0.0);
|
||||
let ggx1 = geometry_schlick_ggx(NdotV, roughness);
|
||||
let ggx2 = geometry_schlick_ggx(NdotL, roughness);
|
||||
return ggx1 * ggx2;
|
||||
}
|
||||
|
||||
// Fresnel-Schlick approximation
|
||||
fn fresnel_schlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {
|
||||
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||
}
|
||||
|
||||
// Point light distance attenuation: inverse-square with smooth falloff at range boundary
|
||||
fn attenuation_point(distance: f32, range: f32) -> f32 {
|
||||
let d_over_r = distance / range;
|
||||
let d_over_r4 = d_over_r * d_over_r * d_over_r * d_over_r;
|
||||
let falloff = clamp(1.0 - d_over_r4, 0.0, 1.0);
|
||||
return (falloff * falloff) / (distance * distance + 0.0001);
|
||||
}
|
||||
|
||||
// Spot light angular attenuation
|
||||
fn attenuation_spot(light: LightData, L: vec3<f32>) -> f32 {
|
||||
let spot_dir = normalize(light.direction);
|
||||
let theta = dot(spot_dir, -L);
|
||||
return clamp(
|
||||
(theta - light.outer_cone) / (light.inner_cone - light.outer_cone + 0.0001),
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
// Cook-Torrance BRDF contribution for one light
|
||||
fn compute_light_contribution(
|
||||
light: LightData,
|
||||
N: vec3<f32>,
|
||||
V: vec3<f32>,
|
||||
world_pos: vec3<f32>,
|
||||
F0: vec3<f32>,
|
||||
albedo: vec3<f32>,
|
||||
metallic: f32,
|
||||
roughness: f32,
|
||||
) -> vec3<f32> {
|
||||
var L: vec3<f32>;
|
||||
var radiance: vec3<f32>;
|
||||
|
||||
if light.light_type == 0u {
|
||||
// Directional
|
||||
L = normalize(-light.direction);
|
||||
radiance = light.color * light.intensity;
|
||||
} else if light.light_type == 1u {
|
||||
// Point
|
||||
let to_light = light.position - world_pos;
|
||||
let dist = length(to_light);
|
||||
L = normalize(to_light);
|
||||
let att = attenuation_point(dist, light.range);
|
||||
radiance = light.color * light.intensity * att;
|
||||
} else {
|
||||
// Spot
|
||||
let to_light = light.position - world_pos;
|
||||
let dist = length(to_light);
|
||||
L = normalize(to_light);
|
||||
let att_dist = attenuation_point(dist, light.range);
|
||||
let att_ang = attenuation_spot(light, L);
|
||||
radiance = light.color * light.intensity * att_dist * att_ang;
|
||||
}
|
||||
|
||||
let H = normalize(V + L);
|
||||
|
||||
let NDF = distribution_ggx(N, H, roughness);
|
||||
let G = geometry_smith(N, V, L, roughness);
|
||||
let F = fresnel_schlick(max(dot(H, V), 0.0), F0);
|
||||
|
||||
let ks = F;
|
||||
let kd = (vec3<f32>(1.0) - ks) * (1.0 - metallic);
|
||||
|
||||
let numerator = NDF * G * F;
|
||||
let NdotL = max(dot(N, L), 0.0);
|
||||
let NdotV = max(dot(N, V), 0.0);
|
||||
let denominator = 4.0 * NdotV * NdotL + 0.0001;
|
||||
let specular = numerator / denominator;
|
||||
|
||||
return (kd * albedo / 3.14159265358979 + specular) * radiance * NdotL;
|
||||
}
|
||||
|
||||
fn calculate_shadow(world_pos: vec3<f32>) -> f32 {
|
||||
// If shadow_map_size == 0, shadow is disabled
|
||||
if shadow.shadow_map_size == 0.0 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let light_space_pos = shadow.light_view_proj * vec4<f32>(world_pos, 1.0);
|
||||
let proj_coords = light_space_pos.xyz / light_space_pos.w;
|
||||
|
||||
// wgpu NDC: x,y [-1,1], z [0,1]
|
||||
let shadow_uv = vec2<f32>(
|
||||
proj_coords.x * 0.5 + 0.5,
|
||||
-proj_coords.y * 0.5 + 0.5,
|
||||
);
|
||||
let current_depth = proj_coords.z;
|
||||
|
||||
if shadow_uv.x < 0.0 || shadow_uv.x > 1.0 || shadow_uv.y < 0.0 || shadow_uv.y > 1.0 {
|
||||
return 1.0;
|
||||
}
|
||||
if current_depth > 1.0 || current_depth < 0.0 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// 3x3 PCF
|
||||
let texel_size = 1.0 / shadow.shadow_map_size;
|
||||
var shadow_val = 0.0;
|
||||
for (var x = -1; x <= 1; x++) {
|
||||
for (var y = -1; y <= 1; y++) {
|
||||
let offset = vec2<f32>(f32(x), f32(y)) * texel_size;
|
||||
shadow_val += textureSampleCompare(
|
||||
t_shadow, s_shadow,
|
||||
shadow_uv + offset,
|
||||
current_depth - shadow.shadow_bias,
|
||||
);
|
||||
}
|
||||
}
|
||||
return shadow_val / 9.0;
|
||||
}
|
||||
|
||||
// Procedural environment sampling for IBL
|
||||
fn sample_environment(direction: vec3<f32>, roughness: f32) -> vec3<f32> {
|
||||
var env: vec3<f32>;
|
||||
if direction.y > 0.0 {
|
||||
let horizon = vec3<f32>(0.6, 0.6, 0.5);
|
||||
let sky = vec3<f32>(0.3, 0.5, 0.9);
|
||||
env = mix(horizon, sky, pow(direction.y, 0.4));
|
||||
} else {
|
||||
let horizon = vec3<f32>(0.6, 0.6, 0.5);
|
||||
let ground = vec3<f32>(0.1, 0.08, 0.06);
|
||||
env = mix(horizon, ground, pow(-direction.y, 0.4));
|
||||
}
|
||||
let avg = vec3<f32>(0.3, 0.35, 0.4);
|
||||
return mix(env, avg, roughness * roughness);
|
||||
}
|
||||
|
||||
// ── Fragment shader ──────────────────────────────────────────────────────────
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let uv = in.uv;
|
||||
|
||||
// Read G-Buffer
|
||||
let position_sample = textureSample(t_position, s_gbuffer, uv);
|
||||
let world_pos = position_sample.xyz;
|
||||
|
||||
// Background pixel: skip shading
|
||||
if length(world_pos) < 0.001 {
|
||||
return vec4<f32>(0.01, 0.01, 0.01, 1.0);
|
||||
}
|
||||
|
||||
let normal_sample = textureSample(t_normal, s_gbuffer, uv).rgb;
|
||||
let N = normalize(normal_sample * 2.0 - 1.0);
|
||||
|
||||
let albedo_sample = textureSample(t_albedo, s_gbuffer, uv);
|
||||
let albedo = albedo_sample.rgb;
|
||||
let alpha = albedo_sample.a;
|
||||
|
||||
let mat_sample = textureSample(t_material, s_gbuffer, uv);
|
||||
let metallic = mat_sample.r;
|
||||
let roughness = mat_sample.g;
|
||||
let ao = mat_sample.b;
|
||||
|
||||
let V = normalize(camera_uniform.camera_pos - world_pos);
|
||||
|
||||
// F0: base reflectivity; 0.04 for dielectrics, albedo for metals
|
||||
let F0 = mix(vec3<f32>(0.04, 0.04, 0.04), albedo, metallic);
|
||||
|
||||
// Shadow: sample RT shadow texture (1.0 = lit, 0.0 = shadowed)
|
||||
let rt_dims = textureDimensions(t_rt_shadow);
|
||||
let rt_coord = vec2<i32>(vec2<f32>(uv.x * f32(rt_dims.x), uv.y * f32(rt_dims.y)));
|
||||
let shadow_factor = textureLoad(t_rt_shadow, rt_coord, 0).r;
|
||||
|
||||
// Accumulate contribution from all active lights
|
||||
var Lo = vec3<f32>(0.0);
|
||||
let light_count = min(lights_uniform.count, 16u);
|
||||
for (var i = 0u; i < light_count; i++) {
|
||||
var contribution = compute_light_contribution(
|
||||
lights_uniform.lights[i],
|
||||
N, V, world_pos, F0, albedo, metallic, roughness,
|
||||
);
|
||||
if lights_uniform.lights[i].light_type == 0u {
|
||||
contribution = contribution * shadow_factor;
|
||||
}
|
||||
Lo += contribution;
|
||||
}
|
||||
|
||||
// IBL ambient term
|
||||
let NdotV_ibl = max(dot(N, V), 0.0);
|
||||
let R = reflect(-V, N);
|
||||
|
||||
// Diffuse IBL
|
||||
let irradiance = sample_environment(N, 1.0);
|
||||
let F_env = fresnel_schlick(NdotV_ibl, F0);
|
||||
let kd_ibl = (vec3<f32>(1.0) - F_env) * (1.0 - metallic);
|
||||
let diffuse_ibl = kd_ibl * albedo * irradiance;
|
||||
|
||||
// Specular IBL
|
||||
let prefiltered = sample_environment(R, roughness);
|
||||
let brdf_val = textureSample(t_brdf_lut, s_brdf_lut, vec2<f32>(NdotV_ibl, roughness));
|
||||
let specular_ibl = prefiltered * (F0 * brdf_val.r + vec3<f32>(brdf_val.g));
|
||||
|
||||
let ssgi_data = textureSample(t_ssgi, s_ssgi, uv);
|
||||
let ssgi_ao = ssgi_data.r;
|
||||
let ssgi_indirect = ssgi_data.gba;
|
||||
let ambient = (diffuse_ibl + specular_ibl) * ao * ssgi_ao + ssgi_indirect;
|
||||
|
||||
// Output raw HDR linear colour; tonemap is applied in a separate tonemap pass.
|
||||
let color = ambient + Lo;
|
||||
|
||||
return vec4<f32>(color, alpha);
|
||||
}
|
||||
905
crates/voltex_renderer/src/deferred_pipeline.rs
Normal file
905
crates/voltex_renderer/src/deferred_pipeline.rs
Normal file
@@ -0,0 +1,905 @@
|
||||
use crate::vertex::MeshVertex;
|
||||
use crate::fullscreen_quad::FullscreenVertex;
|
||||
use crate::gpu::DEPTH_FORMAT;
|
||||
use crate::gbuffer::{
|
||||
GBUFFER_POSITION_FORMAT, GBUFFER_NORMAL_FORMAT, GBUFFER_ALBEDO_FORMAT, GBUFFER_MATERIAL_FORMAT,
|
||||
};
|
||||
use crate::light::CameraUniform;
|
||||
use crate::ssgi::{SsgiUniform, SSGI_OUTPUT_FORMAT};
|
||||
use crate::rt_shadow::{RtShadowUniform, RT_SHADOW_FORMAT};
|
||||
use crate::hdr::HDR_FORMAT;
|
||||
use crate::bloom::BloomUniform;
|
||||
use crate::tonemap::TonemapUniform;
|
||||
|
||||
/// Bind group layout for the G-Buffer pass camera uniform (dynamic offset, group 0).
|
||||
pub fn gbuffer_camera_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("GBuffer Camera Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
std::mem::size_of::<CameraUniform>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the G-Buffer geometry pass render pipeline.
|
||||
pub fn create_gbuffer_pipeline(
|
||||
device: &wgpu::Device,
|
||||
camera_layout: &wgpu::BindGroupLayout,
|
||||
texture_layout: &wgpu::BindGroupLayout,
|
||||
material_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("GBuffer Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("deferred_gbuffer.wgsl").into()),
|
||||
});
|
||||
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("GBuffer Pipeline Layout"),
|
||||
bind_group_layouts: &[camera_layout, texture_layout, material_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("GBuffer Pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[MeshVertex::LAYOUT],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[
|
||||
Some(wgpu::ColorTargetState {
|
||||
format: GBUFFER_POSITION_FORMAT,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}),
|
||||
Some(wgpu::ColorTargetState {
|
||||
format: GBUFFER_NORMAL_FORMAT,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}),
|
||||
Some(wgpu::ColorTargetState {
|
||||
format: GBUFFER_ALBEDO_FORMAT,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}),
|
||||
Some(wgpu::ColorTargetState {
|
||||
format: GBUFFER_MATERIAL_FORMAT,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}),
|
||||
],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Bind group layout for the lighting pass G-Buffer textures + sampler (group 0).
|
||||
/// position is Float non-filterable; normal/albedo/material are filterable.
|
||||
pub fn lighting_gbuffer_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Lighting GBuffer Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: position texture (Rgba32Float → non-filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: normal texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 2: albedo texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 3: material texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 4: non-filtering sampler (required for non-filterable texture)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Bind group layout for the lighting pass lights + camera position uniforms (group 1).
|
||||
pub fn lighting_lights_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Lighting Lights Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: LightsUniform
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: CameraPositionUniform
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Bind group layout for shadow map + BRDF LUT (group 2).
|
||||
pub fn lighting_shadow_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Lighting Shadow Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: shadow depth texture
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: comparison sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||
count: None,
|
||||
},
|
||||
// binding 2: ShadowUniform
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 3: BRDF LUT texture
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 4: filtering sampler for BRDF LUT
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// binding 5: SSGI output texture (Rgba16Float → filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 5,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 6: filtering sampler for SSGI texture
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 6,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// binding 7: RT shadow texture (R32Float → non-filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 7,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 8: non-filtering sampler for RT shadow texture
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 8,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the deferred lighting pass render pipeline.
|
||||
pub fn create_lighting_pipeline(
|
||||
device: &wgpu::Device,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
gbuffer_layout: &wgpu::BindGroupLayout,
|
||||
lights_layout: &wgpu::BindGroupLayout,
|
||||
shadow_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Deferred Lighting Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("deferred_lighting.wgsl").into()),
|
||||
});
|
||||
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Deferred Lighting Pipeline Layout"),
|
||||
bind_group_layouts: &[gbuffer_layout, lights_layout, shadow_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Deferred Lighting Pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[FullscreenVertex::LAYOUT],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
// ── SSGI pipeline ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// Bind group layout for the SSGI pass G-Buffer inputs (group 0).
|
||||
/// position is non-filterable (Rgba32Float); normal and albedo are filterable.
|
||||
/// Sampler is NonFiltering (required when any texture is non-filterable).
|
||||
pub fn ssgi_gbuffer_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("SSGI GBuffer Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: position texture (Rgba32Float → non-filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: normal texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 2: albedo texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 3: non-filtering sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Bind group layout for the SSGI pass uniform data (group 1).
|
||||
/// Contains: SsgiUniform buffer, kernel buffer, noise texture (Rgba32Float), noise sampler.
|
||||
pub fn ssgi_data_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("SSGI Data Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: SsgiUniform
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
std::mem::size_of::<SsgiUniform>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: hemisphere kernel (array<vec4<f32>, 64>)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
(64 * std::mem::size_of::<[f32; 4]>()) as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 2: noise texture (Rgba32Float → non-filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 3: non-filtering sampler for noise
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the SSGI render pipeline.
|
||||
/// Draws a fullscreen triangle and writes to a single SSGI_OUTPUT_FORMAT color target.
|
||||
pub fn create_ssgi_pipeline(
|
||||
device: &wgpu::Device,
|
||||
gbuffer_layout: &wgpu::BindGroupLayout,
|
||||
data_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("SSGI Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("ssgi_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("SSGI Pipeline Layout"),
|
||||
bind_group_layouts: &[gbuffer_layout, data_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("SSGI Pipeline"),
|
||||
layout: Some(&layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[FullscreenVertex::LAYOUT],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: SSGI_OUTPUT_FORMAT,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
// ── RT Shadow pipeline ────────────────────────────────────────────────────────
|
||||
|
||||
/// Bind group layout for the RT shadow compute pass G-Buffer inputs (group 0).
|
||||
/// Bindings: position (non-filterable), normal (filterable) — both COMPUTE visibility.
|
||||
pub fn rt_shadow_gbuffer_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("RT Shadow GBuffer Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: position texture (Rgba32Float → non-filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: normal texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Bind group layout for the RT shadow compute pass data (group 1).
|
||||
/// Bindings: TLAS, storage texture (R32Float write), RtShadowUniform — all COMPUTE visibility.
|
||||
pub fn rt_shadow_data_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("RT Shadow Data Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: TLAS (acceleration structure)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::AccelerationStructure { vertex_return: false },
|
||||
count: None,
|
||||
},
|
||||
// binding 1: shadow output texture (R32Float, write-only storage)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::StorageTexture {
|
||||
access: wgpu::StorageTextureAccess::WriteOnly,
|
||||
format: RT_SHADOW_FORMAT,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 2: RtShadowUniform
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
std::mem::size_of::<RtShadowUniform>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the RT shadow compute pipeline.
|
||||
pub fn create_rt_shadow_pipeline(
|
||||
device: &wgpu::Device,
|
||||
gbuffer_layout: &wgpu::BindGroupLayout,
|
||||
data_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::ComputePipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("RT Shadow Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("rt_shadow_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("RT Shadow Pipeline Layout"),
|
||||
bind_group_layouts: &[gbuffer_layout, data_layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some("RT Shadow Compute Pipeline"),
|
||||
layout: Some(&layout),
|
||||
module: &shader,
|
||||
entry_point: Some("cs_main"),
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Bloom pipelines ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Bind group layout shared by both bloom pipelines (group 0):
|
||||
/// binding 0 — input texture (filterable)
|
||||
/// binding 1 — filtering sampler
|
||||
/// binding 2 — BloomUniform buffer
|
||||
pub fn bloom_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Bloom Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: input HDR texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: filtering sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// binding 2: BloomUniform buffer
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
std::mem::size_of::<BloomUniform>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the bloom downsample render pipeline.
|
||||
/// Entry point: `fs_downsample`. No blending — overwrites the mip target.
|
||||
pub fn create_bloom_downsample_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Bloom Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("bloom_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Bloom Downsample Pipeline Layout"),
|
||||
bind_group_layouts: &[layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Bloom Downsample Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[FullscreenVertex::LAYOUT],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_downsample"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: HDR_FORMAT,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the bloom upsample render pipeline.
|
||||
/// Entry point: `fs_upsample`. Additive blending (One + One) to accumulate mips.
|
||||
pub fn create_bloom_upsample_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Bloom Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("bloom_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Bloom Upsample Pipeline Layout"),
|
||||
bind_group_layouts: &[layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let additive = wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::One,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::One,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
};
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Bloom Upsample Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[FullscreenVertex::LAYOUT],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_upsample"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: HDR_FORMAT,
|
||||
blend: Some(additive),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Tonemap pipeline ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Bind group layout for the tonemap pass (group 0):
|
||||
/// binding 0 — HDR texture (filterable)
|
||||
/// binding 1 — bloom texture (filterable)
|
||||
/// binding 2 — filtering sampler
|
||||
/// binding 3 — TonemapUniform buffer
|
||||
pub fn tonemap_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Tonemap Bind Group Layout"),
|
||||
entries: &[
|
||||
// binding 0: HDR texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 1: bloom texture (filterable)
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// binding 2: filtering sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// binding 3: TonemapUniform buffer
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: wgpu::BufferSize::new(
|
||||
std::mem::size_of::<TonemapUniform>() as u64,
|
||||
),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the tonemap render pipeline.
|
||||
/// Writes to the surface swapchain format.
|
||||
pub fn create_tonemap_pipeline(
|
||||
device: &wgpu::Device,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Tonemap Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("tonemap_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Tonemap Pipeline Layout"),
|
||||
bind_group_layouts: &[layout],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Tonemap Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[FullscreenVertex::LAYOUT],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
38
crates/voltex_renderer/src/fullscreen_quad.rs
Normal file
38
crates/voltex_renderer/src/fullscreen_quad.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct FullscreenVertex {
|
||||
pub position: [f32; 2],
|
||||
}
|
||||
|
||||
impl FullscreenVertex {
|
||||
pub const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<FullscreenVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/// Oversized triangle covering the entire screen (clip-space).
|
||||
/// Three vertices: (-1,-1), (3,-1), (-1,3)
|
||||
pub const FULLSCREEN_VERTICES: [FullscreenVertex; 3] = [
|
||||
FullscreenVertex { position: [-1.0, -1.0] },
|
||||
FullscreenVertex { position: [ 3.0, -1.0] },
|
||||
FullscreenVertex { position: [-1.0, 3.0] },
|
||||
];
|
||||
|
||||
pub fn create_fullscreen_vertex_buffer(device: &wgpu::Device) -> wgpu::Buffer {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Fullscreen Triangle Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&FULLSCREEN_VERTICES),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
})
|
||||
}
|
||||
70
crates/voltex_renderer/src/gbuffer.rs
Normal file
70
crates/voltex_renderer/src/gbuffer.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use crate::gpu::DEPTH_FORMAT;
|
||||
|
||||
pub const GBUFFER_POSITION_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
|
||||
pub const GBUFFER_NORMAL_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
|
||||
pub const GBUFFER_ALBEDO_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||
pub const GBUFFER_MATERIAL_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
||||
|
||||
fn create_rt(
|
||||
device: &wgpu::Device,
|
||||
w: u32,
|
||||
h: u32,
|
||||
format: wgpu::TextureFormat,
|
||||
label: &str,
|
||||
) -> wgpu::TextureView {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
fn create_depth(device: &wgpu::Device, w: u32, h: u32) -> wgpu::TextureView {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("GBuffer Depth Texture"),
|
||||
size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
pub struct GBuffer {
|
||||
pub position_view: wgpu::TextureView,
|
||||
pub normal_view: wgpu::TextureView,
|
||||
pub albedo_view: wgpu::TextureView,
|
||||
pub material_view: wgpu::TextureView,
|
||||
pub depth_view: wgpu::TextureView,
|
||||
}
|
||||
|
||||
impl GBuffer {
|
||||
pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
|
||||
let position_view = create_rt(device, width, height, GBUFFER_POSITION_FORMAT, "GBuffer Position");
|
||||
let normal_view = create_rt(device, width, height, GBUFFER_NORMAL_FORMAT, "GBuffer Normal");
|
||||
let albedo_view = create_rt(device, width, height, GBUFFER_ALBEDO_FORMAT, "GBuffer Albedo");
|
||||
let material_view = create_rt(device, width, height, GBUFFER_MATERIAL_FORMAT, "GBuffer Material");
|
||||
let depth_view = create_depth(device, width, height);
|
||||
|
||||
Self {
|
||||
position_view,
|
||||
normal_view,
|
||||
albedo_view,
|
||||
material_view,
|
||||
depth_view,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
||||
*self = Self::new(device, width, height);
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,15 @@ pub struct GpuContext {
|
||||
|
||||
impl GpuContext {
|
||||
pub fn new(window: Arc<Window>) -> Self {
|
||||
pollster::block_on(Self::new_async(window))
|
||||
pollster::block_on(Self::new_async(window, wgpu::Features::empty()))
|
||||
}
|
||||
|
||||
async fn new_async(window: Arc<Window>) -> Self {
|
||||
/// Create a GpuContext requesting additional device features (e.g. ray tracing).
|
||||
pub fn new_with_features(window: Arc<Window>, features: wgpu::Features) -> Self {
|
||||
pollster::block_on(Self::new_async(window, features))
|
||||
}
|
||||
|
||||
async fn new_async(window: Arc<Window>, extra_features: wgpu::Features) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
@@ -50,12 +55,28 @@ impl GpuContext {
|
||||
.await
|
||||
.expect("Failed to find a suitable GPU adapter");
|
||||
|
||||
// When extra features are requested (e.g. ray tracing), use adapter limits
|
||||
// so RT-specific limits (max_acceleration_structures_per_shader_stage, etc.) are satisfied.
|
||||
let required_limits = if extra_features.is_empty() {
|
||||
wgpu::Limits::default()
|
||||
} else {
|
||||
adapter.limits()
|
||||
};
|
||||
|
||||
let experimental = if extra_features.is_empty() {
|
||||
wgpu::ExperimentalFeatures::disabled()
|
||||
} else {
|
||||
// Safety: we acknowledge experimental features may have bugs
|
||||
unsafe { wgpu::ExperimentalFeatures::enabled() }
|
||||
};
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
label: Some("Voltex Device"),
|
||||
required_features: wgpu::Features::empty(),
|
||||
required_limits: wgpu::Limits::default(),
|
||||
required_features: extra_features,
|
||||
required_limits,
|
||||
memory_hints: Default::default(),
|
||||
experimental_features: experimental,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
|
||||
43
crates/voltex_renderer/src/hdr.rs
Normal file
43
crates/voltex_renderer/src/hdr.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Texture format used for HDR render targets.
|
||||
pub const HDR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
|
||||
|
||||
/// An HDR render target (Rgba16Float) used as the output of the lighting pass.
|
||||
pub struct HdrTarget {
|
||||
pub view: wgpu::TextureView,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl HdrTarget {
|
||||
pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
|
||||
let view = create_hdr_view(device, width, height);
|
||||
Self { view, width, height }
|
||||
}
|
||||
|
||||
/// Recreate the HDR texture when the window is resized.
|
||||
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
||||
self.view = create_hdr_view(device, width, height);
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn create_hdr_view(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("HDR Target Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: HDR_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
}
|
||||
@@ -13,6 +13,15 @@ pub mod shadow;
|
||||
pub mod shadow_pipeline;
|
||||
pub mod brdf_lut;
|
||||
pub mod ibl;
|
||||
pub mod gbuffer;
|
||||
pub mod fullscreen_quad;
|
||||
pub mod deferred_pipeline;
|
||||
pub mod ssgi;
|
||||
pub mod rt_accel;
|
||||
pub mod rt_shadow;
|
||||
pub mod hdr;
|
||||
pub mod bloom;
|
||||
pub mod tonemap;
|
||||
|
||||
pub use gpu::{GpuContext, DEPTH_FORMAT};
|
||||
pub use light::{CameraUniform, LightUniform, LightData, LightsUniform, MAX_LIGHTS, LIGHT_DIRECTIONAL, LIGHT_POINT, LIGHT_SPOT};
|
||||
@@ -25,3 +34,20 @@ pub use pbr_pipeline::create_pbr_pipeline;
|
||||
pub use shadow::{ShadowMap, ShadowUniform, ShadowPassUniform, SHADOW_MAP_SIZE, SHADOW_FORMAT};
|
||||
pub use shadow_pipeline::{create_shadow_pipeline, shadow_pass_bind_group_layout};
|
||||
pub use ibl::IblResources;
|
||||
pub use gbuffer::GBuffer;
|
||||
pub use fullscreen_quad::{create_fullscreen_vertex_buffer, FullscreenVertex};
|
||||
pub use deferred_pipeline::{
|
||||
create_gbuffer_pipeline, create_lighting_pipeline,
|
||||
gbuffer_camera_bind_group_layout,
|
||||
lighting_gbuffer_bind_group_layout, lighting_lights_bind_group_layout, lighting_shadow_bind_group_layout,
|
||||
ssgi_gbuffer_bind_group_layout, ssgi_data_bind_group_layout, create_ssgi_pipeline,
|
||||
rt_shadow_gbuffer_bind_group_layout, rt_shadow_data_bind_group_layout, create_rt_shadow_pipeline,
|
||||
bloom_bind_group_layout, create_bloom_downsample_pipeline, create_bloom_upsample_pipeline,
|
||||
tonemap_bind_group_layout, create_tonemap_pipeline,
|
||||
};
|
||||
pub use ssgi::{SsgiResources, SsgiUniform, SSGI_OUTPUT_FORMAT};
|
||||
pub use rt_accel::{RtAccel, RtInstance, BlasMeshData, mat4_to_tlas_transform};
|
||||
pub use rt_shadow::{RtShadowResources, RtShadowUniform, RT_SHADOW_FORMAT};
|
||||
pub use hdr::{HdrTarget, HDR_FORMAT};
|
||||
pub use bloom::{BloomResources, BloomUniform, mip_sizes, BLOOM_MIP_COUNT};
|
||||
pub use tonemap::{TonemapUniform, aces_tonemap};
|
||||
|
||||
@@ -21,4 +21,24 @@ impl Mesh {
|
||||
});
|
||||
Self { vertex_buffer, index_buffer, num_indices: indices.len() as u32 }
|
||||
}
|
||||
|
||||
/// Create a mesh with additional buffer usage flags (e.g. `BLAS_INPUT` for ray tracing).
|
||||
pub fn new_with_usage(
|
||||
device: &wgpu::Device,
|
||||
vertices: &[MeshVertex],
|
||||
indices: &[u32],
|
||||
extra_usage: wgpu::BufferUsages,
|
||||
) -> Self {
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Mesh Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX | extra_usage,
|
||||
});
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Mesh Index Buffer"),
|
||||
contents: bytemuck::cast_slice(indices),
|
||||
usage: wgpu::BufferUsages::INDEX | extra_usage,
|
||||
});
|
||||
Self { vertex_buffer, index_buffer, num_indices: indices.len() as u32 }
|
||||
}
|
||||
}
|
||||
|
||||
195
crates/voltex_renderer/src/rt_accel.rs
Normal file
195
crates/voltex_renderer/src/rt_accel.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use crate::vertex::MeshVertex;
|
||||
|
||||
/// Data needed to build a BLAS for one mesh.
|
||||
pub struct BlasMeshData<'a> {
|
||||
pub vertex_buffer: &'a wgpu::Buffer,
|
||||
pub index_buffer: &'a wgpu::Buffer,
|
||||
pub vertex_count: u32,
|
||||
pub index_count: u32,
|
||||
}
|
||||
|
||||
/// One instance transform fed to the TLAS: a world transform and a BLAS index.
|
||||
pub struct RtInstance {
|
||||
/// Column-major 4x4 transform matrix.
|
||||
pub transform: [f32; 16],
|
||||
/// Index into `RtAccel::blas_list`.
|
||||
pub blas_index: usize,
|
||||
}
|
||||
|
||||
/// Bottom + Top Level Acceleration Structures for a scene.
|
||||
pub struct RtAccel {
|
||||
pub blas_list: Vec<wgpu::Blas>,
|
||||
pub tlas: wgpu::Tlas,
|
||||
}
|
||||
|
||||
impl RtAccel {
|
||||
/// Create BLAS for each mesh and build a TLAS with the given instances.
|
||||
///
|
||||
/// The encoder must be submitted after this call so the GPU builds fire.
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
meshes: &[BlasMeshData<'_>],
|
||||
instances: &[RtInstance],
|
||||
) -> Self {
|
||||
let vertex_stride = std::mem::size_of::<MeshVertex>() as u64;
|
||||
|
||||
// ── Build one BLAS per mesh ───────────────────────────────────────────
|
||||
let size_descs: Vec<wgpu::BlasTriangleGeometrySizeDescriptor> = meshes
|
||||
.iter()
|
||||
.map(|m| wgpu::BlasTriangleGeometrySizeDescriptor {
|
||||
vertex_format: wgpu::VertexFormat::Float32x3,
|
||||
vertex_count: m.vertex_count,
|
||||
index_format: Some(wgpu::IndexFormat::Uint32),
|
||||
index_count: Some(m.index_count),
|
||||
flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let blas_list: Vec<wgpu::Blas> = meshes
|
||||
.iter()
|
||||
.zip(size_descs.iter())
|
||||
.map(|(_mesh, size_desc)| {
|
||||
device.create_blas(
|
||||
&wgpu::CreateBlasDescriptor {
|
||||
label: Some("Mesh BLAS"),
|
||||
flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE,
|
||||
update_mode: wgpu::AccelerationStructureUpdateMode::Build,
|
||||
},
|
||||
wgpu::BlasGeometrySizeDescriptors::Triangles {
|
||||
descriptors: vec![size_desc.clone()],
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// ── Create TLAS ───────────────────────────────────────────────────────
|
||||
let max_instances = instances.len().max(1) as u32;
|
||||
let mut tlas = device.create_tlas(&wgpu::CreateTlasDescriptor {
|
||||
label: Some("Scene TLAS"),
|
||||
max_instances,
|
||||
flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE,
|
||||
update_mode: wgpu::AccelerationStructureUpdateMode::Build,
|
||||
});
|
||||
|
||||
// ── Populate TLAS instances ───────────────────────────────────────────
|
||||
for (i, inst) in instances.iter().enumerate() {
|
||||
let blas = &blas_list[inst.blas_index];
|
||||
tlas[i] = Some(wgpu::TlasInstance::new(
|
||||
blas,
|
||||
mat4_to_tlas_transform(&inst.transform),
|
||||
0,
|
||||
0xFF,
|
||||
));
|
||||
}
|
||||
|
||||
// ── Build entries ─────────────────────────────────────────────────────
|
||||
let blas_entries: Vec<wgpu::BlasBuildEntry<'_>> = meshes
|
||||
.iter()
|
||||
.zip(blas_list.iter())
|
||||
.zip(size_descs.iter())
|
||||
.map(|((mesh, blas), size_desc)| wgpu::BlasBuildEntry {
|
||||
blas,
|
||||
geometry: wgpu::BlasGeometries::TriangleGeometries(vec![
|
||||
wgpu::BlasTriangleGeometry {
|
||||
size: size_desc,
|
||||
vertex_buffer: mesh.vertex_buffer,
|
||||
first_vertex: 0,
|
||||
vertex_stride,
|
||||
index_buffer: Some(mesh.index_buffer),
|
||||
first_index: Some(0),
|
||||
transform_buffer: None,
|
||||
transform_buffer_offset: None,
|
||||
},
|
||||
]),
|
||||
})
|
||||
.collect();
|
||||
|
||||
encoder.build_acceleration_structures(blas_entries.iter(), std::iter::once(&tlas));
|
||||
|
||||
Self { blas_list, tlas }
|
||||
}
|
||||
|
||||
/// Update TLAS instance transforms and rebuild.
|
||||
pub fn update_instances(
|
||||
&mut self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
instances: &[RtInstance],
|
||||
) {
|
||||
for (i, inst) in instances.iter().enumerate() {
|
||||
if i >= self.tlas.get().len() {
|
||||
break;
|
||||
}
|
||||
let blas = &self.blas_list[inst.blas_index];
|
||||
self.tlas[i] = Some(wgpu::TlasInstance::new(
|
||||
blas,
|
||||
mat4_to_tlas_transform(&inst.transform),
|
||||
0,
|
||||
0xFF,
|
||||
));
|
||||
}
|
||||
encoder.build_acceleration_structures(std::iter::empty(), std::iter::once(&self.tlas));
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a column-major 4×4 matrix to a row-major 3×4 affine transform.
|
||||
///
|
||||
/// The TLAS expects `[f32; 12]` in row-major order (3 rows × 4 columns).
|
||||
/// A standard column-major mat4 stores: col0=[m0,m1,m2,m3], col1=[m4,m5,m6,m7], ...
|
||||
/// Row 0: m[0], m[4], m[8], m[12]
|
||||
/// Row 1: m[1], m[5], m[9], m[13]
|
||||
/// Row 2: m[2], m[6], m[10], m[14]
|
||||
pub fn mat4_to_tlas_transform(m: &[f32; 16]) -> [f32; 12] {
|
||||
[
|
||||
m[0], m[4], m[8], m[12],
|
||||
m[1], m[5], m[9], m[13],
|
||||
m[2], m[6], m[10], m[14],
|
||||
]
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mat4_to_tlas_transform_identity() {
|
||||
#[rustfmt::skip]
|
||||
let identity: [f32; 16] = [
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
];
|
||||
let result = mat4_to_tlas_transform(&identity);
|
||||
// Row 0: [1,0,0,0], Row 1: [0,1,0,0], Row 2: [0,0,1,0]
|
||||
let expected: [f32; 12] = [
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
];
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mat4_to_tlas_transform_translation() {
|
||||
// Column-major: identity rotation + translation (tx=1, ty=2, tz=3)
|
||||
// Column 3 = [tx, ty, tz, 1] stored at indices [12,13,14,15]
|
||||
#[rustfmt::skip]
|
||||
let mat: [f32; 16] = [
|
||||
1.0, 0.0, 0.0, 0.0, // col 0
|
||||
0.0, 1.0, 0.0, 0.0, // col 1
|
||||
0.0, 0.0, 1.0, 0.0, // col 2
|
||||
1.0, 2.0, 3.0, 1.0, // col 3 (translation)
|
||||
];
|
||||
let result = mat4_to_tlas_transform(&mat);
|
||||
// Row-major 3x4: rotation part identity, translation in last column
|
||||
let expected: [f32; 12] = [
|
||||
1.0, 0.0, 0.0, 1.0,
|
||||
0.0, 1.0, 0.0, 2.0,
|
||||
0.0, 0.0, 1.0, 3.0,
|
||||
];
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
89
crates/voltex_renderer/src/rt_shadow.rs
Normal file
89
crates/voltex_renderer/src/rt_shadow.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
/// Texture format used for the RT shadow output (single-channel float).
|
||||
pub const RT_SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Float;
|
||||
|
||||
/// Uniform buffer for the RT shadow compute pass.
|
||||
///
|
||||
/// Layout (32 bytes, 16-byte aligned):
|
||||
/// light_direction [f32; 3] + _pad0: f32 → 16 bytes
|
||||
/// width: u32, height: u32, _pad1: [u32; 2] → 16 bytes
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct RtShadowUniform {
|
||||
pub light_direction: [f32; 3],
|
||||
pub _pad0: f32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub _pad1: [u32; 2],
|
||||
}
|
||||
|
||||
/// GPU resources for the RT shadow compute pass.
|
||||
pub struct RtShadowResources {
|
||||
pub shadow_texture: wgpu::Texture,
|
||||
pub shadow_view: wgpu::TextureView,
|
||||
pub uniform_buffer: wgpu::Buffer,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl RtShadowResources {
|
||||
pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
|
||||
let (shadow_texture, shadow_view) = create_shadow_texture(device, width, height);
|
||||
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("RT Shadow Uniform Buffer"),
|
||||
size: std::mem::size_of::<RtShadowUniform>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
Self { shadow_texture, shadow_view, uniform_buffer, width, height }
|
||||
}
|
||||
|
||||
/// Recreate the shadow texture when the window is resized.
|
||||
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
||||
let (shadow_texture, shadow_view) = create_shadow_texture(device, width, height);
|
||||
self.shadow_texture = shadow_texture;
|
||||
self.shadow_view = shadow_view;
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
fn create_shadow_texture(
|
||||
device: &wgpu::Device,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> (wgpu::Texture, wgpu::TextureView) {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("RT Shadow Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: RT_SHADOW_FORMAT,
|
||||
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
(texture, view)
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rt_shadow_uniform_size() {
|
||||
assert_eq!(std::mem::size_of::<RtShadowUniform>(), 32);
|
||||
}
|
||||
}
|
||||
78
crates/voltex_renderer/src/rt_shadow_shader.wgsl
Normal file
78
crates/voltex_renderer/src/rt_shadow_shader.wgsl
Normal file
@@ -0,0 +1,78 @@
|
||||
enable wgpu_ray_query;
|
||||
|
||||
// RT Shadow compute shader.
|
||||
// Reads world-space position and normal from the G-Buffer, then fires a
|
||||
// shadow ray against the TLAS to determine per-pixel visibility.
|
||||
// Output is 1.0 (lit) or 0.0 (shadowed) stored in an R32Float texture.
|
||||
|
||||
// ── Group 0: G-Buffer inputs ──────────────────────────────────────────────────
|
||||
|
||||
@group(0) @binding(0) var t_position: texture_2d<f32>;
|
||||
@group(0) @binding(1) var t_normal: texture_2d<f32>;
|
||||
|
||||
// ── Group 1: RT data ─────────────────────────────────────────────────────────
|
||||
|
||||
@group(1) @binding(0) var tlas: acceleration_structure;
|
||||
@group(1) @binding(1) var t_shadow_out: texture_storage_2d<r32float, write>;
|
||||
|
||||
struct RtShadowUniform {
|
||||
light_direction: vec3<f32>,
|
||||
_pad0: f32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
_pad1: vec2<u32>,
|
||||
};
|
||||
|
||||
@group(1) @binding(2) var<uniform> uniforms: RtShadowUniform;
|
||||
|
||||
// ── Compute entry point ───────────────────────────────────────────────────────
|
||||
|
||||
@compute @workgroup_size(8, 8)
|
||||
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let coord = vec2<i32>(i32(gid.x), i32(gid.y));
|
||||
|
||||
// Bounds check
|
||||
if gid.x >= uniforms.width || gid.y >= uniforms.height {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read world position from G-Buffer
|
||||
let world_pos = textureLoad(t_position, coord, 0).xyz;
|
||||
|
||||
// Background pixel: skip (position is (0,0,0) for skybox pixels)
|
||||
if dot(world_pos, world_pos) < 0.001 {
|
||||
textureStore(t_shadow_out, coord, vec4<f32>(1.0, 0.0, 0.0, 0.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read and decode normal — G-Buffer stores N * 0.5 + 0.5
|
||||
let normal_encoded = textureLoad(t_normal, coord, 0).rgb;
|
||||
let N = normalize(normal_encoded * 2.0 - 1.0);
|
||||
|
||||
// Ray: from surface towards the light, biased along normal to avoid self-intersection
|
||||
let ray_origin = world_pos + N * 0.01;
|
||||
let ray_dir = normalize(-uniforms.light_direction);
|
||||
|
||||
// Build ray descriptor
|
||||
var desc: RayDesc;
|
||||
desc.flags = RAY_FLAG_TERMINATE_ON_FIRST_HIT;
|
||||
desc.cull_mask = 0xFFu;
|
||||
desc.tmin = 0.001;
|
||||
desc.tmax = 1000.0;
|
||||
desc.origin = ray_origin;
|
||||
desc.dir = ray_dir;
|
||||
|
||||
// Ray query
|
||||
var rq: ray_query;
|
||||
rayQueryInitialize(&rq, tlas, desc);
|
||||
while rayQueryProceed(&rq) {}
|
||||
|
||||
// Check result
|
||||
let intersection = rayQueryGetCommittedIntersection(&rq);
|
||||
var shadow: f32 = 1.0;
|
||||
if intersection.kind != RAY_QUERY_INTERSECTION_NONE {
|
||||
shadow = 0.0;
|
||||
}
|
||||
|
||||
textureStore(t_shadow_out, coord, vec4<f32>(shadow, 0.0, 0.0, 0.0));
|
||||
}
|
||||
317
crates/voltex_renderer/src/ssgi.rs
Normal file
317
crates/voltex_renderer/src/ssgi.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
pub const SSGI_OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
|
||||
pub const SSGI_KERNEL_SIZE: usize = 64;
|
||||
|
||||
const NOISE_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
|
||||
const NOISE_DIM: u32 = 4; // 4x4 = 16 noise samples
|
||||
|
||||
/// Uniform buffer for the SSGI pass.
|
||||
///
|
||||
/// projection and view are column-major 4x4 matrices stored as flat [f32; 16].
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct SsgiUniform {
|
||||
pub projection: [f32; 16],
|
||||
pub view: [f32; 16],
|
||||
pub radius: f32,
|
||||
pub bias: f32,
|
||||
pub intensity: f32,
|
||||
pub indirect_strength: f32,
|
||||
}
|
||||
|
||||
impl Default for SsgiUniform {
|
||||
fn default() -> Self {
|
||||
// Identity matrices for projection and view.
|
||||
#[rustfmt::skip]
|
||||
let identity = [
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0_f32,
|
||||
];
|
||||
Self {
|
||||
projection: identity,
|
||||
view: identity,
|
||||
radius: 0.5,
|
||||
bias: 0.025,
|
||||
intensity: 1.5,
|
||||
indirect_strength: 0.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GPU resources needed for one SSGI pass.
|
||||
pub struct SsgiResources {
|
||||
pub output_view: wgpu::TextureView,
|
||||
pub kernel_buffer: wgpu::Buffer,
|
||||
pub noise_view: wgpu::TextureView,
|
||||
pub noise_sampler: wgpu::Sampler,
|
||||
pub uniform_buffer: wgpu::Buffer,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl SsgiResources {
|
||||
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) -> Self {
|
||||
let output_view = create_ssgi_output(device, width, height);
|
||||
|
||||
// Hemisphere kernel buffer.
|
||||
let kernel_data = generate_kernel(SSGI_KERNEL_SIZE);
|
||||
// Flatten [f32; 4] array to bytes.
|
||||
let kernel_bytes: Vec<u8> = kernel_data
|
||||
.iter()
|
||||
.flat_map(|v| {
|
||||
v.iter()
|
||||
.flat_map(|f| f.to_ne_bytes())
|
||||
.collect::<Vec<u8>>()
|
||||
})
|
||||
.collect();
|
||||
let kernel_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("SSGI Kernel Buffer"),
|
||||
size: kernel_bytes.len() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
queue.write_buffer(&kernel_buffer, 0, &kernel_bytes);
|
||||
|
||||
// Noise texture.
|
||||
let (noise_view, noise_sampler) = create_noise_texture(device, queue);
|
||||
|
||||
// Uniform buffer with default values.
|
||||
let uniform = SsgiUniform::default();
|
||||
let uniform_bytes = bytemuck::bytes_of(&uniform);
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("SSGI Uniform Buffer"),
|
||||
size: uniform_bytes.len() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
queue.write_buffer(&uniform_buffer, 0, uniform_bytes);
|
||||
|
||||
Self {
|
||||
output_view,
|
||||
kernel_buffer,
|
||||
noise_view,
|
||||
noise_sampler,
|
||||
uniform_buffer,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Recreate only the output texture when the window is resized.
|
||||
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
||||
self.output_view = create_ssgi_output(device, width, height);
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Simple deterministic hash → [0, 1) float.
|
||||
pub fn pseudo_random(seed: u32) -> f32 {
|
||||
let mut x = seed;
|
||||
x = x.wrapping_add(0x9e3779b9);
|
||||
x = x.wrapping_mul(0x6c62272e);
|
||||
x ^= x >> 16;
|
||||
x = x.wrapping_mul(0x45d9f3b);
|
||||
x ^= x >> 16;
|
||||
(x as f32) / (u32::MAX as f32)
|
||||
}
|
||||
|
||||
/// Generate `count` hemisphere samples (z >= 0) biased towards center.
|
||||
/// Each sample is stored as [x, y, z, 0.0] (w=0 unused / padding).
|
||||
pub fn generate_kernel(count: usize) -> Vec<[f32; 4]> {
|
||||
let mut samples = Vec::with_capacity(count);
|
||||
let mut seed = 0u32;
|
||||
|
||||
for i in 0..count {
|
||||
// Random point on hemisphere: z in [0, 1], xy distributed on disk.
|
||||
let r1 = pseudo_random(seed);
|
||||
seed = seed.wrapping_add(1);
|
||||
let r2 = pseudo_random(seed);
|
||||
seed = seed.wrapping_add(1);
|
||||
|
||||
// Spherical coordinates: phi in [0, 2π), cos_theta in [0, 1] (upper hemisphere).
|
||||
let phi = r1 * 2.0 * std::f32::consts::PI;
|
||||
let cos_theta = r2;
|
||||
let sin_theta = (1.0 - cos_theta * cos_theta).max(0.0).sqrt();
|
||||
|
||||
let x = phi.cos() * sin_theta;
|
||||
let y = phi.sin() * sin_theta;
|
||||
let z = cos_theta; // z >= 0 (hemisphere)
|
||||
|
||||
// Accelerating interpolation — scale samples closer to origin.
|
||||
let scale = (i + 1) as f32 / count as f32;
|
||||
let scale = lerp(0.1, 1.0, scale * scale);
|
||||
|
||||
samples.push([x * scale, y * scale, z * scale, 0.0]);
|
||||
}
|
||||
|
||||
samples
|
||||
}
|
||||
|
||||
/// Generate 16 (4×4) random rotation vectors for SSGI noise.
|
||||
/// Each entry is [cos(angle), sin(angle), 0.0, 0.0] (xy rotation in tangent space).
|
||||
pub fn generate_noise_data() -> Vec<[f32; 4]> {
|
||||
let mut data = Vec::with_capacity(16);
|
||||
let mut seed = 1337u32;
|
||||
|
||||
for _ in 0..16 {
|
||||
let angle = pseudo_random(seed) * 2.0 * std::f32::consts::PI;
|
||||
seed = seed.wrapping_add(7);
|
||||
// Normalize: (cos, sin) is already unit length.
|
||||
data.push([angle.cos(), angle.sin(), 0.0, 0.0]);
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a + t * (b - a)
|
||||
}
|
||||
|
||||
/// Create the SSGI output render target (RGBA16Float, screen-sized).
|
||||
pub fn create_ssgi_output(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("SSGI Output Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: SSGI_OUTPUT_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
/// Create the 4×4 SSGI noise texture (Rgba32Float) and its sampler.
|
||||
pub fn create_noise_texture(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
) -> (wgpu::TextureView, wgpu::Sampler) {
|
||||
let noise_data = generate_noise_data();
|
||||
// Flatten [f32; 4] → bytes.
|
||||
let noise_bytes: Vec<u8> = noise_data
|
||||
.iter()
|
||||
.flat_map(|v| v.iter().flat_map(|f| f.to_ne_bytes()).collect::<Vec<u8>>())
|
||||
.collect();
|
||||
|
||||
let extent = wgpu::Extent3d {
|
||||
width: NOISE_DIM,
|
||||
height: NOISE_DIM,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("SSGI Noise Texture"),
|
||||
size: extent,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: NOISE_TEXTURE_FORMAT,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
// Each texel is 4 × 4 bytes = 16 bytes (Rgba32Float).
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&noise_bytes,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(NOISE_DIM * 16), // 4 components × 4 bytes × NOISE_DIM
|
||||
rows_per_image: Some(NOISE_DIM),
|
||||
},
|
||||
extent,
|
||||
);
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
// Wrap/repeat so the noise tiles across the screen.
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("SSGI Noise Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::Repeat,
|
||||
address_mode_v: wgpu::AddressMode::Repeat,
|
||||
address_mode_w: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
(view, sampler)
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_kernel_hemisphere() {
|
||||
let kernel = generate_kernel(SSGI_KERNEL_SIZE);
|
||||
assert_eq!(kernel.len(), SSGI_KERNEL_SIZE);
|
||||
for sample in &kernel {
|
||||
// z component must be >= 0 (upper hemisphere).
|
||||
assert!(
|
||||
sample[2] >= 0.0,
|
||||
"kernel sample z={} is negative",
|
||||
sample[2]
|
||||
);
|
||||
// w component is always 0 (padding).
|
||||
assert_eq!(sample[3], 0.0);
|
||||
// Sample must be within the unit sphere (scale <= 1).
|
||||
let len = (sample[0] * sample[0]
|
||||
+ sample[1] * sample[1]
|
||||
+ sample[2] * sample[2])
|
||||
.sqrt();
|
||||
assert!(len <= 1.0 + 1e-5, "sample length {} > 1.0", len);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noise_data() {
|
||||
let noise = generate_noise_data();
|
||||
assert_eq!(noise.len(), 16);
|
||||
for entry in &noise {
|
||||
// xy should form a unit vector (cos²+sin²=1).
|
||||
let len = (entry[0] * entry[0] + entry[1] * entry[1]).sqrt();
|
||||
assert!(
|
||||
(len - 1.0).abs() < 1e-5,
|
||||
"noise xy length {} != 1.0",
|
||||
len
|
||||
);
|
||||
// z and w are always 0.
|
||||
assert_eq!(entry[2], 0.0);
|
||||
assert_eq!(entry[3], 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniform_defaults() {
|
||||
let u = SsgiUniform::default();
|
||||
assert!((u.radius - 0.5).abs() < f32::EPSILON);
|
||||
assert!((u.bias - 0.025).abs() < f32::EPSILON);
|
||||
assert!((u.intensity - 1.5).abs() < f32::EPSILON);
|
||||
assert!((u.indirect_strength - 0.5).abs() < f32::EPSILON);
|
||||
// Identity diagonal elements.
|
||||
assert_eq!(u.projection[0], 1.0);
|
||||
assert_eq!(u.projection[5], 1.0);
|
||||
assert_eq!(u.projection[10], 1.0);
|
||||
assert_eq!(u.projection[15], 1.0);
|
||||
}
|
||||
}
|
||||
149
crates/voltex_renderer/src/ssgi_shader.wgsl
Normal file
149
crates/voltex_renderer/src/ssgi_shader.wgsl
Normal file
@@ -0,0 +1,149 @@
|
||||
// SSGI (Screen-Space Global Illumination) pass shader.
|
||||
// Reads the G-Buffer and computes per-pixel ambient occlusion + indirect color bleeding.
|
||||
// Output: vec4(ao, indirect_r, indirect_g, indirect_b)
|
||||
|
||||
// ── Group 0: G-Buffer inputs ──────────────────────────────────────────────────
|
||||
|
||||
@group(0) @binding(0) var t_position: texture_2d<f32>;
|
||||
@group(0) @binding(1) var t_normal: texture_2d<f32>;
|
||||
@group(0) @binding(2) var t_albedo: texture_2d<f32>;
|
||||
@group(0) @binding(3) var s_gbuffer: sampler;
|
||||
|
||||
// ── Group 1: SSGI data ────────────────────────────────────────────────────────
|
||||
|
||||
struct SsgiUniform {
|
||||
projection: mat4x4<f32>,
|
||||
view: mat4x4<f32>,
|
||||
radius: f32,
|
||||
bias: f32,
|
||||
intensity: f32,
|
||||
indirect_strength: f32,
|
||||
};
|
||||
|
||||
struct SsgiKernel {
|
||||
samples: array<vec4<f32>, 64>,
|
||||
};
|
||||
|
||||
@group(1) @binding(0) var<uniform> ssgi: SsgiUniform;
|
||||
@group(1) @binding(1) var<uniform> kernel: SsgiKernel;
|
||||
@group(1) @binding(2) var t_noise: texture_2d<f32>;
|
||||
@group(1) @binding(3) var s_noise: sampler;
|
||||
|
||||
// ── Vertex stage ──────────────────────────────────────────────────────────────
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(v: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = vec4<f32>(v.position, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(v.position.x * 0.5 + 0.5, 1.0 - (v.position.y * 0.5 + 0.5));
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── Fragment stage ────────────────────────────────────────────────────────────
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let uv = in.uv;
|
||||
|
||||
// Read world-space position from G-Buffer.
|
||||
let world_pos = textureSample(t_position, s_gbuffer, uv).xyz;
|
||||
|
||||
// Background pixel: return full AO, no indirect light.
|
||||
if length(world_pos) < 0.001 {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
// World-space normal (stored as [0,1] encoded, decode back to [-1,1]).
|
||||
let normal_enc = textureSample(t_normal, s_gbuffer, uv).rgb;
|
||||
let N_world = normalize(normal_enc * 2.0 - 1.0);
|
||||
|
||||
// Albedo for color bleeding.
|
||||
let albedo = textureSample(t_albedo, s_gbuffer, uv).rgb;
|
||||
|
||||
// Convert world-space position and normal to view space.
|
||||
let view_pos4 = ssgi.view * vec4<f32>(world_pos, 1.0);
|
||||
let frag_pos_view = view_pos4.xyz;
|
||||
|
||||
// Normal matrix = transpose(inverse(view)) ≈ mat3(view) for orthonormal view matrix.
|
||||
let N_view = normalize((ssgi.view * vec4<f32>(N_world, 0.0)).xyz);
|
||||
|
||||
// ── TBN from noise ────────────────────────────────────────────────────────
|
||||
// Sample a random rotation vector from the noise texture (tiled 4×4 across screen).
|
||||
let tex_size = vec2<f32>(textureDimensions(t_position));
|
||||
let noise_uv = uv * tex_size / 4.0; // tile the 4x4 noise over the screen
|
||||
let rand_vec = textureSample(t_noise, s_noise, noise_uv).xy;
|
||||
let rand_dir = normalize(vec3<f32>(rand_vec, 0.0));
|
||||
|
||||
// Gram-Schmidt orthogonalization to build TBN in view space.
|
||||
let tangent = normalize(rand_dir - N_view * dot(rand_dir, N_view));
|
||||
let bitangent = cross(N_view, tangent);
|
||||
// TBN matrix columns: tangent, bitangent, normal.
|
||||
|
||||
// ── 64-sample hemisphere loop ─────────────────────────────────────────────
|
||||
var occlusion = 0.0;
|
||||
var indirect_rgb = vec3<f32>(0.0);
|
||||
|
||||
for (var i = 0u; i < 64u; i++) {
|
||||
// Transform kernel sample from tangent space to view space.
|
||||
let s = kernel.samples[i].xyz;
|
||||
let sample_vs = tangent * s.x
|
||||
+ bitangent * s.y
|
||||
+ N_view * s.z;
|
||||
|
||||
// Offset from current view-space fragment position.
|
||||
let sample_pos = frag_pos_view + sample_vs * ssgi.radius;
|
||||
|
||||
// Project sample to get screen UV.
|
||||
let offset4 = ssgi.projection * vec4<f32>(sample_pos, 1.0);
|
||||
let offset_ndc = offset4.xyz / offset4.w;
|
||||
let sample_uv = vec2<f32>(
|
||||
offset_ndc.x * 0.5 + 0.5,
|
||||
1.0 - (offset_ndc.y * 0.5 + 0.5),
|
||||
);
|
||||
|
||||
// Clamp to [0,1] to avoid sampling outside the texture.
|
||||
if sample_uv.x < 0.0 || sample_uv.x > 1.0
|
||||
|| sample_uv.y < 0.0 || sample_uv.y > 1.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Actual geometry depth at the projected UV.
|
||||
let scene_pos_world = textureSample(t_position, s_gbuffer, sample_uv).xyz;
|
||||
let scene_pos_view = (ssgi.view * vec4<f32>(scene_pos_world, 1.0)).xyz;
|
||||
let scene_depth = scene_pos_view.z;
|
||||
|
||||
// Range check: ignore samples that are too far away (avoid halo artefacts).
|
||||
let range_check = smoothstep(0.0, 1.0, ssgi.radius / abs(frag_pos_view.z - scene_depth));
|
||||
|
||||
// Occlusion: geometry behind the sample blocks light.
|
||||
let occluded = select(0.0, 1.0, scene_depth >= sample_pos.z + ssgi.bias);
|
||||
occlusion += occluded * range_check;
|
||||
|
||||
// Color bleeding: gather albedo from occluding surfaces.
|
||||
let neighbor_albedo = textureSample(t_albedo, s_gbuffer, sample_uv).rgb;
|
||||
indirect_rgb += neighbor_albedo * occluded * range_check;
|
||||
}
|
||||
|
||||
// Normalize by sample count.
|
||||
let inv_samples = 1.0 / 64.0;
|
||||
occlusion *= inv_samples;
|
||||
indirect_rgb *= inv_samples;
|
||||
|
||||
// AO: how unoccluded the fragment is (1 = fully lit, 0 = fully occluded).
|
||||
let ao = 1.0 - occlusion * ssgi.intensity;
|
||||
let ao_clamped = clamp(ao, 0.0, 1.0);
|
||||
|
||||
// Indirect light contribution scaled by indirect_strength and albedo.
|
||||
let indirect = indirect_rgb * ssgi.indirect_strength * albedo;
|
||||
|
||||
return vec4<f32>(ao_clamped, indirect.r, indirect.g, indirect.b);
|
||||
}
|
||||
71
crates/voltex_renderer/src/tonemap.rs
Normal file
71
crates/voltex_renderer/src/tonemap.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
/// Uniform buffer for the tonemap pass.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct TonemapUniform {
|
||||
/// Bloom contribution weight.
|
||||
pub bloom_intensity: f32,
|
||||
/// Pre-tonemap exposure multiplier.
|
||||
pub exposure: f32,
|
||||
pub _padding: [f32; 2],
|
||||
}
|
||||
|
||||
impl Default for TonemapUniform {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bloom_intensity: 0.5,
|
||||
exposure: 1.0,
|
||||
_padding: [0.0; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CPU implementation of the ACES filmic tonemap curve (for testing / CPU-side work).
|
||||
///
|
||||
/// Formula: clamp((x*(2.51*x+0.03))/(x*(2.43*x+0.59)+0.14), 0, 1)
|
||||
pub fn aces_tonemap(x: f32) -> f32 {
|
||||
let num = x * (2.51 * x + 0.03);
|
||||
let den = x * (2.43 * x + 0.59) + 0.14;
|
||||
(num / den).clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn aces_zero() {
|
||||
// aces(0) should be ≈ 0
|
||||
assert!(aces_tonemap(0.0).abs() < 1e-5, "aces(0) = {}", aces_tonemap(0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aces_one() {
|
||||
// aces(1) ≈ 0.80 with the standard formula
|
||||
// clamp((1*(2.51+0.03))/(1*(2.43+0.59)+0.14), 0, 1) = 2.54/3.16 ≈ 0.8038
|
||||
let v = aces_tonemap(1.0);
|
||||
assert!(
|
||||
(v - 0.8038).abs() < 0.001,
|
||||
"aces(1) = {}, expected ≈ 0.8038",
|
||||
v
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aces_large() {
|
||||
// aces(10) should be very close to 1.0 (saturated)
|
||||
let v = aces_tonemap(10.0);
|
||||
assert!(v > 0.999, "aces(10) = {}, expected ≈ 1.0", v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tonemap_uniform_default() {
|
||||
let u = TonemapUniform::default();
|
||||
assert!((u.bloom_intensity - 0.5).abs() < f32::EPSILON);
|
||||
assert!((u.exposure - 1.0).abs() < f32::EPSILON);
|
||||
assert_eq!(u._padding, [0.0f32; 2]);
|
||||
}
|
||||
}
|
||||
68
crates/voltex_renderer/src/tonemap_shader.wgsl
Normal file
68
crates/voltex_renderer/src/tonemap_shader.wgsl
Normal file
@@ -0,0 +1,68 @@
|
||||
// Tonemap pass shader.
|
||||
// Combines HDR scene colour + bloom, applies exposure and ACES tonemapping,
|
||||
// then converts to gamma-corrected sRGB for the swapchain.
|
||||
|
||||
// ── Group 0 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@group(0) @binding(0) var t_hdr: texture_2d<f32>;
|
||||
@group(0) @binding(1) var t_bloom: texture_2d<f32>;
|
||||
@group(0) @binding(2) var s_sampler: sampler;
|
||||
|
||||
struct TonemapUniform {
|
||||
bloom_intensity: f32,
|
||||
exposure: f32,
|
||||
_padding: vec2<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(3) var<uniform> tonemap: TonemapUniform;
|
||||
|
||||
// ── Vertex stage ──────────────────────────────────────────────────────────────
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(v: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = vec4<f32>(v.position, 0.0, 1.0);
|
||||
out.uv = vec2<f32>(v.position.x * 0.5 + 0.5, 1.0 - (v.position.y * 0.5 + 0.5));
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── ACES tonemap ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn aces_tonemap(x: vec3<f32>) -> vec3<f32> {
|
||||
let num = x * (2.51 * x + vec3<f32>(0.03));
|
||||
let den = x * (2.43 * x + vec3<f32>(0.59)) + vec3<f32>(0.14);
|
||||
return clamp(num / den, vec3<f32>(0.0), vec3<f32>(1.0));
|
||||
}
|
||||
|
||||
// ── Fragment stage ────────────────────────────────────────────────────────────
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let uv = in.uv;
|
||||
|
||||
let hdr = textureSample(t_hdr, s_sampler, uv).rgb;
|
||||
let bloom = textureSample(t_bloom, s_sampler, uv).rgb;
|
||||
|
||||
// Combine HDR + bloom
|
||||
var color = hdr + bloom * tonemap.bloom_intensity;
|
||||
|
||||
// Apply exposure
|
||||
color = color * tonemap.exposure;
|
||||
|
||||
// ACES tonemapping
|
||||
color = aces_tonemap(color);
|
||||
|
||||
// Gamma correction (linear → sRGB, γ = 2.2)
|
||||
color = pow(color, vec3<f32>(1.0 / 2.2));
|
||||
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
9
crates/voltex_script/Cargo.toml
Normal file
9
crates/voltex_script/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "voltex_script"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
19
crates/voltex_script/build.rs
Normal file
19
crates/voltex_script/build.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
fn main() {
|
||||
let mut build = cc::Build::new();
|
||||
build.include("lua");
|
||||
|
||||
// Add all .c files except lua.c and luac.c (standalone executables with main())
|
||||
for entry in std::fs::read_dir("lua").unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
if let Some(ext) = path.extension() {
|
||||
if ext == "c" {
|
||||
let name = path.file_name().unwrap().to_str().unwrap();
|
||||
if name != "lua.c" && name != "luac.c" {
|
||||
build.file(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build.compile("lua54");
|
||||
}
|
||||
1463
crates/voltex_script/lua/lapi.c
Normal file
1463
crates/voltex_script/lua/lapi.c
Normal file
File diff suppressed because it is too large
Load Diff
52
crates/voltex_script/lua/lapi.h
Normal file
52
crates/voltex_script/lua/lapi.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
** $Id: lapi.h $
|
||||
** Auxiliary functions from Lua API
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef lapi_h
|
||||
#define lapi_h
|
||||
|
||||
|
||||
#include "llimits.h"
|
||||
#include "lstate.h"
|
||||
|
||||
|
||||
/* Increments 'L->top.p', checking for stack overflows */
|
||||
#define api_incr_top(L) {L->top.p++; \
|
||||
api_check(L, L->top.p <= L->ci->top.p, \
|
||||
"stack overflow");}
|
||||
|
||||
|
||||
/*
|
||||
** If a call returns too many multiple returns, the callee may not have
|
||||
** stack space to accommodate all results. In this case, this macro
|
||||
** increases its stack space ('L->ci->top.p').
|
||||
*/
|
||||
#define adjustresults(L,nres) \
|
||||
{ if ((nres) <= LUA_MULTRET && L->ci->top.p < L->top.p) \
|
||||
L->ci->top.p = L->top.p; }
|
||||
|
||||
|
||||
/* Ensure the stack has at least 'n' elements */
|
||||
#define api_checknelems(L,n) \
|
||||
api_check(L, (n) < (L->top.p - L->ci->func.p), \
|
||||
"not enough elements in the stack")
|
||||
|
||||
|
||||
/*
|
||||
** To reduce the overhead of returning from C functions, the presence of
|
||||
** to-be-closed variables in these functions is coded in the CallInfo's
|
||||
** field 'nresults', in a way that functions with no to-be-closed variables
|
||||
** with zero, one, or "all" wanted results have no overhead. Functions
|
||||
** with other number of wanted results, as well as functions with
|
||||
** variables to be closed, have an extra check.
|
||||
*/
|
||||
|
||||
#define hastocloseCfunc(n) ((n) < LUA_MULTRET)
|
||||
|
||||
/* Map [-1, inf) (range of 'nresults') into (-inf, -2] */
|
||||
#define codeNresults(n) (-(n) - 3)
|
||||
#define decodeNresults(n) (-(n) - 3)
|
||||
|
||||
#endif
|
||||
1126
crates/voltex_script/lua/lauxlib.c
Normal file
1126
crates/voltex_script/lua/lauxlib.c
Normal file
File diff suppressed because it is too large
Load Diff
301
crates/voltex_script/lua/lauxlib.h
Normal file
301
crates/voltex_script/lua/lauxlib.h
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
** $Id: lauxlib.h $
|
||||
** Auxiliary functions for building Lua libraries
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
|
||||
#ifndef lauxlib_h
|
||||
#define lauxlib_h
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "luaconf.h"
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
/* global table */
|
||||
#define LUA_GNAME "_G"
|
||||
|
||||
|
||||
typedef struct luaL_Buffer luaL_Buffer;
|
||||
|
||||
|
||||
/* extra error code for 'luaL_loadfilex' */
|
||||
#define LUA_ERRFILE (LUA_ERRERR+1)
|
||||
|
||||
|
||||
/* key, in the registry, for table of loaded modules */
|
||||
#define LUA_LOADED_TABLE "_LOADED"
|
||||
|
||||
|
||||
/* key, in the registry, for table of preloaded loaders */
|
||||
#define LUA_PRELOAD_TABLE "_PRELOAD"
|
||||
|
||||
|
||||
typedef struct luaL_Reg {
|
||||
const char *name;
|
||||
lua_CFunction func;
|
||||
} luaL_Reg;
|
||||
|
||||
|
||||
#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number))
|
||||
|
||||
LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz);
|
||||
#define luaL_checkversion(L) \
|
||||
luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES)
|
||||
|
||||
LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);
|
||||
LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);
|
||||
LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len);
|
||||
LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg);
|
||||
LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname);
|
||||
LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg,
|
||||
size_t *l);
|
||||
LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg,
|
||||
const char *def, size_t *l);
|
||||
LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg);
|
||||
LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def);
|
||||
|
||||
LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg);
|
||||
LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg,
|
||||
lua_Integer def);
|
||||
|
||||
LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);
|
||||
LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t);
|
||||
LUALIB_API void (luaL_checkany) (lua_State *L, int arg);
|
||||
|
||||
LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname);
|
||||
LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname);
|
||||
LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);
|
||||
LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname);
|
||||
|
||||
LUALIB_API void (luaL_where) (lua_State *L, int lvl);
|
||||
LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);
|
||||
|
||||
LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def,
|
||||
const char *const lst[]);
|
||||
|
||||
LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname);
|
||||
LUALIB_API int (luaL_execresult) (lua_State *L, int stat);
|
||||
|
||||
|
||||
/* predefined references */
|
||||
#define LUA_NOREF (-2)
|
||||
#define LUA_REFNIL (-1)
|
||||
|
||||
LUALIB_API int (luaL_ref) (lua_State *L, int t);
|
||||
LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref);
|
||||
|
||||
LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename,
|
||||
const char *mode);
|
||||
|
||||
#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL)
|
||||
|
||||
LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz,
|
||||
const char *name, const char *mode);
|
||||
LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);
|
||||
|
||||
LUALIB_API lua_State *(luaL_newstate) (void);
|
||||
|
||||
LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx);
|
||||
|
||||
LUALIB_API void (luaL_addgsub) (luaL_Buffer *b, const char *s,
|
||||
const char *p, const char *r);
|
||||
LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s,
|
||||
const char *p, const char *r);
|
||||
|
||||
LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup);
|
||||
|
||||
LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname);
|
||||
|
||||
LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1,
|
||||
const char *msg, int level);
|
||||
|
||||
LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname,
|
||||
lua_CFunction openf, int glb);
|
||||
|
||||
/*
|
||||
** ===============================================================
|
||||
** some useful macros
|
||||
** ===============================================================
|
||||
*/
|
||||
|
||||
|
||||
#define luaL_newlibtable(L,l) \
|
||||
lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
|
||||
|
||||
#define luaL_newlib(L,l) \
|
||||
(luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
|
||||
|
||||
#define luaL_argcheck(L, cond,arg,extramsg) \
|
||||
((void)(luai_likely(cond) || luaL_argerror(L, (arg), (extramsg))))
|
||||
|
||||
#define luaL_argexpected(L,cond,arg,tname) \
|
||||
((void)(luai_likely(cond) || luaL_typeerror(L, (arg), (tname))))
|
||||
|
||||
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
|
||||
#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
|
||||
|
||||
#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
|
||||
|
||||
#define luaL_dofile(L, fn) \
|
||||
(luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
|
||||
|
||||
#define luaL_dostring(L, s) \
|
||||
(luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
|
||||
|
||||
#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
|
||||
|
||||
#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
|
||||
|
||||
#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL)
|
||||
|
||||
|
||||
/*
|
||||
** Perform arithmetic operations on lua_Integer values with wrap-around
|
||||
** semantics, as the Lua core does.
|
||||
*/
|
||||
#define luaL_intop(op,v1,v2) \
|
||||
((lua_Integer)((lua_Unsigned)(v1) op (lua_Unsigned)(v2)))
|
||||
|
||||
|
||||
/* push the value used to represent failure/error */
|
||||
#define luaL_pushfail(L) lua_pushnil(L)
|
||||
|
||||
|
||||
/*
|
||||
** Internal assertions for in-house debugging
|
||||
*/
|
||||
#if !defined(lua_assert)
|
||||
|
||||
#if defined LUAI_ASSERT
|
||||
#include <assert.h>
|
||||
#define lua_assert(c) assert(c)
|
||||
#else
|
||||
#define lua_assert(c) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Generic Buffer manipulation
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
struct luaL_Buffer {
|
||||
char *b; /* buffer address */
|
||||
size_t size; /* buffer size */
|
||||
size_t n; /* number of characters in buffer */
|
||||
lua_State *L;
|
||||
union {
|
||||
LUAI_MAXALIGN; /* ensure maximum alignment for buffer */
|
||||
char b[LUAL_BUFFERSIZE]; /* initial buffer */
|
||||
} init;
|
||||
};
|
||||
|
||||
|
||||
#define luaL_bufflen(bf) ((bf)->n)
|
||||
#define luaL_buffaddr(bf) ((bf)->b)
|
||||
|
||||
|
||||
#define luaL_addchar(B,c) \
|
||||
((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \
|
||||
((B)->b[(B)->n++] = (c)))
|
||||
|
||||
#define luaL_addsize(B,s) ((B)->n += (s))
|
||||
|
||||
#define luaL_buffsub(B,s) ((B)->n -= (s))
|
||||
|
||||
LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B);
|
||||
LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz);
|
||||
LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
|
||||
LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s);
|
||||
LUALIB_API void (luaL_addvalue) (luaL_Buffer *B);
|
||||
LUALIB_API void (luaL_pushresult) (luaL_Buffer *B);
|
||||
LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz);
|
||||
LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz);
|
||||
|
||||
#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** File handles for IO library
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and
|
||||
** initial structure 'luaL_Stream' (it may contain other fields
|
||||
** after that initial structure).
|
||||
*/
|
||||
|
||||
#define LUA_FILEHANDLE "FILE*"
|
||||
|
||||
|
||||
typedef struct luaL_Stream {
|
||||
FILE *f; /* stream (NULL for incompletely created streams) */
|
||||
lua_CFunction closef; /* to close stream (NULL for closed streams) */
|
||||
} luaL_Stream;
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** "Abstraction Layer" for basic report of messages and errors
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/* print a string */
|
||||
#if !defined(lua_writestring)
|
||||
#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout)
|
||||
#endif
|
||||
|
||||
/* print a newline and flush the output */
|
||||
#if !defined(lua_writeline)
|
||||
#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout))
|
||||
#endif
|
||||
|
||||
/* print an error message */
|
||||
#if !defined(lua_writestringerror)
|
||||
#define lua_writestringerror(s,p) \
|
||||
(fprintf(stderr, (s), (p)), fflush(stderr))
|
||||
#endif
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {============================================================
|
||||
** Compatibility with deprecated conversions
|
||||
** =============================================================
|
||||
*/
|
||||
#if defined(LUA_COMPAT_APIINTCASTS)
|
||||
|
||||
#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a))
|
||||
#define luaL_optunsigned(L,a,d) \
|
||||
((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d)))
|
||||
|
||||
#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
|
||||
#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
|
||||
|
||||
#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
|
||||
#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
|
||||
|
||||
#endif
|
||||
/* }============================================================ */
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
549
crates/voltex_script/lua/lbaselib.c
Normal file
549
crates/voltex_script/lua/lbaselib.c
Normal file
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
** $Id: lbaselib.c $
|
||||
** Basic library
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lbaselib_c
|
||||
#define LUA_LIB
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
static int luaB_print (lua_State *L) {
|
||||
int n = lua_gettop(L); /* number of arguments */
|
||||
int i;
|
||||
for (i = 1; i <= n; i++) { /* for each argument */
|
||||
size_t l;
|
||||
const char *s = luaL_tolstring(L, i, &l); /* convert it to string */
|
||||
if (i > 1) /* not the first element? */
|
||||
lua_writestring("\t", 1); /* add a tab before it */
|
||||
lua_writestring(s, l); /* print it */
|
||||
lua_pop(L, 1); /* pop result */
|
||||
}
|
||||
lua_writeline();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Creates a warning with all given arguments.
|
||||
** Check first for errors; otherwise an error may interrupt
|
||||
** the composition of a warning, leaving it unfinished.
|
||||
*/
|
||||
static int luaB_warn (lua_State *L) {
|
||||
int n = lua_gettop(L); /* number of arguments */
|
||||
int i;
|
||||
luaL_checkstring(L, 1); /* at least one argument */
|
||||
for (i = 2; i <= n; i++)
|
||||
luaL_checkstring(L, i); /* make sure all arguments are strings */
|
||||
for (i = 1; i < n; i++) /* compose warning */
|
||||
lua_warning(L, lua_tostring(L, i), 1);
|
||||
lua_warning(L, lua_tostring(L, n), 0); /* close warning */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#define SPACECHARS " \f\n\r\t\v"
|
||||
|
||||
static const char *b_str2int (const char *s, int base, lua_Integer *pn) {
|
||||
lua_Unsigned n = 0;
|
||||
int neg = 0;
|
||||
s += strspn(s, SPACECHARS); /* skip initial spaces */
|
||||
if (*s == '-') { s++; neg = 1; } /* handle sign */
|
||||
else if (*s == '+') s++;
|
||||
if (!isalnum((unsigned char)*s)) /* no digit? */
|
||||
return NULL;
|
||||
do {
|
||||
int digit = (isdigit((unsigned char)*s)) ? *s - '0'
|
||||
: (toupper((unsigned char)*s) - 'A') + 10;
|
||||
if (digit >= base) return NULL; /* invalid numeral */
|
||||
n = n * base + digit;
|
||||
s++;
|
||||
} while (isalnum((unsigned char)*s));
|
||||
s += strspn(s, SPACECHARS); /* skip trailing spaces */
|
||||
*pn = (lua_Integer)((neg) ? (0u - n) : n);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_tonumber (lua_State *L) {
|
||||
if (lua_isnoneornil(L, 2)) { /* standard conversion? */
|
||||
if (lua_type(L, 1) == LUA_TNUMBER) { /* already a number? */
|
||||
lua_settop(L, 1); /* yes; return it */
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
size_t l;
|
||||
const char *s = lua_tolstring(L, 1, &l);
|
||||
if (s != NULL && lua_stringtonumber(L, s) == l + 1)
|
||||
return 1; /* successful conversion to number */
|
||||
/* else not a number */
|
||||
luaL_checkany(L, 1); /* (but there must be some parameter) */
|
||||
}
|
||||
}
|
||||
else {
|
||||
size_t l;
|
||||
const char *s;
|
||||
lua_Integer n = 0; /* to avoid warnings */
|
||||
lua_Integer base = luaL_checkinteger(L, 2);
|
||||
luaL_checktype(L, 1, LUA_TSTRING); /* no numbers as strings */
|
||||
s = lua_tolstring(L, 1, &l);
|
||||
luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");
|
||||
if (b_str2int(s, (int)base, &n) == s + l) {
|
||||
lua_pushinteger(L, n);
|
||||
return 1;
|
||||
} /* else not a number */
|
||||
} /* else not a number */
|
||||
luaL_pushfail(L); /* not a number */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_error (lua_State *L) {
|
||||
int level = (int)luaL_optinteger(L, 2, 1);
|
||||
lua_settop(L, 1);
|
||||
if (lua_type(L, 1) == LUA_TSTRING && level > 0) {
|
||||
luaL_where(L, level); /* add extra information */
|
||||
lua_pushvalue(L, 1);
|
||||
lua_concat(L, 2);
|
||||
}
|
||||
return lua_error(L);
|
||||
}
|
||||
|
||||
|
||||
static int luaB_getmetatable (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
if (!lua_getmetatable(L, 1)) {
|
||||
lua_pushnil(L);
|
||||
return 1; /* no metatable */
|
||||
}
|
||||
luaL_getmetafield(L, 1, "__metatable");
|
||||
return 1; /* returns either __metatable field (if present) or metatable */
|
||||
}
|
||||
|
||||
|
||||
static int luaB_setmetatable (lua_State *L) {
|
||||
int t = lua_type(L, 2);
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table");
|
||||
if (l_unlikely(luaL_getmetafield(L, 1, "__metatable") != LUA_TNIL))
|
||||
return luaL_error(L, "cannot change a protected metatable");
|
||||
lua_settop(L, 2);
|
||||
lua_setmetatable(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_rawequal (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
luaL_checkany(L, 2);
|
||||
lua_pushboolean(L, lua_rawequal(L, 1, 2));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_rawlen (lua_State *L) {
|
||||
int t = lua_type(L, 1);
|
||||
luaL_argexpected(L, t == LUA_TTABLE || t == LUA_TSTRING, 1,
|
||||
"table or string");
|
||||
lua_pushinteger(L, lua_rawlen(L, 1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_rawget (lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_checkany(L, 2);
|
||||
lua_settop(L, 2);
|
||||
lua_rawget(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luaB_rawset (lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
luaL_checkany(L, 2);
|
||||
luaL_checkany(L, 3);
|
||||
lua_settop(L, 3);
|
||||
lua_rawset(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int pushmode (lua_State *L, int oldmode) {
|
||||
if (oldmode == -1)
|
||||
luaL_pushfail(L); /* invalid call to 'lua_gc' */
|
||||
else
|
||||
lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental"
|
||||
: "generational");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** check whether call to 'lua_gc' was valid (not inside a finalizer)
|
||||
*/
|
||||
#define checkvalres(res) { if (res == -1) break; }
|
||||
|
||||
static int luaB_collectgarbage (lua_State *L) {
|
||||
static const char *const opts[] = {"stop", "restart", "collect",
|
||||
"count", "step", "setpause", "setstepmul",
|
||||
"isrunning", "generational", "incremental", NULL};
|
||||
static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT,
|
||||
LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL,
|
||||
LUA_GCISRUNNING, LUA_GCGEN, LUA_GCINC};
|
||||
int o = optsnum[luaL_checkoption(L, 1, "collect", opts)];
|
||||
switch (o) {
|
||||
case LUA_GCCOUNT: {
|
||||
int k = lua_gc(L, o);
|
||||
int b = lua_gc(L, LUA_GCCOUNTB);
|
||||
checkvalres(k);
|
||||
lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024));
|
||||
return 1;
|
||||
}
|
||||
case LUA_GCSTEP: {
|
||||
int step = (int)luaL_optinteger(L, 2, 0);
|
||||
int res = lua_gc(L, o, step);
|
||||
checkvalres(res);
|
||||
lua_pushboolean(L, res);
|
||||
return 1;
|
||||
}
|
||||
case LUA_GCSETPAUSE:
|
||||
case LUA_GCSETSTEPMUL: {
|
||||
int p = (int)luaL_optinteger(L, 2, 0);
|
||||
int previous = lua_gc(L, o, p);
|
||||
checkvalres(previous);
|
||||
lua_pushinteger(L, previous);
|
||||
return 1;
|
||||
}
|
||||
case LUA_GCISRUNNING: {
|
||||
int res = lua_gc(L, o);
|
||||
checkvalres(res);
|
||||
lua_pushboolean(L, res);
|
||||
return 1;
|
||||
}
|
||||
case LUA_GCGEN: {
|
||||
int minormul = (int)luaL_optinteger(L, 2, 0);
|
||||
int majormul = (int)luaL_optinteger(L, 3, 0);
|
||||
return pushmode(L, lua_gc(L, o, minormul, majormul));
|
||||
}
|
||||
case LUA_GCINC: {
|
||||
int pause = (int)luaL_optinteger(L, 2, 0);
|
||||
int stepmul = (int)luaL_optinteger(L, 3, 0);
|
||||
int stepsize = (int)luaL_optinteger(L, 4, 0);
|
||||
return pushmode(L, lua_gc(L, o, pause, stepmul, stepsize));
|
||||
}
|
||||
default: {
|
||||
int res = lua_gc(L, o);
|
||||
checkvalres(res);
|
||||
lua_pushinteger(L, res);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
luaL_pushfail(L); /* invalid call (inside a finalizer) */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_type (lua_State *L) {
|
||||
int t = lua_type(L, 1);
|
||||
luaL_argcheck(L, t != LUA_TNONE, 1, "value expected");
|
||||
lua_pushstring(L, lua_typename(L, t));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_next (lua_State *L) {
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
lua_settop(L, 2); /* create a 2nd argument if there isn't one */
|
||||
if (lua_next(L, 1))
|
||||
return 2;
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int pairscont (lua_State *L, int status, lua_KContext k) {
|
||||
(void)L; (void)status; (void)k; /* unused */
|
||||
return 3;
|
||||
}
|
||||
|
||||
static int luaB_pairs (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */
|
||||
lua_pushcfunction(L, luaB_next); /* will return generator, */
|
||||
lua_pushvalue(L, 1); /* state, */
|
||||
lua_pushnil(L); /* and initial value */
|
||||
}
|
||||
else {
|
||||
lua_pushvalue(L, 1); /* argument 'self' to metamethod */
|
||||
lua_callk(L, 1, 3, 0, pairscont); /* get 3 values from metamethod */
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Traversal function for 'ipairs'
|
||||
*/
|
||||
static int ipairsaux (lua_State *L) {
|
||||
lua_Integer i = luaL_checkinteger(L, 2);
|
||||
i = luaL_intop(+, i, 1);
|
||||
lua_pushinteger(L, i);
|
||||
return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** 'ipairs' function. Returns 'ipairsaux', given "table", 0.
|
||||
** (The given "table" may not be a table.)
|
||||
*/
|
||||
static int luaB_ipairs (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
lua_pushcfunction(L, ipairsaux); /* iteration function */
|
||||
lua_pushvalue(L, 1); /* state */
|
||||
lua_pushinteger(L, 0); /* initial value */
|
||||
return 3;
|
||||
}
|
||||
|
||||
|
||||
static int load_aux (lua_State *L, int status, int envidx) {
|
||||
if (l_likely(status == LUA_OK)) {
|
||||
if (envidx != 0) { /* 'env' parameter? */
|
||||
lua_pushvalue(L, envidx); /* environment for loaded function */
|
||||
if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */
|
||||
lua_pop(L, 1); /* remove 'env' if not used by previous call */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else { /* error (message is on top of the stack) */
|
||||
luaL_pushfail(L);
|
||||
lua_insert(L, -2); /* put before error message */
|
||||
return 2; /* return fail plus error message */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int luaB_loadfile (lua_State *L) {
|
||||
const char *fname = luaL_optstring(L, 1, NULL);
|
||||
const char *mode = luaL_optstring(L, 2, NULL);
|
||||
int env = (!lua_isnone(L, 3) ? 3 : 0); /* 'env' index or 0 if no 'env' */
|
||||
int status = luaL_loadfilex(L, fname, mode);
|
||||
return load_aux(L, status, env);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Generic Read function
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** reserved slot, above all arguments, to hold a copy of the returned
|
||||
** string to avoid it being collected while parsed. 'load' has four
|
||||
** optional arguments (chunk, source name, mode, and environment).
|
||||
*/
|
||||
#define RESERVEDSLOT 5
|
||||
|
||||
|
||||
/*
|
||||
** Reader for generic 'load' function: 'lua_load' uses the
|
||||
** stack for internal stuff, so the reader cannot change the
|
||||
** stack top. Instead, it keeps its resulting string in a
|
||||
** reserved slot inside the stack.
|
||||
*/
|
||||
static const char *generic_reader (lua_State *L, void *ud, size_t *size) {
|
||||
(void)(ud); /* not used */
|
||||
luaL_checkstack(L, 2, "too many nested functions");
|
||||
lua_pushvalue(L, 1); /* get function */
|
||||
lua_call(L, 0, 1); /* call it */
|
||||
if (lua_isnil(L, -1)) {
|
||||
lua_pop(L, 1); /* pop result */
|
||||
*size = 0;
|
||||
return NULL;
|
||||
}
|
||||
else if (l_unlikely(!lua_isstring(L, -1)))
|
||||
luaL_error(L, "reader function must return a string");
|
||||
lua_replace(L, RESERVEDSLOT); /* save string in reserved slot */
|
||||
return lua_tolstring(L, RESERVEDSLOT, size);
|
||||
}
|
||||
|
||||
|
||||
static int luaB_load (lua_State *L) {
|
||||
int status;
|
||||
size_t l;
|
||||
const char *s = lua_tolstring(L, 1, &l);
|
||||
const char *mode = luaL_optstring(L, 3, "bt");
|
||||
int env = (!lua_isnone(L, 4) ? 4 : 0); /* 'env' index or 0 if no 'env' */
|
||||
if (s != NULL) { /* loading a string? */
|
||||
const char *chunkname = luaL_optstring(L, 2, s);
|
||||
status = luaL_loadbufferx(L, s, l, chunkname, mode);
|
||||
}
|
||||
else { /* loading from a reader function */
|
||||
const char *chunkname = luaL_optstring(L, 2, "=(load)");
|
||||
luaL_checktype(L, 1, LUA_TFUNCTION);
|
||||
lua_settop(L, RESERVEDSLOT); /* create reserved slot */
|
||||
status = lua_load(L, generic_reader, NULL, chunkname, mode);
|
||||
}
|
||||
return load_aux(L, status, env);
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
static int dofilecont (lua_State *L, int d1, lua_KContext d2) {
|
||||
(void)d1; (void)d2; /* only to match 'lua_Kfunction' prototype */
|
||||
return lua_gettop(L) - 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_dofile (lua_State *L) {
|
||||
const char *fname = luaL_optstring(L, 1, NULL);
|
||||
lua_settop(L, 1);
|
||||
if (l_unlikely(luaL_loadfile(L, fname) != LUA_OK))
|
||||
return lua_error(L);
|
||||
lua_callk(L, 0, LUA_MULTRET, 0, dofilecont);
|
||||
return dofilecont(L, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static int luaB_assert (lua_State *L) {
|
||||
if (l_likely(lua_toboolean(L, 1))) /* condition is true? */
|
||||
return lua_gettop(L); /* return all arguments */
|
||||
else { /* error */
|
||||
luaL_checkany(L, 1); /* there must be a condition */
|
||||
lua_remove(L, 1); /* remove it */
|
||||
lua_pushliteral(L, "assertion failed!"); /* default message */
|
||||
lua_settop(L, 1); /* leave only message (default if no other one) */
|
||||
return luaB_error(L); /* call 'error' */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int luaB_select (lua_State *L) {
|
||||
int n = lua_gettop(L);
|
||||
if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') {
|
||||
lua_pushinteger(L, n-1);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
lua_Integer i = luaL_checkinteger(L, 1);
|
||||
if (i < 0) i = n + i;
|
||||
else if (i > n) i = n;
|
||||
luaL_argcheck(L, 1 <= i, 1, "index out of range");
|
||||
return n - (int)i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Continuation function for 'pcall' and 'xpcall'. Both functions
|
||||
** already pushed a 'true' before doing the call, so in case of success
|
||||
** 'finishpcall' only has to return everything in the stack minus
|
||||
** 'extra' values (where 'extra' is exactly the number of items to be
|
||||
** ignored).
|
||||
*/
|
||||
static int finishpcall (lua_State *L, int status, lua_KContext extra) {
|
||||
if (l_unlikely(status != LUA_OK && status != LUA_YIELD)) { /* error? */
|
||||
lua_pushboolean(L, 0); /* first result (false) */
|
||||
lua_pushvalue(L, -2); /* error message */
|
||||
return 2; /* return false, msg */
|
||||
}
|
||||
else
|
||||
return lua_gettop(L) - (int)extra; /* return all results */
|
||||
}
|
||||
|
||||
|
||||
static int luaB_pcall (lua_State *L) {
|
||||
int status;
|
||||
luaL_checkany(L, 1);
|
||||
lua_pushboolean(L, 1); /* first result if no errors */
|
||||
lua_insert(L, 1); /* put it in place */
|
||||
status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 0, 0, finishpcall);
|
||||
return finishpcall(L, status, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Do a protected call with error handling. After 'lua_rotate', the
|
||||
** stack will have <f, err, true, f, [args...]>; so, the function passes
|
||||
** 2 to 'finishpcall' to skip the 2 first values when returning results.
|
||||
*/
|
||||
static int luaB_xpcall (lua_State *L) {
|
||||
int status;
|
||||
int n = lua_gettop(L);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION); /* check error function */
|
||||
lua_pushboolean(L, 1); /* first result */
|
||||
lua_pushvalue(L, 1); /* function */
|
||||
lua_rotate(L, 3, 2); /* move them below function's arguments */
|
||||
status = lua_pcallk(L, n - 2, LUA_MULTRET, 2, 2, finishpcall);
|
||||
return finishpcall(L, status, 2);
|
||||
}
|
||||
|
||||
|
||||
static int luaB_tostring (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
luaL_tolstring(L, 1, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg base_funcs[] = {
|
||||
{"assert", luaB_assert},
|
||||
{"collectgarbage", luaB_collectgarbage},
|
||||
{"dofile", luaB_dofile},
|
||||
{"error", luaB_error},
|
||||
{"getmetatable", luaB_getmetatable},
|
||||
{"ipairs", luaB_ipairs},
|
||||
{"loadfile", luaB_loadfile},
|
||||
{"load", luaB_load},
|
||||
{"next", luaB_next},
|
||||
{"pairs", luaB_pairs},
|
||||
{"pcall", luaB_pcall},
|
||||
{"print", luaB_print},
|
||||
{"warn", luaB_warn},
|
||||
{"rawequal", luaB_rawequal},
|
||||
{"rawlen", luaB_rawlen},
|
||||
{"rawget", luaB_rawget},
|
||||
{"rawset", luaB_rawset},
|
||||
{"select", luaB_select},
|
||||
{"setmetatable", luaB_setmetatable},
|
||||
{"tonumber", luaB_tonumber},
|
||||
{"tostring", luaB_tostring},
|
||||
{"type", luaB_type},
|
||||
{"xpcall", luaB_xpcall},
|
||||
/* placeholders */
|
||||
{LUA_GNAME, NULL},
|
||||
{"_VERSION", NULL},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
LUAMOD_API int luaopen_base (lua_State *L) {
|
||||
/* open lib into global table */
|
||||
lua_pushglobaltable(L);
|
||||
luaL_setfuncs(L, base_funcs, 0);
|
||||
/* set global _G */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, LUA_GNAME);
|
||||
/* set global _VERSION */
|
||||
lua_pushliteral(L, LUA_VERSION);
|
||||
lua_setfield(L, -2, "_VERSION");
|
||||
return 1;
|
||||
}
|
||||
|
||||
1874
crates/voltex_script/lua/lcode.c
Normal file
1874
crates/voltex_script/lua/lcode.c
Normal file
File diff suppressed because it is too large
Load Diff
101
crates/voltex_script/lua/lcode.h
Normal file
101
crates/voltex_script/lua/lcode.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
** $Id: lcode.h $
|
||||
** Code generator for Lua
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef lcode_h
|
||||
#define lcode_h
|
||||
|
||||
#include "llex.h"
|
||||
#include "lobject.h"
|
||||
#include "lopcodes.h"
|
||||
#include "lparser.h"
|
||||
|
||||
|
||||
/*
|
||||
** Marks the end of a patch list. It is an invalid value both as an absolute
|
||||
** address, and as a list link (would link an element to itself).
|
||||
*/
|
||||
#define NO_JUMP (-1)
|
||||
|
||||
|
||||
/*
|
||||
** grep "ORDER OPR" if you change these enums (ORDER OP)
|
||||
*/
|
||||
typedef enum BinOpr {
|
||||
/* arithmetic operators */
|
||||
OPR_ADD, OPR_SUB, OPR_MUL, OPR_MOD, OPR_POW,
|
||||
OPR_DIV, OPR_IDIV,
|
||||
/* bitwise operators */
|
||||
OPR_BAND, OPR_BOR, OPR_BXOR,
|
||||
OPR_SHL, OPR_SHR,
|
||||
/* string operator */
|
||||
OPR_CONCAT,
|
||||
/* comparison operators */
|
||||
OPR_EQ, OPR_LT, OPR_LE,
|
||||
OPR_NE, OPR_GT, OPR_GE,
|
||||
/* logical operators */
|
||||
OPR_AND, OPR_OR,
|
||||
OPR_NOBINOPR
|
||||
} BinOpr;
|
||||
|
||||
|
||||
/* true if operation is foldable (that is, it is arithmetic or bitwise) */
|
||||
#define foldbinop(op) ((op) <= OPR_SHR)
|
||||
|
||||
|
||||
#define luaK_codeABC(fs,o,a,b,c) luaK_codeABCk(fs,o,a,b,c,0)
|
||||
|
||||
|
||||
typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr;
|
||||
|
||||
|
||||
/* get (pointer to) instruction of given 'expdesc' */
|
||||
#define getinstruction(fs,e) ((fs)->f->code[(e)->u.info])
|
||||
|
||||
|
||||
#define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET)
|
||||
|
||||
#define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t)
|
||||
|
||||
LUAI_FUNC int luaK_code (FuncState *fs, Instruction i);
|
||||
LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx);
|
||||
LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A,
|
||||
int B, int C, int k);
|
||||
LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v);
|
||||
LUAI_FUNC void luaK_fixline (FuncState *fs, int line);
|
||||
LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n);
|
||||
LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n);
|
||||
LUAI_FUNC void luaK_checkstack (FuncState *fs, int n);
|
||||
LUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n);
|
||||
LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key);
|
||||
LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k);
|
||||
LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC void luaK_goiffalse (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e);
|
||||
LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults);
|
||||
LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e);
|
||||
LUAI_FUNC int luaK_jump (FuncState *fs);
|
||||
LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret);
|
||||
LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target);
|
||||
LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list);
|
||||
LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2);
|
||||
LUAI_FUNC int luaK_getlabel (FuncState *fs);
|
||||
LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line);
|
||||
LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v);
|
||||
LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1,
|
||||
expdesc *v2, int line);
|
||||
LUAI_FUNC void luaK_settablesize (FuncState *fs, int pc,
|
||||
int ra, int asize, int hsize);
|
||||
LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore);
|
||||
LUAI_FUNC void luaK_finish (FuncState *fs);
|
||||
LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg);
|
||||
|
||||
|
||||
#endif
|
||||
210
crates/voltex_script/lua/lcorolib.c
Normal file
210
crates/voltex_script/lua/lcorolib.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
** $Id: lcorolib.c $
|
||||
** Coroutine Library
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lcorolib_c
|
||||
#define LUA_LIB
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
static lua_State *getco (lua_State *L) {
|
||||
lua_State *co = lua_tothread(L, 1);
|
||||
luaL_argexpected(L, co, 1, "thread");
|
||||
return co;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Resumes a coroutine. Returns the number of results for non-error
|
||||
** cases or -1 for errors.
|
||||
*/
|
||||
static int auxresume (lua_State *L, lua_State *co, int narg) {
|
||||
int status, nres;
|
||||
if (l_unlikely(!lua_checkstack(co, narg))) {
|
||||
lua_pushliteral(L, "too many arguments to resume");
|
||||
return -1; /* error flag */
|
||||
}
|
||||
lua_xmove(L, co, narg);
|
||||
status = lua_resume(co, L, narg, &nres);
|
||||
if (l_likely(status == LUA_OK || status == LUA_YIELD)) {
|
||||
if (l_unlikely(!lua_checkstack(L, nres + 1))) {
|
||||
lua_pop(co, nres); /* remove results anyway */
|
||||
lua_pushliteral(L, "too many results to resume");
|
||||
return -1; /* error flag */
|
||||
}
|
||||
lua_xmove(co, L, nres); /* move yielded values */
|
||||
return nres;
|
||||
}
|
||||
else {
|
||||
lua_xmove(co, L, 1); /* move error message */
|
||||
return -1; /* error flag */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int luaB_coresume (lua_State *L) {
|
||||
lua_State *co = getco(L);
|
||||
int r;
|
||||
r = auxresume(L, co, lua_gettop(L) - 1);
|
||||
if (l_unlikely(r < 0)) {
|
||||
lua_pushboolean(L, 0);
|
||||
lua_insert(L, -2);
|
||||
return 2; /* return false + error message */
|
||||
}
|
||||
else {
|
||||
lua_pushboolean(L, 1);
|
||||
lua_insert(L, -(r + 1));
|
||||
return r + 1; /* return true + 'resume' returns */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int luaB_auxwrap (lua_State *L) {
|
||||
lua_State *co = lua_tothread(L, lua_upvalueindex(1));
|
||||
int r = auxresume(L, co, lua_gettop(L));
|
||||
if (l_unlikely(r < 0)) { /* error? */
|
||||
int stat = lua_status(co);
|
||||
if (stat != LUA_OK && stat != LUA_YIELD) { /* error in the coroutine? */
|
||||
stat = lua_closethread(co, L); /* close its tbc variables */
|
||||
lua_assert(stat != LUA_OK);
|
||||
lua_xmove(co, L, 1); /* move error message to the caller */
|
||||
}
|
||||
if (stat != LUA_ERRMEM && /* not a memory error and ... */
|
||||
lua_type(L, -1) == LUA_TSTRING) { /* ... error object is a string? */
|
||||
luaL_where(L, 1); /* add extra info, if available */
|
||||
lua_insert(L, -2);
|
||||
lua_concat(L, 2);
|
||||
}
|
||||
return lua_error(L); /* propagate error */
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_cocreate (lua_State *L) {
|
||||
lua_State *NL;
|
||||
luaL_checktype(L, 1, LUA_TFUNCTION);
|
||||
NL = lua_newthread(L);
|
||||
lua_pushvalue(L, 1); /* move function to top */
|
||||
lua_xmove(L, NL, 1); /* move function from L to NL */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_cowrap (lua_State *L) {
|
||||
luaB_cocreate(L);
|
||||
lua_pushcclosure(L, luaB_auxwrap, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_yield (lua_State *L) {
|
||||
return lua_yield(L, lua_gettop(L));
|
||||
}
|
||||
|
||||
|
||||
#define COS_RUN 0
|
||||
#define COS_DEAD 1
|
||||
#define COS_YIELD 2
|
||||
#define COS_NORM 3
|
||||
|
||||
|
||||
static const char *const statname[] =
|
||||
{"running", "dead", "suspended", "normal"};
|
||||
|
||||
|
||||
static int auxstatus (lua_State *L, lua_State *co) {
|
||||
if (L == co) return COS_RUN;
|
||||
else {
|
||||
switch (lua_status(co)) {
|
||||
case LUA_YIELD:
|
||||
return COS_YIELD;
|
||||
case LUA_OK: {
|
||||
lua_Debug ar;
|
||||
if (lua_getstack(co, 0, &ar)) /* does it have frames? */
|
||||
return COS_NORM; /* it is running */
|
||||
else if (lua_gettop(co) == 0)
|
||||
return COS_DEAD;
|
||||
else
|
||||
return COS_YIELD; /* initial state */
|
||||
}
|
||||
default: /* some error occurred */
|
||||
return COS_DEAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int luaB_costatus (lua_State *L) {
|
||||
lua_State *co = getco(L);
|
||||
lua_pushstring(L, statname[auxstatus(L, co)]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_yieldable (lua_State *L) {
|
||||
lua_State *co = lua_isnone(L, 1) ? L : getco(L);
|
||||
lua_pushboolean(L, lua_isyieldable(co));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_corunning (lua_State *L) {
|
||||
int ismain = lua_pushthread(L);
|
||||
lua_pushboolean(L, ismain);
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_close (lua_State *L) {
|
||||
lua_State *co = getco(L);
|
||||
int status = auxstatus(L, co);
|
||||
switch (status) {
|
||||
case COS_DEAD: case COS_YIELD: {
|
||||
status = lua_closethread(co, L);
|
||||
if (status == LUA_OK) {
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
lua_pushboolean(L, 0);
|
||||
lua_xmove(co, L, 1); /* move error message */
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
default: /* normal or running coroutine */
|
||||
return luaL_error(L, "cannot close a %s coroutine", statname[status]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg co_funcs[] = {
|
||||
{"create", luaB_cocreate},
|
||||
{"resume", luaB_coresume},
|
||||
{"running", luaB_corunning},
|
||||
{"status", luaB_costatus},
|
||||
{"wrap", luaB_cowrap},
|
||||
{"yield", luaB_yield},
|
||||
{"isyieldable", luaB_yieldable},
|
||||
{"close", luaB_close},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
|
||||
LUAMOD_API int luaopen_coroutine (lua_State *L) {
|
||||
luaL_newlib(L, co_funcs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
64
crates/voltex_script/lua/lctype.c
Normal file
64
crates/voltex_script/lua/lctype.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
** $Id: lctype.c $
|
||||
** 'ctype' functions for Lua
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lctype_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include "lctype.h"
|
||||
|
||||
#if !LUA_USE_CTYPE /* { */
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
#if defined (LUA_UCID) /* accept UniCode IDentifiers? */
|
||||
/* consider all non-ascii codepoints to be alphabetic */
|
||||
#define NONA 0x01
|
||||
#else
|
||||
#define NONA 0x00 /* default */
|
||||
#endif
|
||||
|
||||
|
||||
LUAI_DDEF const lu_byte luai_ctype_[UCHAR_MAX + 2] = {
|
||||
0x00, /* EOZ */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0. */
|
||||
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 1. */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, /* 2. */
|
||||
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, /* 3. */
|
||||
0x16, 0x16, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
||||
0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 4. */
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 5. */
|
||||
0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x05,
|
||||
0x04, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x05, /* 6. */
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
||||
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, /* 7. */
|
||||
0x05, 0x05, 0x05, 0x04, 0x04, 0x04, 0x04, 0x00,
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 8. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* 9. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* a. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* b. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
0x00, 0x00, NONA, NONA, NONA, NONA, NONA, NONA, /* c. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* d. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA, /* e. */
|
||||
NONA, NONA, NONA, NONA, NONA, NONA, NONA, NONA,
|
||||
NONA, NONA, NONA, NONA, NONA, 0x00, 0x00, 0x00, /* f. */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
#endif /* } */
|
||||
101
crates/voltex_script/lua/lctype.h
Normal file
101
crates/voltex_script/lua/lctype.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
** $Id: lctype.h $
|
||||
** 'ctype' functions for Lua
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef lctype_h
|
||||
#define lctype_h
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
/*
|
||||
** WARNING: the functions defined here do not necessarily correspond
|
||||
** to the similar functions in the standard C ctype.h. They are
|
||||
** optimized for the specific needs of Lua.
|
||||
*/
|
||||
|
||||
#if !defined(LUA_USE_CTYPE)
|
||||
|
||||
#if 'A' == 65 && '0' == 48
|
||||
/* ASCII case: can use its own tables; faster and fixed */
|
||||
#define LUA_USE_CTYPE 0
|
||||
#else
|
||||
/* must use standard C ctype */
|
||||
#define LUA_USE_CTYPE 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if !LUA_USE_CTYPE /* { */
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "llimits.h"
|
||||
|
||||
|
||||
#define ALPHABIT 0
|
||||
#define DIGITBIT 1
|
||||
#define PRINTBIT 2
|
||||
#define SPACEBIT 3
|
||||
#define XDIGITBIT 4
|
||||
|
||||
|
||||
#define MASK(B) (1 << (B))
|
||||
|
||||
|
||||
/*
|
||||
** add 1 to char to allow index -1 (EOZ)
|
||||
*/
|
||||
#define testprop(c,p) (luai_ctype_[(c)+1] & (p))
|
||||
|
||||
/*
|
||||
** 'lalpha' (Lua alphabetic) and 'lalnum' (Lua alphanumeric) both include '_'
|
||||
*/
|
||||
#define lislalpha(c) testprop(c, MASK(ALPHABIT))
|
||||
#define lislalnum(c) testprop(c, (MASK(ALPHABIT) | MASK(DIGITBIT)))
|
||||
#define lisdigit(c) testprop(c, MASK(DIGITBIT))
|
||||
#define lisspace(c) testprop(c, MASK(SPACEBIT))
|
||||
#define lisprint(c) testprop(c, MASK(PRINTBIT))
|
||||
#define lisxdigit(c) testprop(c, MASK(XDIGITBIT))
|
||||
|
||||
|
||||
/*
|
||||
** In ASCII, this 'ltolower' is correct for alphabetic characters and
|
||||
** for '.'. That is enough for Lua needs. ('check_exp' ensures that
|
||||
** the character either is an upper-case letter or is unchanged by
|
||||
** the transformation, which holds for lower-case letters and '.'.)
|
||||
*/
|
||||
#define ltolower(c) \
|
||||
check_exp(('A' <= (c) && (c) <= 'Z') || (c) == ((c) | ('A' ^ 'a')), \
|
||||
(c) | ('A' ^ 'a'))
|
||||
|
||||
|
||||
/* one entry for each character and for -1 (EOZ) */
|
||||
LUAI_DDEC(const lu_byte luai_ctype_[UCHAR_MAX + 2];)
|
||||
|
||||
|
||||
#else /* }{ */
|
||||
|
||||
/*
|
||||
** use standard C ctypes
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
#define lislalpha(c) (isalpha(c) || (c) == '_')
|
||||
#define lislalnum(c) (isalnum(c) || (c) == '_')
|
||||
#define lisdigit(c) (isdigit(c))
|
||||
#define lisspace(c) (isspace(c))
|
||||
#define lisprint(c) (isprint(c))
|
||||
#define lisxdigit(c) (isxdigit(c))
|
||||
|
||||
#define ltolower(c) (tolower(c))
|
||||
|
||||
#endif /* } */
|
||||
|
||||
#endif
|
||||
|
||||
483
crates/voltex_script/lua/ldblib.c
Normal file
483
crates/voltex_script/lua/ldblib.c
Normal file
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
** $Id: ldblib.c $
|
||||
** Interface from Lua to its debug API
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define ldblib_c
|
||||
#define LUA_LIB
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
/*
|
||||
** The hook table at registry[HOOKKEY] maps threads to their current
|
||||
** hook function.
|
||||
*/
|
||||
static const char *const HOOKKEY = "_HOOKKEY";
|
||||
|
||||
|
||||
/*
|
||||
** If L1 != L, L1 can be in any state, and therefore there are no
|
||||
** guarantees about its stack space; any push in L1 must be
|
||||
** checked.
|
||||
*/
|
||||
static void checkstack (lua_State *L, lua_State *L1, int n) {
|
||||
if (l_unlikely(L != L1 && !lua_checkstack(L1, n)))
|
||||
luaL_error(L, "stack overflow");
|
||||
}
|
||||
|
||||
|
||||
static int db_getregistry (lua_State *L) {
|
||||
lua_pushvalue(L, LUA_REGISTRYINDEX);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int db_getmetatable (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
if (!lua_getmetatable(L, 1)) {
|
||||
lua_pushnil(L); /* no metatable */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int db_setmetatable (lua_State *L) {
|
||||
int t = lua_type(L, 2);
|
||||
luaL_argexpected(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table");
|
||||
lua_settop(L, 2);
|
||||
lua_setmetatable(L, 1);
|
||||
return 1; /* return 1st argument */
|
||||
}
|
||||
|
||||
|
||||
static int db_getuservalue (lua_State *L) {
|
||||
int n = (int)luaL_optinteger(L, 2, 1);
|
||||
if (lua_type(L, 1) != LUA_TUSERDATA)
|
||||
luaL_pushfail(L);
|
||||
else if (lua_getiuservalue(L, 1, n) != LUA_TNONE) {
|
||||
lua_pushboolean(L, 1);
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int db_setuservalue (lua_State *L) {
|
||||
int n = (int)luaL_optinteger(L, 3, 1);
|
||||
luaL_checktype(L, 1, LUA_TUSERDATA);
|
||||
luaL_checkany(L, 2);
|
||||
lua_settop(L, 2);
|
||||
if (!lua_setiuservalue(L, 1, n))
|
||||
luaL_pushfail(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Auxiliary function used by several library functions: check for
|
||||
** an optional thread as function's first argument and set 'arg' with
|
||||
** 1 if this argument is present (so that functions can skip it to
|
||||
** access their other arguments)
|
||||
*/
|
||||
static lua_State *getthread (lua_State *L, int *arg) {
|
||||
if (lua_isthread(L, 1)) {
|
||||
*arg = 1;
|
||||
return lua_tothread(L, 1);
|
||||
}
|
||||
else {
|
||||
*arg = 0;
|
||||
return L; /* function will operate over current thread */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Variations of 'lua_settable', used by 'db_getinfo' to put results
|
||||
** from 'lua_getinfo' into result table. Key is always a string;
|
||||
** value can be a string, an int, or a boolean.
|
||||
*/
|
||||
static void settabss (lua_State *L, const char *k, const char *v) {
|
||||
lua_pushstring(L, v);
|
||||
lua_setfield(L, -2, k);
|
||||
}
|
||||
|
||||
static void settabsi (lua_State *L, const char *k, int v) {
|
||||
lua_pushinteger(L, v);
|
||||
lua_setfield(L, -2, k);
|
||||
}
|
||||
|
||||
static void settabsb (lua_State *L, const char *k, int v) {
|
||||
lua_pushboolean(L, v);
|
||||
lua_setfield(L, -2, k);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** In function 'db_getinfo', the call to 'lua_getinfo' may push
|
||||
** results on the stack; later it creates the result table to put
|
||||
** these objects. Function 'treatstackoption' puts the result from
|
||||
** 'lua_getinfo' on top of the result table so that it can call
|
||||
** 'lua_setfield'.
|
||||
*/
|
||||
static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) {
|
||||
if (L == L1)
|
||||
lua_rotate(L, -2, 1); /* exchange object and table */
|
||||
else
|
||||
lua_xmove(L1, L, 1); /* move object to the "main" stack */
|
||||
lua_setfield(L, -2, fname); /* put object into table */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Calls 'lua_getinfo' and collects all results in a new table.
|
||||
** L1 needs stack space for an optional input (function) plus
|
||||
** two optional outputs (function and line table) from function
|
||||
** 'lua_getinfo'.
|
||||
*/
|
||||
static int db_getinfo (lua_State *L) {
|
||||
lua_Debug ar;
|
||||
int arg;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
const char *options = luaL_optstring(L, arg+2, "flnSrtu");
|
||||
checkstack(L, L1, 3);
|
||||
luaL_argcheck(L, options[0] != '>', arg + 2, "invalid option '>'");
|
||||
if (lua_isfunction(L, arg + 1)) { /* info about a function? */
|
||||
options = lua_pushfstring(L, ">%s", options); /* add '>' to 'options' */
|
||||
lua_pushvalue(L, arg + 1); /* move function to 'L1' stack */
|
||||
lua_xmove(L, L1, 1);
|
||||
}
|
||||
else { /* stack level */
|
||||
if (!lua_getstack(L1, (int)luaL_checkinteger(L, arg + 1), &ar)) {
|
||||
luaL_pushfail(L); /* level out of range */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!lua_getinfo(L1, options, &ar))
|
||||
return luaL_argerror(L, arg+2, "invalid option");
|
||||
lua_newtable(L); /* table to collect results */
|
||||
if (strchr(options, 'S')) {
|
||||
lua_pushlstring(L, ar.source, ar.srclen);
|
||||
lua_setfield(L, -2, "source");
|
||||
settabss(L, "short_src", ar.short_src);
|
||||
settabsi(L, "linedefined", ar.linedefined);
|
||||
settabsi(L, "lastlinedefined", ar.lastlinedefined);
|
||||
settabss(L, "what", ar.what);
|
||||
}
|
||||
if (strchr(options, 'l'))
|
||||
settabsi(L, "currentline", ar.currentline);
|
||||
if (strchr(options, 'u')) {
|
||||
settabsi(L, "nups", ar.nups);
|
||||
settabsi(L, "nparams", ar.nparams);
|
||||
settabsb(L, "isvararg", ar.isvararg);
|
||||
}
|
||||
if (strchr(options, 'n')) {
|
||||
settabss(L, "name", ar.name);
|
||||
settabss(L, "namewhat", ar.namewhat);
|
||||
}
|
||||
if (strchr(options, 'r')) {
|
||||
settabsi(L, "ftransfer", ar.ftransfer);
|
||||
settabsi(L, "ntransfer", ar.ntransfer);
|
||||
}
|
||||
if (strchr(options, 't'))
|
||||
settabsb(L, "istailcall", ar.istailcall);
|
||||
if (strchr(options, 'L'))
|
||||
treatstackoption(L, L1, "activelines");
|
||||
if (strchr(options, 'f'))
|
||||
treatstackoption(L, L1, "func");
|
||||
return 1; /* return table */
|
||||
}
|
||||
|
||||
|
||||
static int db_getlocal (lua_State *L) {
|
||||
int arg;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
int nvar = (int)luaL_checkinteger(L, arg + 2); /* local-variable index */
|
||||
if (lua_isfunction(L, arg + 1)) { /* function argument? */
|
||||
lua_pushvalue(L, arg + 1); /* push function */
|
||||
lua_pushstring(L, lua_getlocal(L, NULL, nvar)); /* push local name */
|
||||
return 1; /* return only name (there is no value) */
|
||||
}
|
||||
else { /* stack-level argument */
|
||||
lua_Debug ar;
|
||||
const char *name;
|
||||
int level = (int)luaL_checkinteger(L, arg + 1);
|
||||
if (l_unlikely(!lua_getstack(L1, level, &ar))) /* out of range? */
|
||||
return luaL_argerror(L, arg+1, "level out of range");
|
||||
checkstack(L, L1, 1);
|
||||
name = lua_getlocal(L1, &ar, nvar);
|
||||
if (name) {
|
||||
lua_xmove(L1, L, 1); /* move local value */
|
||||
lua_pushstring(L, name); /* push name */
|
||||
lua_rotate(L, -2, 1); /* re-order */
|
||||
return 2;
|
||||
}
|
||||
else {
|
||||
luaL_pushfail(L); /* no name (nor value) */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int db_setlocal (lua_State *L) {
|
||||
int arg;
|
||||
const char *name;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
lua_Debug ar;
|
||||
int level = (int)luaL_checkinteger(L, arg + 1);
|
||||
int nvar = (int)luaL_checkinteger(L, arg + 2);
|
||||
if (l_unlikely(!lua_getstack(L1, level, &ar))) /* out of range? */
|
||||
return luaL_argerror(L, arg+1, "level out of range");
|
||||
luaL_checkany(L, arg+3);
|
||||
lua_settop(L, arg+3);
|
||||
checkstack(L, L1, 1);
|
||||
lua_xmove(L, L1, 1);
|
||||
name = lua_setlocal(L1, &ar, nvar);
|
||||
if (name == NULL)
|
||||
lua_pop(L1, 1); /* pop value (if not popped by 'lua_setlocal') */
|
||||
lua_pushstring(L, name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** get (if 'get' is true) or set an upvalue from a closure
|
||||
*/
|
||||
static int auxupvalue (lua_State *L, int get) {
|
||||
const char *name;
|
||||
int n = (int)luaL_checkinteger(L, 2); /* upvalue index */
|
||||
luaL_checktype(L, 1, LUA_TFUNCTION); /* closure */
|
||||
name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n);
|
||||
if (name == NULL) return 0;
|
||||
lua_pushstring(L, name);
|
||||
lua_insert(L, -(get+1)); /* no-op if get is false */
|
||||
return get + 1;
|
||||
}
|
||||
|
||||
|
||||
static int db_getupvalue (lua_State *L) {
|
||||
return auxupvalue(L, 1);
|
||||
}
|
||||
|
||||
|
||||
static int db_setupvalue (lua_State *L) {
|
||||
luaL_checkany(L, 3);
|
||||
return auxupvalue(L, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether a given upvalue from a given closure exists and
|
||||
** returns its index
|
||||
*/
|
||||
static void *checkupval (lua_State *L, int argf, int argnup, int *pnup) {
|
||||
void *id;
|
||||
int nup = (int)luaL_checkinteger(L, argnup); /* upvalue index */
|
||||
luaL_checktype(L, argf, LUA_TFUNCTION); /* closure */
|
||||
id = lua_upvalueid(L, argf, nup);
|
||||
if (pnup) {
|
||||
luaL_argcheck(L, id != NULL, argnup, "invalid upvalue index");
|
||||
*pnup = nup;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
static int db_upvalueid (lua_State *L) {
|
||||
void *id = checkupval(L, 1, 2, NULL);
|
||||
if (id != NULL)
|
||||
lua_pushlightuserdata(L, id);
|
||||
else
|
||||
luaL_pushfail(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int db_upvaluejoin (lua_State *L) {
|
||||
int n1, n2;
|
||||
checkupval(L, 1, 2, &n1);
|
||||
checkupval(L, 3, 4, &n2);
|
||||
luaL_argcheck(L, !lua_iscfunction(L, 1), 1, "Lua function expected");
|
||||
luaL_argcheck(L, !lua_iscfunction(L, 3), 3, "Lua function expected");
|
||||
lua_upvaluejoin(L, 1, n1, 3, n2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Call hook function registered at hook table for the current
|
||||
** thread (if there is one)
|
||||
*/
|
||||
static void hookf (lua_State *L, lua_Debug *ar) {
|
||||
static const char *const hooknames[] =
|
||||
{"call", "return", "line", "count", "tail call"};
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY);
|
||||
lua_pushthread(L);
|
||||
if (lua_rawget(L, -2) == LUA_TFUNCTION) { /* is there a hook function? */
|
||||
lua_pushstring(L, hooknames[(int)ar->event]); /* push event name */
|
||||
if (ar->currentline >= 0)
|
||||
lua_pushinteger(L, ar->currentline); /* push current line */
|
||||
else lua_pushnil(L);
|
||||
lua_assert(lua_getinfo(L, "lS", ar));
|
||||
lua_call(L, 2, 0); /* call hook function */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Convert a string mask (for 'sethook') into a bit mask
|
||||
*/
|
||||
static int makemask (const char *smask, int count) {
|
||||
int mask = 0;
|
||||
if (strchr(smask, 'c')) mask |= LUA_MASKCALL;
|
||||
if (strchr(smask, 'r')) mask |= LUA_MASKRET;
|
||||
if (strchr(smask, 'l')) mask |= LUA_MASKLINE;
|
||||
if (count > 0) mask |= LUA_MASKCOUNT;
|
||||
return mask;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Convert a bit mask (for 'gethook') into a string mask
|
||||
*/
|
||||
static char *unmakemask (int mask, char *smask) {
|
||||
int i = 0;
|
||||
if (mask & LUA_MASKCALL) smask[i++] = 'c';
|
||||
if (mask & LUA_MASKRET) smask[i++] = 'r';
|
||||
if (mask & LUA_MASKLINE) smask[i++] = 'l';
|
||||
smask[i] = '\0';
|
||||
return smask;
|
||||
}
|
||||
|
||||
|
||||
static int db_sethook (lua_State *L) {
|
||||
int arg, mask, count;
|
||||
lua_Hook func;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
if (lua_isnoneornil(L, arg+1)) { /* no hook? */
|
||||
lua_settop(L, arg+1);
|
||||
func = NULL; mask = 0; count = 0; /* turn off hooks */
|
||||
}
|
||||
else {
|
||||
const char *smask = luaL_checkstring(L, arg+2);
|
||||
luaL_checktype(L, arg+1, LUA_TFUNCTION);
|
||||
count = (int)luaL_optinteger(L, arg + 3, 0);
|
||||
func = hookf; mask = makemask(smask, count);
|
||||
}
|
||||
if (!luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY)) {
|
||||
/* table just created; initialize it */
|
||||
lua_pushliteral(L, "k");
|
||||
lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setmetatable(L, -2); /* metatable(hooktable) = hooktable */
|
||||
}
|
||||
checkstack(L, L1, 1);
|
||||
lua_pushthread(L1); lua_xmove(L1, L, 1); /* key (thread) */
|
||||
lua_pushvalue(L, arg + 1); /* value (hook function) */
|
||||
lua_rawset(L, -3); /* hooktable[L1] = new Lua hook */
|
||||
lua_sethook(L1, func, mask, count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int db_gethook (lua_State *L) {
|
||||
int arg;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
char buff[5];
|
||||
int mask = lua_gethookmask(L1);
|
||||
lua_Hook hook = lua_gethook(L1);
|
||||
if (hook == NULL) { /* no hook? */
|
||||
luaL_pushfail(L);
|
||||
return 1;
|
||||
}
|
||||
else if (hook != hookf) /* external hook? */
|
||||
lua_pushliteral(L, "external hook");
|
||||
else { /* hook table must exist */
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, HOOKKEY);
|
||||
checkstack(L, L1, 1);
|
||||
lua_pushthread(L1); lua_xmove(L1, L, 1);
|
||||
lua_rawget(L, -2); /* 1st result = hooktable[L1] */
|
||||
lua_remove(L, -2); /* remove hook table */
|
||||
}
|
||||
lua_pushstring(L, unmakemask(mask, buff)); /* 2nd result = mask */
|
||||
lua_pushinteger(L, lua_gethookcount(L1)); /* 3rd result = count */
|
||||
return 3;
|
||||
}
|
||||
|
||||
|
||||
static int db_debug (lua_State *L) {
|
||||
for (;;) {
|
||||
char buffer[250];
|
||||
lua_writestringerror("%s", "lua_debug> ");
|
||||
if (fgets(buffer, sizeof(buffer), stdin) == NULL ||
|
||||
strcmp(buffer, "cont\n") == 0)
|
||||
return 0;
|
||||
if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") ||
|
||||
lua_pcall(L, 0, 0, 0))
|
||||
lua_writestringerror("%s\n", luaL_tolstring(L, -1, NULL));
|
||||
lua_settop(L, 0); /* remove eventual returns */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int db_traceback (lua_State *L) {
|
||||
int arg;
|
||||
lua_State *L1 = getthread(L, &arg);
|
||||
const char *msg = lua_tostring(L, arg + 1);
|
||||
if (msg == NULL && !lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */
|
||||
lua_pushvalue(L, arg + 1); /* return it untouched */
|
||||
else {
|
||||
int level = (int)luaL_optinteger(L, arg + 2, (L == L1) ? 1 : 0);
|
||||
luaL_traceback(L, L1, msg, level);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int db_setcstacklimit (lua_State *L) {
|
||||
int limit = (int)luaL_checkinteger(L, 1);
|
||||
int res = lua_setcstacklimit(L, limit);
|
||||
lua_pushinteger(L, res);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg dblib[] = {
|
||||
{"debug", db_debug},
|
||||
{"getuservalue", db_getuservalue},
|
||||
{"gethook", db_gethook},
|
||||
{"getinfo", db_getinfo},
|
||||
{"getlocal", db_getlocal},
|
||||
{"getregistry", db_getregistry},
|
||||
{"getmetatable", db_getmetatable},
|
||||
{"getupvalue", db_getupvalue},
|
||||
{"upvaluejoin", db_upvaluejoin},
|
||||
{"upvalueid", db_upvalueid},
|
||||
{"setuservalue", db_setuservalue},
|
||||
{"sethook", db_sethook},
|
||||
{"setlocal", db_setlocal},
|
||||
{"setmetatable", db_setmetatable},
|
||||
{"setupvalue", db_setupvalue},
|
||||
{"traceback", db_traceback},
|
||||
{"setcstacklimit", db_setcstacklimit},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
LUAMOD_API int luaopen_debug (lua_State *L) {
|
||||
luaL_newlib(L, dblib);
|
||||
return 1;
|
||||
}
|
||||
|
||||
962
crates/voltex_script/lua/ldebug.c
Normal file
962
crates/voltex_script/lua/ldebug.c
Normal file
@@ -0,0 +1,962 @@
|
||||
/*
|
||||
** $Id: ldebug.c $
|
||||
** Debug Interface
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define ldebug_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lapi.h"
|
||||
#include "lcode.h"
|
||||
#include "ldebug.h"
|
||||
#include "ldo.h"
|
||||
#include "lfunc.h"
|
||||
#include "lobject.h"
|
||||
#include "lopcodes.h"
|
||||
#include "lstate.h"
|
||||
#include "lstring.h"
|
||||
#include "ltable.h"
|
||||
#include "ltm.h"
|
||||
#include "lvm.h"
|
||||
|
||||
|
||||
|
||||
#define LuaClosure(f) ((f) != NULL && (f)->c.tt == LUA_VLCL)
|
||||
|
||||
|
||||
static const char *funcnamefromcall (lua_State *L, CallInfo *ci,
|
||||
const char **name);
|
||||
|
||||
|
||||
static int currentpc (CallInfo *ci) {
|
||||
lua_assert(isLua(ci));
|
||||
return pcRel(ci->u.l.savedpc, ci_func(ci)->p);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Get a "base line" to find the line corresponding to an instruction.
|
||||
** Base lines are regularly placed at MAXIWTHABS intervals, so usually
|
||||
** an integer division gets the right place. When the source file has
|
||||
** large sequences of empty/comment lines, it may need extra entries,
|
||||
** so the original estimate needs a correction.
|
||||
** If the original estimate is -1, the initial 'if' ensures that the
|
||||
** 'while' will run at least once.
|
||||
** The assertion that the estimate is a lower bound for the correct base
|
||||
** is valid as long as the debug info has been generated with the same
|
||||
** value for MAXIWTHABS or smaller. (Previous releases use a little
|
||||
** smaller value.)
|
||||
*/
|
||||
static int getbaseline (const Proto *f, int pc, int *basepc) {
|
||||
if (f->sizeabslineinfo == 0 || pc < f->abslineinfo[0].pc) {
|
||||
*basepc = -1; /* start from the beginning */
|
||||
return f->linedefined;
|
||||
}
|
||||
else {
|
||||
int i = cast_uint(pc) / MAXIWTHABS - 1; /* get an estimate */
|
||||
/* estimate must be a lower bound of the correct base */
|
||||
lua_assert(i < 0 ||
|
||||
(i < f->sizeabslineinfo && f->abslineinfo[i].pc <= pc));
|
||||
while (i + 1 < f->sizeabslineinfo && pc >= f->abslineinfo[i + 1].pc)
|
||||
i++; /* low estimate; adjust it */
|
||||
*basepc = f->abslineinfo[i].pc;
|
||||
return f->abslineinfo[i].line;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Get the line corresponding to instruction 'pc' in function 'f';
|
||||
** first gets a base line and from there does the increments until
|
||||
** the desired instruction.
|
||||
*/
|
||||
int luaG_getfuncline (const Proto *f, int pc) {
|
||||
if (f->lineinfo == NULL) /* no debug information? */
|
||||
return -1;
|
||||
else {
|
||||
int basepc;
|
||||
int baseline = getbaseline(f, pc, &basepc);
|
||||
while (basepc++ < pc) { /* walk until given instruction */
|
||||
lua_assert(f->lineinfo[basepc] != ABSLINEINFO);
|
||||
baseline += f->lineinfo[basepc]; /* correct line */
|
||||
}
|
||||
return baseline;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int getcurrentline (CallInfo *ci) {
|
||||
return luaG_getfuncline(ci_func(ci)->p, currentpc(ci));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Set 'trap' for all active Lua frames.
|
||||
** This function can be called during a signal, under "reasonable"
|
||||
** assumptions. A new 'ci' is completely linked in the list before it
|
||||
** becomes part of the "active" list, and we assume that pointers are
|
||||
** atomic; see comment in next function.
|
||||
** (A compiler doing interprocedural optimizations could, theoretically,
|
||||
** reorder memory writes in such a way that the list could be
|
||||
** temporarily broken while inserting a new element. We simply assume it
|
||||
** has no good reasons to do that.)
|
||||
*/
|
||||
static void settraps (CallInfo *ci) {
|
||||
for (; ci != NULL; ci = ci->previous)
|
||||
if (isLua(ci))
|
||||
ci->u.l.trap = 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This function can be called during a signal, under "reasonable"
|
||||
** assumptions.
|
||||
** Fields 'basehookcount' and 'hookcount' (set by 'resethookcount')
|
||||
** are for debug only, and it is no problem if they get arbitrary
|
||||
** values (causes at most one wrong hook call). 'hookmask' is an atomic
|
||||
** value. We assume that pointers are atomic too (e.g., gcc ensures that
|
||||
** for all platforms where it runs). Moreover, 'hook' is always checked
|
||||
** before being called (see 'luaD_hook').
|
||||
*/
|
||||
LUA_API void lua_sethook (lua_State *L, lua_Hook func, int mask, int count) {
|
||||
if (func == NULL || mask == 0) { /* turn off hooks? */
|
||||
mask = 0;
|
||||
func = NULL;
|
||||
}
|
||||
L->hook = func;
|
||||
L->basehookcount = count;
|
||||
resethookcount(L);
|
||||
L->hookmask = cast_byte(mask);
|
||||
if (mask)
|
||||
settraps(L->ci); /* to trace inside 'luaV_execute' */
|
||||
}
|
||||
|
||||
|
||||
LUA_API lua_Hook lua_gethook (lua_State *L) {
|
||||
return L->hook;
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_gethookmask (lua_State *L) {
|
||||
return L->hookmask;
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_gethookcount (lua_State *L) {
|
||||
return L->basehookcount;
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {
|
||||
int status;
|
||||
CallInfo *ci;
|
||||
if (level < 0) return 0; /* invalid (negative) level */
|
||||
lua_lock(L);
|
||||
for (ci = L->ci; level > 0 && ci != &L->base_ci; ci = ci->previous)
|
||||
level--;
|
||||
if (level == 0 && ci != &L->base_ci) { /* level found? */
|
||||
status = 1;
|
||||
ar->i_ci = ci;
|
||||
}
|
||||
else status = 0; /* no such level */
|
||||
lua_unlock(L);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static const char *upvalname (const Proto *p, int uv) {
|
||||
TString *s = check_exp(uv < p->sizeupvalues, p->upvalues[uv].name);
|
||||
if (s == NULL) return "?";
|
||||
else return getstr(s);
|
||||
}
|
||||
|
||||
|
||||
static const char *findvararg (CallInfo *ci, int n, StkId *pos) {
|
||||
if (clLvalue(s2v(ci->func.p))->p->is_vararg) {
|
||||
int nextra = ci->u.l.nextraargs;
|
||||
if (n >= -nextra) { /* 'n' is negative */
|
||||
*pos = ci->func.p - nextra - (n + 1);
|
||||
return "(vararg)"; /* generic name for any vararg */
|
||||
}
|
||||
}
|
||||
return NULL; /* no such vararg */
|
||||
}
|
||||
|
||||
|
||||
const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n, StkId *pos) {
|
||||
StkId base = ci->func.p + 1;
|
||||
const char *name = NULL;
|
||||
if (isLua(ci)) {
|
||||
if (n < 0) /* access to vararg values? */
|
||||
return findvararg(ci, n, pos);
|
||||
else
|
||||
name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci));
|
||||
}
|
||||
if (name == NULL) { /* no 'standard' name? */
|
||||
StkId limit = (ci == L->ci) ? L->top.p : ci->next->func.p;
|
||||
if (limit - base >= n && n > 0) { /* is 'n' inside 'ci' stack? */
|
||||
/* generic name for any valid slot */
|
||||
name = isLua(ci) ? "(temporary)" : "(C temporary)";
|
||||
}
|
||||
else
|
||||
return NULL; /* no name */
|
||||
}
|
||||
if (pos)
|
||||
*pos = base + (n - 1);
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) {
|
||||
const char *name;
|
||||
lua_lock(L);
|
||||
if (ar == NULL) { /* information about non-active function? */
|
||||
if (!isLfunction(s2v(L->top.p - 1))) /* not a Lua function? */
|
||||
name = NULL;
|
||||
else /* consider live variables at function start (parameters) */
|
||||
name = luaF_getlocalname(clLvalue(s2v(L->top.p - 1))->p, n, 0);
|
||||
}
|
||||
else { /* active function; get information through 'ar' */
|
||||
StkId pos = NULL; /* to avoid warnings */
|
||||
name = luaG_findlocal(L, ar->i_ci, n, &pos);
|
||||
if (name) {
|
||||
setobjs2s(L, L->top.p, pos);
|
||||
api_incr_top(L);
|
||||
}
|
||||
}
|
||||
lua_unlock(L);
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) {
|
||||
StkId pos = NULL; /* to avoid warnings */
|
||||
const char *name;
|
||||
lua_lock(L);
|
||||
name = luaG_findlocal(L, ar->i_ci, n, &pos);
|
||||
if (name) {
|
||||
setobjs2s(L, pos, L->top.p - 1);
|
||||
L->top.p--; /* pop value */
|
||||
}
|
||||
lua_unlock(L);
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
static void funcinfo (lua_Debug *ar, Closure *cl) {
|
||||
if (!LuaClosure(cl)) {
|
||||
ar->source = "=[C]";
|
||||
ar->srclen = LL("=[C]");
|
||||
ar->linedefined = -1;
|
||||
ar->lastlinedefined = -1;
|
||||
ar->what = "C";
|
||||
}
|
||||
else {
|
||||
const Proto *p = cl->l.p;
|
||||
if (p->source) {
|
||||
ar->source = getstr(p->source);
|
||||
ar->srclen = tsslen(p->source);
|
||||
}
|
||||
else {
|
||||
ar->source = "=?";
|
||||
ar->srclen = LL("=?");
|
||||
}
|
||||
ar->linedefined = p->linedefined;
|
||||
ar->lastlinedefined = p->lastlinedefined;
|
||||
ar->what = (ar->linedefined == 0) ? "main" : "Lua";
|
||||
}
|
||||
luaO_chunkid(ar->short_src, ar->source, ar->srclen);
|
||||
}
|
||||
|
||||
|
||||
static int nextline (const Proto *p, int currentline, int pc) {
|
||||
if (p->lineinfo[pc] != ABSLINEINFO)
|
||||
return currentline + p->lineinfo[pc];
|
||||
else
|
||||
return luaG_getfuncline(p, pc);
|
||||
}
|
||||
|
||||
|
||||
static void collectvalidlines (lua_State *L, Closure *f) {
|
||||
if (!LuaClosure(f)) {
|
||||
setnilvalue(s2v(L->top.p));
|
||||
api_incr_top(L);
|
||||
}
|
||||
else {
|
||||
const Proto *p = f->l.p;
|
||||
int currentline = p->linedefined;
|
||||
Table *t = luaH_new(L); /* new table to store active lines */
|
||||
sethvalue2s(L, L->top.p, t); /* push it on stack */
|
||||
api_incr_top(L);
|
||||
if (p->lineinfo != NULL) { /* proto with debug information? */
|
||||
int i;
|
||||
TValue v;
|
||||
setbtvalue(&v); /* boolean 'true' to be the value of all indices */
|
||||
if (!p->is_vararg) /* regular function? */
|
||||
i = 0; /* consider all instructions */
|
||||
else { /* vararg function */
|
||||
lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP);
|
||||
currentline = nextline(p, currentline, 0);
|
||||
i = 1; /* skip first instruction (OP_VARARGPREP) */
|
||||
}
|
||||
for (; i < p->sizelineinfo; i++) { /* for each instruction */
|
||||
currentline = nextline(p, currentline, i); /* get its line */
|
||||
luaH_setint(L, t, currentline, &v); /* table[line] = true */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) {
|
||||
/* calling function is a known function? */
|
||||
if (ci != NULL && !(ci->callstatus & CIST_TAIL))
|
||||
return funcnamefromcall(L, ci->previous, name);
|
||||
else return NULL; /* no way to find a name */
|
||||
}
|
||||
|
||||
|
||||
static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar,
|
||||
Closure *f, CallInfo *ci) {
|
||||
int status = 1;
|
||||
for (; *what; what++) {
|
||||
switch (*what) {
|
||||
case 'S': {
|
||||
funcinfo(ar, f);
|
||||
break;
|
||||
}
|
||||
case 'l': {
|
||||
ar->currentline = (ci && isLua(ci)) ? getcurrentline(ci) : -1;
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
ar->nups = (f == NULL) ? 0 : f->c.nupvalues;
|
||||
if (!LuaClosure(f)) {
|
||||
ar->isvararg = 1;
|
||||
ar->nparams = 0;
|
||||
}
|
||||
else {
|
||||
ar->isvararg = f->l.p->is_vararg;
|
||||
ar->nparams = f->l.p->numparams;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
ar->istailcall = (ci) ? ci->callstatus & CIST_TAIL : 0;
|
||||
break;
|
||||
}
|
||||
case 'n': {
|
||||
ar->namewhat = getfuncname(L, ci, &ar->name);
|
||||
if (ar->namewhat == NULL) {
|
||||
ar->namewhat = ""; /* not found */
|
||||
ar->name = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'r': {
|
||||
if (ci == NULL || !(ci->callstatus & CIST_TRAN))
|
||||
ar->ftransfer = ar->ntransfer = 0;
|
||||
else {
|
||||
ar->ftransfer = ci->u2.transferinfo.ftransfer;
|
||||
ar->ntransfer = ci->u2.transferinfo.ntransfer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'L':
|
||||
case 'f': /* handled by lua_getinfo */
|
||||
break;
|
||||
default: status = 0; /* invalid option */
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) {
|
||||
int status;
|
||||
Closure *cl;
|
||||
CallInfo *ci;
|
||||
TValue *func;
|
||||
lua_lock(L);
|
||||
if (*what == '>') {
|
||||
ci = NULL;
|
||||
func = s2v(L->top.p - 1);
|
||||
api_check(L, ttisfunction(func), "function expected");
|
||||
what++; /* skip the '>' */
|
||||
L->top.p--; /* pop function */
|
||||
}
|
||||
else {
|
||||
ci = ar->i_ci;
|
||||
func = s2v(ci->func.p);
|
||||
lua_assert(ttisfunction(func));
|
||||
}
|
||||
cl = ttisclosure(func) ? clvalue(func) : NULL;
|
||||
status = auxgetinfo(L, what, ar, cl, ci);
|
||||
if (strchr(what, 'f')) {
|
||||
setobj2s(L, L->top.p, func);
|
||||
api_incr_top(L);
|
||||
}
|
||||
if (strchr(what, 'L'))
|
||||
collectvalidlines(L, cl);
|
||||
lua_unlock(L);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Symbolic Execution
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
static int filterpc (int pc, int jmptarget) {
|
||||
if (pc < jmptarget) /* is code conditional (inside a jump)? */
|
||||
return -1; /* cannot know who sets that register */
|
||||
else return pc; /* current position sets that register */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Try to find last instruction before 'lastpc' that modified register 'reg'.
|
||||
*/
|
||||
static int findsetreg (const Proto *p, int lastpc, int reg) {
|
||||
int pc;
|
||||
int setreg = -1; /* keep last instruction that changed 'reg' */
|
||||
int jmptarget = 0; /* any code before this address is conditional */
|
||||
if (testMMMode(GET_OPCODE(p->code[lastpc])))
|
||||
lastpc--; /* previous instruction was not actually executed */
|
||||
for (pc = 0; pc < lastpc; pc++) {
|
||||
Instruction i = p->code[pc];
|
||||
OpCode op = GET_OPCODE(i);
|
||||
int a = GETARG_A(i);
|
||||
int change; /* true if current instruction changed 'reg' */
|
||||
switch (op) {
|
||||
case OP_LOADNIL: { /* set registers from 'a' to 'a+b' */
|
||||
int b = GETARG_B(i);
|
||||
change = (a <= reg && reg <= a + b);
|
||||
break;
|
||||
}
|
||||
case OP_TFORCALL: { /* affect all regs above its base */
|
||||
change = (reg >= a + 2);
|
||||
break;
|
||||
}
|
||||
case OP_CALL:
|
||||
case OP_TAILCALL: { /* affect all registers above base */
|
||||
change = (reg >= a);
|
||||
break;
|
||||
}
|
||||
case OP_JMP: { /* doesn't change registers, but changes 'jmptarget' */
|
||||
int b = GETARG_sJ(i);
|
||||
int dest = pc + 1 + b;
|
||||
/* jump does not skip 'lastpc' and is larger than current one? */
|
||||
if (dest <= lastpc && dest > jmptarget)
|
||||
jmptarget = dest; /* update 'jmptarget' */
|
||||
change = 0;
|
||||
break;
|
||||
}
|
||||
default: /* any instruction that sets A */
|
||||
change = (testAMode(op) && reg == a);
|
||||
break;
|
||||
}
|
||||
if (change)
|
||||
setreg = filterpc(pc, jmptarget);
|
||||
}
|
||||
return setreg;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Find a "name" for the constant 'c'.
|
||||
*/
|
||||
static const char *kname (const Proto *p, int index, const char **name) {
|
||||
TValue *kvalue = &p->k[index];
|
||||
if (ttisstring(kvalue)) {
|
||||
*name = getstr(tsvalue(kvalue));
|
||||
return "constant";
|
||||
}
|
||||
else {
|
||||
*name = "?";
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const char *basicgetobjname (const Proto *p, int *ppc, int reg,
|
||||
const char **name) {
|
||||
int pc = *ppc;
|
||||
*name = luaF_getlocalname(p, reg + 1, pc);
|
||||
if (*name) /* is a local? */
|
||||
return "local";
|
||||
/* else try symbolic execution */
|
||||
*ppc = pc = findsetreg(p, pc, reg);
|
||||
if (pc != -1) { /* could find instruction? */
|
||||
Instruction i = p->code[pc];
|
||||
OpCode op = GET_OPCODE(i);
|
||||
switch (op) {
|
||||
case OP_MOVE: {
|
||||
int b = GETARG_B(i); /* move from 'b' to 'a' */
|
||||
if (b < GETARG_A(i))
|
||||
return basicgetobjname(p, ppc, b, name); /* get name for 'b' */
|
||||
break;
|
||||
}
|
||||
case OP_GETUPVAL: {
|
||||
*name = upvalname(p, GETARG_B(i));
|
||||
return "upvalue";
|
||||
}
|
||||
case OP_LOADK: return kname(p, GETARG_Bx(i), name);
|
||||
case OP_LOADKX: return kname(p, GETARG_Ax(p->code[pc + 1]), name);
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return NULL; /* could not find reasonable name */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Find a "name" for the register 'c'.
|
||||
*/
|
||||
static void rname (const Proto *p, int pc, int c, const char **name) {
|
||||
const char *what = basicgetobjname(p, &pc, c, name); /* search for 'c' */
|
||||
if (!(what && *what == 'c')) /* did not find a constant name? */
|
||||
*name = "?";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Find a "name" for a 'C' value in an RK instruction.
|
||||
*/
|
||||
static void rkname (const Proto *p, int pc, Instruction i, const char **name) {
|
||||
int c = GETARG_C(i); /* key index */
|
||||
if (GETARG_k(i)) /* is 'c' a constant? */
|
||||
kname(p, c, name);
|
||||
else /* 'c' is a register */
|
||||
rname(p, pc, c, name);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether table being indexed by instruction 'i' is the
|
||||
** environment '_ENV'
|
||||
*/
|
||||
static const char *isEnv (const Proto *p, int pc, Instruction i, int isup) {
|
||||
int t = GETARG_B(i); /* table index */
|
||||
const char *name; /* name of indexed variable */
|
||||
if (isup) /* is 't' an upvalue? */
|
||||
name = upvalname(p, t);
|
||||
else /* 't' is a register */
|
||||
basicgetobjname(p, &pc, t, &name);
|
||||
return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Extend 'basicgetobjname' to handle table accesses
|
||||
*/
|
||||
static const char *getobjname (const Proto *p, int lastpc, int reg,
|
||||
const char **name) {
|
||||
const char *kind = basicgetobjname(p, &lastpc, reg, name);
|
||||
if (kind != NULL)
|
||||
return kind;
|
||||
else if (lastpc != -1) { /* could find instruction? */
|
||||
Instruction i = p->code[lastpc];
|
||||
OpCode op = GET_OPCODE(i);
|
||||
switch (op) {
|
||||
case OP_GETTABUP: {
|
||||
int k = GETARG_C(i); /* key index */
|
||||
kname(p, k, name);
|
||||
return isEnv(p, lastpc, i, 1);
|
||||
}
|
||||
case OP_GETTABLE: {
|
||||
int k = GETARG_C(i); /* key index */
|
||||
rname(p, lastpc, k, name);
|
||||
return isEnv(p, lastpc, i, 0);
|
||||
}
|
||||
case OP_GETI: {
|
||||
*name = "integer index";
|
||||
return "field";
|
||||
}
|
||||
case OP_GETFIELD: {
|
||||
int k = GETARG_C(i); /* key index */
|
||||
kname(p, k, name);
|
||||
return isEnv(p, lastpc, i, 0);
|
||||
}
|
||||
case OP_SELF: {
|
||||
rkname(p, lastpc, i, name);
|
||||
return "method";
|
||||
}
|
||||
default: break; /* go through to return NULL */
|
||||
}
|
||||
}
|
||||
return NULL; /* could not find reasonable name */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Try to find a name for a function based on the code that called it.
|
||||
** (Only works when function was called by a Lua function.)
|
||||
** Returns what the name is (e.g., "for iterator", "method",
|
||||
** "metamethod") and sets '*name' to point to the name.
|
||||
*/
|
||||
static const char *funcnamefromcode (lua_State *L, const Proto *p,
|
||||
int pc, const char **name) {
|
||||
TMS tm = (TMS)0; /* (initial value avoids warnings) */
|
||||
Instruction i = p->code[pc]; /* calling instruction */
|
||||
switch (GET_OPCODE(i)) {
|
||||
case OP_CALL:
|
||||
case OP_TAILCALL:
|
||||
return getobjname(p, pc, GETARG_A(i), name); /* get function name */
|
||||
case OP_TFORCALL: { /* for iterator */
|
||||
*name = "for iterator";
|
||||
return "for iterator";
|
||||
}
|
||||
/* other instructions can do calls through metamethods */
|
||||
case OP_SELF: case OP_GETTABUP: case OP_GETTABLE:
|
||||
case OP_GETI: case OP_GETFIELD:
|
||||
tm = TM_INDEX;
|
||||
break;
|
||||
case OP_SETTABUP: case OP_SETTABLE: case OP_SETI: case OP_SETFIELD:
|
||||
tm = TM_NEWINDEX;
|
||||
break;
|
||||
case OP_MMBIN: case OP_MMBINI: case OP_MMBINK: {
|
||||
tm = cast(TMS, GETARG_C(i));
|
||||
break;
|
||||
}
|
||||
case OP_UNM: tm = TM_UNM; break;
|
||||
case OP_BNOT: tm = TM_BNOT; break;
|
||||
case OP_LEN: tm = TM_LEN; break;
|
||||
case OP_CONCAT: tm = TM_CONCAT; break;
|
||||
case OP_EQ: tm = TM_EQ; break;
|
||||
/* no cases for OP_EQI and OP_EQK, as they don't call metamethods */
|
||||
case OP_LT: case OP_LTI: case OP_GTI: tm = TM_LT; break;
|
||||
case OP_LE: case OP_LEI: case OP_GEI: tm = TM_LE; break;
|
||||
case OP_CLOSE: case OP_RETURN: tm = TM_CLOSE; break;
|
||||
default:
|
||||
return NULL; /* cannot find a reasonable name */
|
||||
}
|
||||
*name = getshrstr(G(L)->tmname[tm]) + 2;
|
||||
return "metamethod";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Try to find a name for a function based on how it was called.
|
||||
*/
|
||||
static const char *funcnamefromcall (lua_State *L, CallInfo *ci,
|
||||
const char **name) {
|
||||
if (ci->callstatus & CIST_HOOKED) { /* was it called inside a hook? */
|
||||
*name = "?";
|
||||
return "hook";
|
||||
}
|
||||
else if (ci->callstatus & CIST_FIN) { /* was it called as a finalizer? */
|
||||
*name = "__gc";
|
||||
return "metamethod"; /* report it as such */
|
||||
}
|
||||
else if (isLua(ci))
|
||||
return funcnamefromcode(L, ci_func(ci)->p, currentpc(ci), name);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Check whether pointer 'o' points to some value in the stack frame of
|
||||
** the current function and, if so, returns its index. Because 'o' may
|
||||
** not point to a value in this stack, we cannot compare it with the
|
||||
** region boundaries (undefined behavior in ISO C).
|
||||
*/
|
||||
static int instack (CallInfo *ci, const TValue *o) {
|
||||
int pos;
|
||||
StkId base = ci->func.p + 1;
|
||||
for (pos = 0; base + pos < ci->top.p; pos++) {
|
||||
if (o == s2v(base + pos))
|
||||
return pos;
|
||||
}
|
||||
return -1; /* not found */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Checks whether value 'o' came from an upvalue. (That can only happen
|
||||
** with instructions OP_GETTABUP/OP_SETTABUP, which operate directly on
|
||||
** upvalues.)
|
||||
*/
|
||||
static const char *getupvalname (CallInfo *ci, const TValue *o,
|
||||
const char **name) {
|
||||
LClosure *c = ci_func(ci);
|
||||
int i;
|
||||
for (i = 0; i < c->nupvalues; i++) {
|
||||
if (c->upvals[i]->v.p == o) {
|
||||
*name = upvalname(c->p, i);
|
||||
return "upvalue";
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static const char *formatvarinfo (lua_State *L, const char *kind,
|
||||
const char *name) {
|
||||
if (kind == NULL)
|
||||
return ""; /* no information */
|
||||
else
|
||||
return luaO_pushfstring(L, " (%s '%s')", kind, name);
|
||||
}
|
||||
|
||||
/*
|
||||
** Build a string with a "description" for the value 'o', such as
|
||||
** "variable 'x'" or "upvalue 'y'".
|
||||
*/
|
||||
static const char *varinfo (lua_State *L, const TValue *o) {
|
||||
CallInfo *ci = L->ci;
|
||||
const char *name = NULL; /* to avoid warnings */
|
||||
const char *kind = NULL;
|
||||
if (isLua(ci)) {
|
||||
kind = getupvalname(ci, o, &name); /* check whether 'o' is an upvalue */
|
||||
if (!kind) { /* not an upvalue? */
|
||||
int reg = instack(ci, o); /* try a register */
|
||||
if (reg >= 0) /* is 'o' a register? */
|
||||
kind = getobjname(ci_func(ci)->p, currentpc(ci), reg, &name);
|
||||
}
|
||||
}
|
||||
return formatvarinfo(L, kind, name);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Raise a type error
|
||||
*/
|
||||
static l_noret typeerror (lua_State *L, const TValue *o, const char *op,
|
||||
const char *extra) {
|
||||
const char *t = luaT_objtypename(L, o);
|
||||
luaG_runerror(L, "attempt to %s a %s value%s", op, t, extra);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Raise a type error with "standard" information about the faulty
|
||||
** object 'o' (using 'varinfo').
|
||||
*/
|
||||
l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) {
|
||||
typeerror(L, o, op, varinfo(L, o));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Raise an error for calling a non-callable object. Try to find a name
|
||||
** for the object based on how it was called ('funcnamefromcall'); if it
|
||||
** cannot get a name there, try 'varinfo'.
|
||||
*/
|
||||
l_noret luaG_callerror (lua_State *L, const TValue *o) {
|
||||
CallInfo *ci = L->ci;
|
||||
const char *name = NULL; /* to avoid warnings */
|
||||
const char *kind = funcnamefromcall(L, ci, &name);
|
||||
const char *extra = kind ? formatvarinfo(L, kind, name) : varinfo(L, o);
|
||||
typeerror(L, o, "call", extra);
|
||||
}
|
||||
|
||||
|
||||
l_noret luaG_forerror (lua_State *L, const TValue *o, const char *what) {
|
||||
luaG_runerror(L, "bad 'for' %s (number expected, got %s)",
|
||||
what, luaT_objtypename(L, o));
|
||||
}
|
||||
|
||||
|
||||
l_noret luaG_concaterror (lua_State *L, const TValue *p1, const TValue *p2) {
|
||||
if (ttisstring(p1) || cvt2str(p1)) p1 = p2;
|
||||
luaG_typeerror(L, p1, "concatenate");
|
||||
}
|
||||
|
||||
|
||||
l_noret luaG_opinterror (lua_State *L, const TValue *p1,
|
||||
const TValue *p2, const char *msg) {
|
||||
if (!ttisnumber(p1)) /* first operand is wrong? */
|
||||
p2 = p1; /* now second is wrong */
|
||||
luaG_typeerror(L, p2, msg);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Error when both values are convertible to numbers, but not to integers
|
||||
*/
|
||||
l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2) {
|
||||
lua_Integer temp;
|
||||
if (!luaV_tointegerns(p1, &temp, LUA_FLOORN2I))
|
||||
p2 = p1;
|
||||
luaG_runerror(L, "number%s has no integer representation", varinfo(L, p2));
|
||||
}
|
||||
|
||||
|
||||
l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) {
|
||||
const char *t1 = luaT_objtypename(L, p1);
|
||||
const char *t2 = luaT_objtypename(L, p2);
|
||||
if (strcmp(t1, t2) == 0)
|
||||
luaG_runerror(L, "attempt to compare two %s values", t1);
|
||||
else
|
||||
luaG_runerror(L, "attempt to compare %s with %s", t1, t2);
|
||||
}
|
||||
|
||||
|
||||
/* add src:line information to 'msg' */
|
||||
const char *luaG_addinfo (lua_State *L, const char *msg, TString *src,
|
||||
int line) {
|
||||
char buff[LUA_IDSIZE];
|
||||
if (src)
|
||||
luaO_chunkid(buff, getstr(src), tsslen(src));
|
||||
else { /* no source available; use "?" instead */
|
||||
buff[0] = '?'; buff[1] = '\0';
|
||||
}
|
||||
return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg);
|
||||
}
|
||||
|
||||
|
||||
l_noret luaG_errormsg (lua_State *L) {
|
||||
if (L->errfunc != 0) { /* is there an error handling function? */
|
||||
StkId errfunc = restorestack(L, L->errfunc);
|
||||
lua_assert(ttisfunction(s2v(errfunc)));
|
||||
setobjs2s(L, L->top.p, L->top.p - 1); /* move argument */
|
||||
setobjs2s(L, L->top.p - 1, errfunc); /* push function */
|
||||
L->top.p++; /* assume EXTRA_STACK */
|
||||
luaD_callnoyield(L, L->top.p - 2, 1); /* call it */
|
||||
}
|
||||
luaD_throw(L, LUA_ERRRUN);
|
||||
}
|
||||
|
||||
|
||||
l_noret luaG_runerror (lua_State *L, const char *fmt, ...) {
|
||||
CallInfo *ci = L->ci;
|
||||
const char *msg;
|
||||
va_list argp;
|
||||
luaC_checkGC(L); /* error message uses memory */
|
||||
va_start(argp, fmt);
|
||||
msg = luaO_pushvfstring(L, fmt, argp); /* format message */
|
||||
va_end(argp);
|
||||
if (isLua(ci)) { /* if Lua function, add source:line information */
|
||||
luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci));
|
||||
setobjs2s(L, L->top.p - 2, L->top.p - 1); /* remove 'msg' */
|
||||
L->top.p--;
|
||||
}
|
||||
luaG_errormsg(L);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether new instruction 'newpc' is in a different line from
|
||||
** previous instruction 'oldpc'. More often than not, 'newpc' is only
|
||||
** one or a few instructions after 'oldpc' (it must be after, see
|
||||
** caller), so try to avoid calling 'luaG_getfuncline'. If they are
|
||||
** too far apart, there is a good chance of a ABSLINEINFO in the way,
|
||||
** so it goes directly to 'luaG_getfuncline'.
|
||||
*/
|
||||
static int changedline (const Proto *p, int oldpc, int newpc) {
|
||||
if (p->lineinfo == NULL) /* no debug information? */
|
||||
return 0;
|
||||
if (newpc - oldpc < MAXIWTHABS / 2) { /* not too far apart? */
|
||||
int delta = 0; /* line difference */
|
||||
int pc = oldpc;
|
||||
for (;;) {
|
||||
int lineinfo = p->lineinfo[++pc];
|
||||
if (lineinfo == ABSLINEINFO)
|
||||
break; /* cannot compute delta; fall through */
|
||||
delta += lineinfo;
|
||||
if (pc == newpc)
|
||||
return (delta != 0); /* delta computed successfully */
|
||||
}
|
||||
}
|
||||
/* either instructions are too far apart or there is an absolute line
|
||||
info in the way; compute line difference explicitly */
|
||||
return (luaG_getfuncline(p, oldpc) != luaG_getfuncline(p, newpc));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Traces Lua calls. If code is running the first instruction of a function,
|
||||
** and function is not vararg, and it is not coming from an yield,
|
||||
** calls 'luaD_hookcall'. (Vararg functions will call 'luaD_hookcall'
|
||||
** after adjusting its variable arguments; otherwise, they could call
|
||||
** a line/count hook before the call hook. Functions coming from
|
||||
** an yield already called 'luaD_hookcall' before yielding.)
|
||||
*/
|
||||
int luaG_tracecall (lua_State *L) {
|
||||
CallInfo *ci = L->ci;
|
||||
Proto *p = ci_func(ci)->p;
|
||||
ci->u.l.trap = 1; /* ensure hooks will be checked */
|
||||
if (ci->u.l.savedpc == p->code) { /* first instruction (not resuming)? */
|
||||
if (p->is_vararg)
|
||||
return 0; /* hooks will start at VARARGPREP instruction */
|
||||
else if (!(ci->callstatus & CIST_HOOKYIELD)) /* not yieded? */
|
||||
luaD_hookcall(L, ci); /* check 'call' hook */
|
||||
}
|
||||
return 1; /* keep 'trap' on */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Traces the execution of a Lua function. Called before the execution
|
||||
** of each opcode, when debug is on. 'L->oldpc' stores the last
|
||||
** instruction traced, to detect line changes. When entering a new
|
||||
** function, 'npci' will be zero and will test as a new line whatever
|
||||
** the value of 'oldpc'. Some exceptional conditions may return to
|
||||
** a function without setting 'oldpc'. In that case, 'oldpc' may be
|
||||
** invalid; if so, use zero as a valid value. (A wrong but valid 'oldpc'
|
||||
** at most causes an extra call to a line hook.)
|
||||
** This function is not "Protected" when called, so it should correct
|
||||
** 'L->top.p' before calling anything that can run the GC.
|
||||
*/
|
||||
int luaG_traceexec (lua_State *L, const Instruction *pc) {
|
||||
CallInfo *ci = L->ci;
|
||||
lu_byte mask = L->hookmask;
|
||||
const Proto *p = ci_func(ci)->p;
|
||||
int counthook;
|
||||
if (!(mask & (LUA_MASKLINE | LUA_MASKCOUNT))) { /* no hooks? */
|
||||
ci->u.l.trap = 0; /* don't need to stop again */
|
||||
return 0; /* turn off 'trap' */
|
||||
}
|
||||
pc++; /* reference is always next instruction */
|
||||
ci->u.l.savedpc = pc; /* save 'pc' */
|
||||
counthook = (mask & LUA_MASKCOUNT) && (--L->hookcount == 0);
|
||||
if (counthook)
|
||||
resethookcount(L); /* reset count */
|
||||
else if (!(mask & LUA_MASKLINE))
|
||||
return 1; /* no line hook and count != 0; nothing to be done now */
|
||||
if (ci->callstatus & CIST_HOOKYIELD) { /* hook yielded last time? */
|
||||
ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */
|
||||
return 1; /* do not call hook again (VM yielded, so it did not move) */
|
||||
}
|
||||
if (!isIT(*(ci->u.l.savedpc - 1))) /* top not being used? */
|
||||
L->top.p = ci->top.p; /* correct top */
|
||||
if (counthook)
|
||||
luaD_hook(L, LUA_HOOKCOUNT, -1, 0, 0); /* call count hook */
|
||||
if (mask & LUA_MASKLINE) {
|
||||
/* 'L->oldpc' may be invalid; use zero in this case */
|
||||
int oldpc = (L->oldpc < p->sizecode) ? L->oldpc : 0;
|
||||
int npci = pcRel(pc, p);
|
||||
if (npci <= oldpc || /* call hook when jump back (loop), */
|
||||
changedline(p, oldpc, npci)) { /* or when enter new line */
|
||||
int newline = luaG_getfuncline(p, npci);
|
||||
luaD_hook(L, LUA_HOOKLINE, newline, 0, 0); /* call line hook */
|
||||
}
|
||||
L->oldpc = npci; /* 'pc' of last call to line hook */
|
||||
}
|
||||
if (L->status == LUA_YIELD) { /* did hook yield? */
|
||||
if (counthook)
|
||||
L->hookcount = 1; /* undo decrement to zero */
|
||||
ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */
|
||||
luaD_throw(L, LUA_YIELD);
|
||||
}
|
||||
return 1; /* keep 'trap' on */
|
||||
}
|
||||
|
||||
64
crates/voltex_script/lua/ldebug.h
Normal file
64
crates/voltex_script/lua/ldebug.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
** $Id: ldebug.h $
|
||||
** Auxiliary functions from Debug Interface module
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef ldebug_h
|
||||
#define ldebug_h
|
||||
|
||||
|
||||
#include "lstate.h"
|
||||
|
||||
|
||||
#define pcRel(pc, p) (cast_int((pc) - (p)->code) - 1)
|
||||
|
||||
|
||||
/* Active Lua function (given call info) */
|
||||
#define ci_func(ci) (clLvalue(s2v((ci)->func.p)))
|
||||
|
||||
|
||||
#define resethookcount(L) (L->hookcount = L->basehookcount)
|
||||
|
||||
/*
|
||||
** mark for entries in 'lineinfo' array that has absolute information in
|
||||
** 'abslineinfo' array
|
||||
*/
|
||||
#define ABSLINEINFO (-0x80)
|
||||
|
||||
|
||||
/*
|
||||
** MAXimum number of successive Instructions WiTHout ABSolute line
|
||||
** information. (A power of two allows fast divisions.)
|
||||
*/
|
||||
#if !defined(MAXIWTHABS)
|
||||
#define MAXIWTHABS 128
|
||||
#endif
|
||||
|
||||
|
||||
LUAI_FUNC int luaG_getfuncline (const Proto *f, int pc);
|
||||
LUAI_FUNC const char *luaG_findlocal (lua_State *L, CallInfo *ci, int n,
|
||||
StkId *pos);
|
||||
LUAI_FUNC l_noret luaG_typeerror (lua_State *L, const TValue *o,
|
||||
const char *opname);
|
||||
LUAI_FUNC l_noret luaG_callerror (lua_State *L, const TValue *o);
|
||||
LUAI_FUNC l_noret luaG_forerror (lua_State *L, const TValue *o,
|
||||
const char *what);
|
||||
LUAI_FUNC l_noret luaG_concaterror (lua_State *L, const TValue *p1,
|
||||
const TValue *p2);
|
||||
LUAI_FUNC l_noret luaG_opinterror (lua_State *L, const TValue *p1,
|
||||
const TValue *p2,
|
||||
const char *msg);
|
||||
LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1,
|
||||
const TValue *p2);
|
||||
LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1,
|
||||
const TValue *p2);
|
||||
LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...);
|
||||
LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg,
|
||||
TString *src, int line);
|
||||
LUAI_FUNC l_noret luaG_errormsg (lua_State *L);
|
||||
LUAI_FUNC int luaG_traceexec (lua_State *L, const Instruction *pc);
|
||||
LUAI_FUNC int luaG_tracecall (lua_State *L);
|
||||
|
||||
|
||||
#endif
|
||||
1028
crates/voltex_script/lua/ldo.c
Normal file
1028
crates/voltex_script/lua/ldo.c
Normal file
File diff suppressed because it is too large
Load Diff
87
crates/voltex_script/lua/ldo.h
Normal file
87
crates/voltex_script/lua/ldo.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
** $Id: ldo.h $
|
||||
** Stack and Call structure of Lua
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef ldo_h
|
||||
#define ldo_h
|
||||
|
||||
|
||||
#include "llimits.h"
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
#include "lzio.h"
|
||||
|
||||
|
||||
/*
|
||||
** Macro to check stack size and grow stack if needed. Parameters
|
||||
** 'pre'/'pos' allow the macro to preserve a pointer into the
|
||||
** stack across reallocations, doing the work only when needed.
|
||||
** It also allows the running of one GC step when the stack is
|
||||
** reallocated.
|
||||
** 'condmovestack' is used in heavy tests to force a stack reallocation
|
||||
** at every check.
|
||||
*/
|
||||
#define luaD_checkstackaux(L,n,pre,pos) \
|
||||
if (l_unlikely(L->stack_last.p - L->top.p <= (n))) \
|
||||
{ pre; luaD_growstack(L, n, 1); pos; } \
|
||||
else { condmovestack(L,pre,pos); }
|
||||
|
||||
/* In general, 'pre'/'pos' are empty (nothing to save) */
|
||||
#define luaD_checkstack(L,n) luaD_checkstackaux(L,n,(void)0,(void)0)
|
||||
|
||||
|
||||
|
||||
#define savestack(L,pt) (cast_charp(pt) - cast_charp(L->stack.p))
|
||||
#define restorestack(L,n) cast(StkId, cast_charp(L->stack.p) + (n))
|
||||
|
||||
|
||||
/* macro to check stack size, preserving 'p' */
|
||||
#define checkstackp(L,n,p) \
|
||||
luaD_checkstackaux(L, n, \
|
||||
ptrdiff_t t__ = savestack(L, p), /* save 'p' */ \
|
||||
p = restorestack(L, t__)) /* 'pos' part: restore 'p' */
|
||||
|
||||
|
||||
/* macro to check stack size and GC, preserving 'p' */
|
||||
#define checkstackGCp(L,n,p) \
|
||||
luaD_checkstackaux(L, n, \
|
||||
ptrdiff_t t__ = savestack(L, p); /* save 'p' */ \
|
||||
luaC_checkGC(L), /* stack grow uses memory */ \
|
||||
p = restorestack(L, t__)) /* 'pos' part: restore 'p' */
|
||||
|
||||
|
||||
/* macro to check stack size and GC */
|
||||
#define checkstackGC(L,fsize) \
|
||||
luaD_checkstackaux(L, (fsize), luaC_checkGC(L), (void)0)
|
||||
|
||||
|
||||
/* type of protected functions, to be ran by 'runprotected' */
|
||||
typedef void (*Pfunc) (lua_State *L, void *ud);
|
||||
|
||||
LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);
|
||||
LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,
|
||||
const char *mode);
|
||||
LUAI_FUNC void luaD_hook (lua_State *L, int event, int line,
|
||||
int fTransfer, int nTransfer);
|
||||
LUAI_FUNC void luaD_hookcall (lua_State *L, CallInfo *ci);
|
||||
LUAI_FUNC int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func,
|
||||
int narg1, int delta);
|
||||
LUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults);
|
||||
LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults);
|
||||
LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults);
|
||||
LUAI_FUNC int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status);
|
||||
LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u,
|
||||
ptrdiff_t oldtop, ptrdiff_t ef);
|
||||
LUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, int nres);
|
||||
LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror);
|
||||
LUAI_FUNC int luaD_growstack (lua_State *L, int n, int raiseerror);
|
||||
LUAI_FUNC void luaD_shrinkstack (lua_State *L);
|
||||
LUAI_FUNC void luaD_inctop (lua_State *L);
|
||||
|
||||
LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode);
|
||||
LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
|
||||
|
||||
#endif
|
||||
|
||||
230
crates/voltex_script/lua/ldump.c
Normal file
230
crates/voltex_script/lua/ldump.c
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
** $Id: ldump.c $
|
||||
** save precompiled Lua chunks
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define ldump_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
#include "lundump.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
lua_State *L;
|
||||
lua_Writer writer;
|
||||
void *data;
|
||||
int strip;
|
||||
int status;
|
||||
} DumpState;
|
||||
|
||||
|
||||
/*
|
||||
** All high-level dumps go through dumpVector; you can change it to
|
||||
** change the endianness of the result
|
||||
*/
|
||||
#define dumpVector(D,v,n) dumpBlock(D,v,(n)*sizeof((v)[0]))
|
||||
|
||||
#define dumpLiteral(D, s) dumpBlock(D,s,sizeof(s) - sizeof(char))
|
||||
|
||||
|
||||
static void dumpBlock (DumpState *D, const void *b, size_t size) {
|
||||
if (D->status == 0 && size > 0) {
|
||||
lua_unlock(D->L);
|
||||
D->status = (*D->writer)(D->L, b, size, D->data);
|
||||
lua_lock(D->L);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define dumpVar(D,x) dumpVector(D,&x,1)
|
||||
|
||||
|
||||
static void dumpByte (DumpState *D, int y) {
|
||||
lu_byte x = (lu_byte)y;
|
||||
dumpVar(D, x);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** 'dumpSize' buffer size: each byte can store up to 7 bits. (The "+6"
|
||||
** rounds up the division.)
|
||||
*/
|
||||
#define DIBS ((sizeof(size_t) * CHAR_BIT + 6) / 7)
|
||||
|
||||
static void dumpSize (DumpState *D, size_t x) {
|
||||
lu_byte buff[DIBS];
|
||||
int n = 0;
|
||||
do {
|
||||
buff[DIBS - (++n)] = x & 0x7f; /* fill buffer in reverse order */
|
||||
x >>= 7;
|
||||
} while (x != 0);
|
||||
buff[DIBS - 1] |= 0x80; /* mark last byte */
|
||||
dumpVector(D, buff + DIBS - n, n);
|
||||
}
|
||||
|
||||
|
||||
static void dumpInt (DumpState *D, int x) {
|
||||
dumpSize(D, x);
|
||||
}
|
||||
|
||||
|
||||
static void dumpNumber (DumpState *D, lua_Number x) {
|
||||
dumpVar(D, x);
|
||||
}
|
||||
|
||||
|
||||
static void dumpInteger (DumpState *D, lua_Integer x) {
|
||||
dumpVar(D, x);
|
||||
}
|
||||
|
||||
|
||||
static void dumpString (DumpState *D, const TString *s) {
|
||||
if (s == NULL)
|
||||
dumpSize(D, 0);
|
||||
else {
|
||||
size_t size = tsslen(s);
|
||||
const char *str = getstr(s);
|
||||
dumpSize(D, size + 1);
|
||||
dumpVector(D, str, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void dumpCode (DumpState *D, const Proto *f) {
|
||||
dumpInt(D, f->sizecode);
|
||||
dumpVector(D, f->code, f->sizecode);
|
||||
}
|
||||
|
||||
|
||||
static void dumpFunction(DumpState *D, const Proto *f, TString *psource);
|
||||
|
||||
static void dumpConstants (DumpState *D, const Proto *f) {
|
||||
int i;
|
||||
int n = f->sizek;
|
||||
dumpInt(D, n);
|
||||
for (i = 0; i < n; i++) {
|
||||
const TValue *o = &f->k[i];
|
||||
int tt = ttypetag(o);
|
||||
dumpByte(D, tt);
|
||||
switch (tt) {
|
||||
case LUA_VNUMFLT:
|
||||
dumpNumber(D, fltvalue(o));
|
||||
break;
|
||||
case LUA_VNUMINT:
|
||||
dumpInteger(D, ivalue(o));
|
||||
break;
|
||||
case LUA_VSHRSTR:
|
||||
case LUA_VLNGSTR:
|
||||
dumpString(D, tsvalue(o));
|
||||
break;
|
||||
default:
|
||||
lua_assert(tt == LUA_VNIL || tt == LUA_VFALSE || tt == LUA_VTRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void dumpProtos (DumpState *D, const Proto *f) {
|
||||
int i;
|
||||
int n = f->sizep;
|
||||
dumpInt(D, n);
|
||||
for (i = 0; i < n; i++)
|
||||
dumpFunction(D, f->p[i], f->source);
|
||||
}
|
||||
|
||||
|
||||
static void dumpUpvalues (DumpState *D, const Proto *f) {
|
||||
int i, n = f->sizeupvalues;
|
||||
dumpInt(D, n);
|
||||
for (i = 0; i < n; i++) {
|
||||
dumpByte(D, f->upvalues[i].instack);
|
||||
dumpByte(D, f->upvalues[i].idx);
|
||||
dumpByte(D, f->upvalues[i].kind);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void dumpDebug (DumpState *D, const Proto *f) {
|
||||
int i, n;
|
||||
n = (D->strip) ? 0 : f->sizelineinfo;
|
||||
dumpInt(D, n);
|
||||
dumpVector(D, f->lineinfo, n);
|
||||
n = (D->strip) ? 0 : f->sizeabslineinfo;
|
||||
dumpInt(D, n);
|
||||
for (i = 0; i < n; i++) {
|
||||
dumpInt(D, f->abslineinfo[i].pc);
|
||||
dumpInt(D, f->abslineinfo[i].line);
|
||||
}
|
||||
n = (D->strip) ? 0 : f->sizelocvars;
|
||||
dumpInt(D, n);
|
||||
for (i = 0; i < n; i++) {
|
||||
dumpString(D, f->locvars[i].varname);
|
||||
dumpInt(D, f->locvars[i].startpc);
|
||||
dumpInt(D, f->locvars[i].endpc);
|
||||
}
|
||||
n = (D->strip) ? 0 : f->sizeupvalues;
|
||||
dumpInt(D, n);
|
||||
for (i = 0; i < n; i++)
|
||||
dumpString(D, f->upvalues[i].name);
|
||||
}
|
||||
|
||||
|
||||
static void dumpFunction (DumpState *D, const Proto *f, TString *psource) {
|
||||
if (D->strip || f->source == psource)
|
||||
dumpString(D, NULL); /* no debug info or same source as its parent */
|
||||
else
|
||||
dumpString(D, f->source);
|
||||
dumpInt(D, f->linedefined);
|
||||
dumpInt(D, f->lastlinedefined);
|
||||
dumpByte(D, f->numparams);
|
||||
dumpByte(D, f->is_vararg);
|
||||
dumpByte(D, f->maxstacksize);
|
||||
dumpCode(D, f);
|
||||
dumpConstants(D, f);
|
||||
dumpUpvalues(D, f);
|
||||
dumpProtos(D, f);
|
||||
dumpDebug(D, f);
|
||||
}
|
||||
|
||||
|
||||
static void dumpHeader (DumpState *D) {
|
||||
dumpLiteral(D, LUA_SIGNATURE);
|
||||
dumpByte(D, LUAC_VERSION);
|
||||
dumpByte(D, LUAC_FORMAT);
|
||||
dumpLiteral(D, LUAC_DATA);
|
||||
dumpByte(D, sizeof(Instruction));
|
||||
dumpByte(D, sizeof(lua_Integer));
|
||||
dumpByte(D, sizeof(lua_Number));
|
||||
dumpInteger(D, LUAC_INT);
|
||||
dumpNumber(D, LUAC_NUM);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** dump Lua function as precompiled chunk
|
||||
*/
|
||||
int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data,
|
||||
int strip) {
|
||||
DumpState D;
|
||||
D.L = L;
|
||||
D.writer = w;
|
||||
D.data = data;
|
||||
D.strip = strip;
|
||||
D.status = 0;
|
||||
dumpHeader(&D);
|
||||
dumpByte(&D, f->sizeupvalues);
|
||||
dumpFunction(&D, f, NULL);
|
||||
return D.status;
|
||||
}
|
||||
|
||||
294
crates/voltex_script/lua/lfunc.c
Normal file
294
crates/voltex_script/lua/lfunc.c
Normal file
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
** $Id: lfunc.c $
|
||||
** Auxiliary functions to manipulate prototypes and closures
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lfunc_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "ldebug.h"
|
||||
#include "ldo.h"
|
||||
#include "lfunc.h"
|
||||
#include "lgc.h"
|
||||
#include "lmem.h"
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
|
||||
|
||||
|
||||
CClosure *luaF_newCclosure (lua_State *L, int nupvals) {
|
||||
GCObject *o = luaC_newobj(L, LUA_VCCL, sizeCclosure(nupvals));
|
||||
CClosure *c = gco2ccl(o);
|
||||
c->nupvalues = cast_byte(nupvals);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
LClosure *luaF_newLclosure (lua_State *L, int nupvals) {
|
||||
GCObject *o = luaC_newobj(L, LUA_VLCL, sizeLclosure(nupvals));
|
||||
LClosure *c = gco2lcl(o);
|
||||
c->p = NULL;
|
||||
c->nupvalues = cast_byte(nupvals);
|
||||
while (nupvals--) c->upvals[nupvals] = NULL;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** fill a closure with new closed upvalues
|
||||
*/
|
||||
void luaF_initupvals (lua_State *L, LClosure *cl) {
|
||||
int i;
|
||||
for (i = 0; i < cl->nupvalues; i++) {
|
||||
GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal));
|
||||
UpVal *uv = gco2upv(o);
|
||||
uv->v.p = &uv->u.value; /* make it closed */
|
||||
setnilvalue(uv->v.p);
|
||||
cl->upvals[i] = uv;
|
||||
luaC_objbarrier(L, cl, uv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Create a new upvalue at the given level, and link it to the list of
|
||||
** open upvalues of 'L' after entry 'prev'.
|
||||
**/
|
||||
static UpVal *newupval (lua_State *L, StkId level, UpVal **prev) {
|
||||
GCObject *o = luaC_newobj(L, LUA_VUPVAL, sizeof(UpVal));
|
||||
UpVal *uv = gco2upv(o);
|
||||
UpVal *next = *prev;
|
||||
uv->v.p = s2v(level); /* current value lives in the stack */
|
||||
uv->u.open.next = next; /* link it to list of open upvalues */
|
||||
uv->u.open.previous = prev;
|
||||
if (next)
|
||||
next->u.open.previous = &uv->u.open.next;
|
||||
*prev = uv;
|
||||
if (!isintwups(L)) { /* thread not in list of threads with upvalues? */
|
||||
L->twups = G(L)->twups; /* link it to the list */
|
||||
G(L)->twups = L;
|
||||
}
|
||||
return uv;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Find and reuse, or create if it does not exist, an upvalue
|
||||
** at the given level.
|
||||
*/
|
||||
UpVal *luaF_findupval (lua_State *L, StkId level) {
|
||||
UpVal **pp = &L->openupval;
|
||||
UpVal *p;
|
||||
lua_assert(isintwups(L) || L->openupval == NULL);
|
||||
while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */
|
||||
lua_assert(!isdead(G(L), p));
|
||||
if (uplevel(p) == level) /* corresponding upvalue? */
|
||||
return p; /* return it */
|
||||
pp = &p->u.open.next;
|
||||
}
|
||||
/* not found: create a new upvalue after 'pp' */
|
||||
return newupval(L, level, pp);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Call closing method for object 'obj' with error message 'err'. The
|
||||
** boolean 'yy' controls whether the call is yieldable.
|
||||
** (This function assumes EXTRA_STACK.)
|
||||
*/
|
||||
static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) {
|
||||
StkId top = L->top.p;
|
||||
const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
|
||||
setobj2s(L, top, tm); /* will call metamethod... */
|
||||
setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */
|
||||
setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */
|
||||
L->top.p = top + 3; /* add function and arguments */
|
||||
if (yy)
|
||||
luaD_call(L, top, 0);
|
||||
else
|
||||
luaD_callnoyield(L, top, 0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether object at given level has a close metamethod and raise
|
||||
** an error if not.
|
||||
*/
|
||||
static void checkclosemth (lua_State *L, StkId level) {
|
||||
const TValue *tm = luaT_gettmbyobj(L, s2v(level), TM_CLOSE);
|
||||
if (ttisnil(tm)) { /* no metamethod? */
|
||||
int idx = cast_int(level - L->ci->func.p); /* variable index */
|
||||
const char *vname = luaG_findlocal(L, L->ci, idx, NULL);
|
||||
if (vname == NULL) vname = "?";
|
||||
luaG_runerror(L, "variable '%s' got a non-closable value", vname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Prepare and call a closing method.
|
||||
** If status is CLOSEKTOP, the call to the closing method will be pushed
|
||||
** at the top of the stack. Otherwise, values can be pushed right after
|
||||
** the 'level' of the upvalue being closed, as everything after that
|
||||
** won't be used again.
|
||||
*/
|
||||
static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) {
|
||||
TValue *uv = s2v(level); /* value being closed */
|
||||
TValue *errobj;
|
||||
if (status == CLOSEKTOP)
|
||||
errobj = &G(L)->nilvalue; /* error object is nil */
|
||||
else { /* 'luaD_seterrorobj' will set top to level + 2 */
|
||||
errobj = s2v(level + 1); /* error object goes after 'uv' */
|
||||
luaD_seterrorobj(L, status, level + 1); /* set error object */
|
||||
}
|
||||
callclosemethod(L, uv, errobj, yy);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Maximum value for deltas in 'tbclist', dependent on the type
|
||||
** of delta. (This macro assumes that an 'L' is in scope where it
|
||||
** is used.)
|
||||
*/
|
||||
#define MAXDELTA \
|
||||
((256ul << ((sizeof(L->stack.p->tbclist.delta) - 1) * 8)) - 1)
|
||||
|
||||
|
||||
/*
|
||||
** Insert a variable in the list of to-be-closed variables.
|
||||
*/
|
||||
void luaF_newtbcupval (lua_State *L, StkId level) {
|
||||
lua_assert(level > L->tbclist.p);
|
||||
if (l_isfalse(s2v(level)))
|
||||
return; /* false doesn't need to be closed */
|
||||
checkclosemth(L, level); /* value must have a close method */
|
||||
while (cast_uint(level - L->tbclist.p) > MAXDELTA) {
|
||||
L->tbclist.p += MAXDELTA; /* create a dummy node at maximum delta */
|
||||
L->tbclist.p->tbclist.delta = 0;
|
||||
}
|
||||
level->tbclist.delta = cast(unsigned short, level - L->tbclist.p);
|
||||
L->tbclist.p = level;
|
||||
}
|
||||
|
||||
|
||||
void luaF_unlinkupval (UpVal *uv) {
|
||||
lua_assert(upisopen(uv));
|
||||
*uv->u.open.previous = uv->u.open.next;
|
||||
if (uv->u.open.next)
|
||||
uv->u.open.next->u.open.previous = uv->u.open.previous;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Close all upvalues up to the given stack level.
|
||||
*/
|
||||
void luaF_closeupval (lua_State *L, StkId level) {
|
||||
UpVal *uv;
|
||||
StkId upl; /* stack index pointed by 'uv' */
|
||||
while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {
|
||||
TValue *slot = &uv->u.value; /* new position for value */
|
||||
lua_assert(uplevel(uv) < L->top.p);
|
||||
luaF_unlinkupval(uv); /* remove upvalue from 'openupval' list */
|
||||
setobj(L, slot, uv->v.p); /* move value to upvalue slot */
|
||||
uv->v.p = slot; /* now current value lives here */
|
||||
if (!iswhite(uv)) { /* neither white nor dead? */
|
||||
nw2black(uv); /* closed upvalues cannot be gray */
|
||||
luaC_barrier(L, uv, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Remove first element from the tbclist plus its dummy nodes.
|
||||
*/
|
||||
static void poptbclist (lua_State *L) {
|
||||
StkId tbc = L->tbclist.p;
|
||||
lua_assert(tbc->tbclist.delta > 0); /* first element cannot be dummy */
|
||||
tbc -= tbc->tbclist.delta;
|
||||
while (tbc > L->stack.p && tbc->tbclist.delta == 0)
|
||||
tbc -= MAXDELTA; /* remove dummy nodes */
|
||||
L->tbclist.p = tbc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Close all upvalues and to-be-closed variables up to the given stack
|
||||
** level. Return restored 'level'.
|
||||
*/
|
||||
StkId luaF_close (lua_State *L, StkId level, int status, int yy) {
|
||||
ptrdiff_t levelrel = savestack(L, level);
|
||||
luaF_closeupval(L, level); /* first, close the upvalues */
|
||||
while (L->tbclist.p >= level) { /* traverse tbc's down to that level */
|
||||
StkId tbc = L->tbclist.p; /* get variable index */
|
||||
poptbclist(L); /* remove it from list */
|
||||
prepcallclosemth(L, tbc, status, yy); /* close variable */
|
||||
level = restorestack(L, levelrel);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
|
||||
Proto *luaF_newproto (lua_State *L) {
|
||||
GCObject *o = luaC_newobj(L, LUA_VPROTO, sizeof(Proto));
|
||||
Proto *f = gco2p(o);
|
||||
f->k = NULL;
|
||||
f->sizek = 0;
|
||||
f->p = NULL;
|
||||
f->sizep = 0;
|
||||
f->code = NULL;
|
||||
f->sizecode = 0;
|
||||
f->lineinfo = NULL;
|
||||
f->sizelineinfo = 0;
|
||||
f->abslineinfo = NULL;
|
||||
f->sizeabslineinfo = 0;
|
||||
f->upvalues = NULL;
|
||||
f->sizeupvalues = 0;
|
||||
f->numparams = 0;
|
||||
f->is_vararg = 0;
|
||||
f->maxstacksize = 0;
|
||||
f->locvars = NULL;
|
||||
f->sizelocvars = 0;
|
||||
f->linedefined = 0;
|
||||
f->lastlinedefined = 0;
|
||||
f->source = NULL;
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
void luaF_freeproto (lua_State *L, Proto *f) {
|
||||
luaM_freearray(L, f->code, f->sizecode);
|
||||
luaM_freearray(L, f->p, f->sizep);
|
||||
luaM_freearray(L, f->k, f->sizek);
|
||||
luaM_freearray(L, f->lineinfo, f->sizelineinfo);
|
||||
luaM_freearray(L, f->abslineinfo, f->sizeabslineinfo);
|
||||
luaM_freearray(L, f->locvars, f->sizelocvars);
|
||||
luaM_freearray(L, f->upvalues, f->sizeupvalues);
|
||||
luaM_free(L, f);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Look for n-th local variable at line 'line' in function 'func'.
|
||||
** Returns NULL if not found.
|
||||
*/
|
||||
const char *luaF_getlocalname (const Proto *f, int local_number, int pc) {
|
||||
int i;
|
||||
for (i = 0; i<f->sizelocvars && f->locvars[i].startpc <= pc; i++) {
|
||||
if (pc < f->locvars[i].endpc) { /* is variable active? */
|
||||
local_number--;
|
||||
if (local_number == 0)
|
||||
return getstr(f->locvars[i].varname);
|
||||
}
|
||||
}
|
||||
return NULL; /* not found */
|
||||
}
|
||||
|
||||
64
crates/voltex_script/lua/lfunc.h
Normal file
64
crates/voltex_script/lua/lfunc.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
** $Id: lfunc.h $
|
||||
** Auxiliary functions to manipulate prototypes and closures
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef lfunc_h
|
||||
#define lfunc_h
|
||||
|
||||
|
||||
#include "lobject.h"
|
||||
|
||||
|
||||
#define sizeCclosure(n) (cast_int(offsetof(CClosure, upvalue)) + \
|
||||
cast_int(sizeof(TValue)) * (n))
|
||||
|
||||
#define sizeLclosure(n) (cast_int(offsetof(LClosure, upvals)) + \
|
||||
cast_int(sizeof(TValue *)) * (n))
|
||||
|
||||
|
||||
/* test whether thread is in 'twups' list */
|
||||
#define isintwups(L) (L->twups != L)
|
||||
|
||||
|
||||
/*
|
||||
** maximum number of upvalues in a closure (both C and Lua). (Value
|
||||
** must fit in a VM register.)
|
||||
*/
|
||||
#define MAXUPVAL 255
|
||||
|
||||
|
||||
#define upisopen(up) ((up)->v.p != &(up)->u.value)
|
||||
|
||||
|
||||
#define uplevel(up) check_exp(upisopen(up), cast(StkId, (up)->v.p))
|
||||
|
||||
|
||||
/*
|
||||
** maximum number of misses before giving up the cache of closures
|
||||
** in prototypes
|
||||
*/
|
||||
#define MAXMISS 10
|
||||
|
||||
|
||||
|
||||
/* special status to close upvalues preserving the top of the stack */
|
||||
#define CLOSEKTOP (-1)
|
||||
|
||||
|
||||
LUAI_FUNC Proto *luaF_newproto (lua_State *L);
|
||||
LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nupvals);
|
||||
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals);
|
||||
LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
|
||||
LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
|
||||
LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level);
|
||||
LUAI_FUNC void luaF_closeupval (lua_State *L, StkId level);
|
||||
LUAI_FUNC StkId luaF_close (lua_State *L, StkId level, int status, int yy);
|
||||
LUAI_FUNC void luaF_unlinkupval (UpVal *uv);
|
||||
LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
|
||||
LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number,
|
||||
int pc);
|
||||
|
||||
|
||||
#endif
|
||||
1743
crates/voltex_script/lua/lgc.c
Normal file
1743
crates/voltex_script/lua/lgc.c
Normal file
File diff suppressed because it is too large
Load Diff
202
crates/voltex_script/lua/lgc.h
Normal file
202
crates/voltex_script/lua/lgc.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
** $Id: lgc.h $
|
||||
** Garbage Collector
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef lgc_h
|
||||
#define lgc_h
|
||||
|
||||
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
|
||||
/*
|
||||
** Collectable objects may have one of three colors: white, which means
|
||||
** the object is not marked; gray, which means the object is marked, but
|
||||
** its references may be not marked; and black, which means that the
|
||||
** object and all its references are marked. The main invariant of the
|
||||
** garbage collector, while marking objects, is that a black object can
|
||||
** never point to a white one. Moreover, any gray object must be in a
|
||||
** "gray list" (gray, grayagain, weak, allweak, ephemeron) so that it
|
||||
** can be visited again before finishing the collection cycle. (Open
|
||||
** upvalues are an exception to this rule.) These lists have no meaning
|
||||
** when the invariant is not being enforced (e.g., sweep phase).
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** Possible states of the Garbage Collector
|
||||
*/
|
||||
#define GCSpropagate 0
|
||||
#define GCSenteratomic 1
|
||||
#define GCSatomic 2
|
||||
#define GCSswpallgc 3
|
||||
#define GCSswpfinobj 4
|
||||
#define GCSswptobefnz 5
|
||||
#define GCSswpend 6
|
||||
#define GCScallfin 7
|
||||
#define GCSpause 8
|
||||
|
||||
|
||||
#define issweepphase(g) \
|
||||
(GCSswpallgc <= (g)->gcstate && (g)->gcstate <= GCSswpend)
|
||||
|
||||
|
||||
/*
|
||||
** macro to tell when main invariant (white objects cannot point to black
|
||||
** ones) must be kept. During a collection, the sweep
|
||||
** phase may break the invariant, as objects turned white may point to
|
||||
** still-black objects. The invariant is restored when sweep ends and
|
||||
** all objects are white again.
|
||||
*/
|
||||
|
||||
#define keepinvariant(g) ((g)->gcstate <= GCSatomic)
|
||||
|
||||
|
||||
/*
|
||||
** some useful bit tricks
|
||||
*/
|
||||
#define resetbits(x,m) ((x) &= cast_byte(~(m)))
|
||||
#define setbits(x,m) ((x) |= (m))
|
||||
#define testbits(x,m) ((x) & (m))
|
||||
#define bitmask(b) (1<<(b))
|
||||
#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2))
|
||||
#define l_setbit(x,b) setbits(x, bitmask(b))
|
||||
#define resetbit(x,b) resetbits(x, bitmask(b))
|
||||
#define testbit(x,b) testbits(x, bitmask(b))
|
||||
|
||||
|
||||
/*
|
||||
** Layout for bit use in 'marked' field. First three bits are
|
||||
** used for object "age" in generational mode. Last bit is used
|
||||
** by tests.
|
||||
*/
|
||||
#define WHITE0BIT 3 /* object is white (type 0) */
|
||||
#define WHITE1BIT 4 /* object is white (type 1) */
|
||||
#define BLACKBIT 5 /* object is black */
|
||||
#define FINALIZEDBIT 6 /* object has been marked for finalization */
|
||||
|
||||
#define TESTBIT 7
|
||||
|
||||
|
||||
|
||||
#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT)
|
||||
|
||||
|
||||
#define iswhite(x) testbits((x)->marked, WHITEBITS)
|
||||
#define isblack(x) testbit((x)->marked, BLACKBIT)
|
||||
#define isgray(x) /* neither white nor black */ \
|
||||
(!testbits((x)->marked, WHITEBITS | bitmask(BLACKBIT)))
|
||||
|
||||
#define tofinalize(x) testbit((x)->marked, FINALIZEDBIT)
|
||||
|
||||
#define otherwhite(g) ((g)->currentwhite ^ WHITEBITS)
|
||||
#define isdeadm(ow,m) ((m) & (ow))
|
||||
#define isdead(g,v) isdeadm(otherwhite(g), (v)->marked)
|
||||
|
||||
#define changewhite(x) ((x)->marked ^= WHITEBITS)
|
||||
#define nw2black(x) \
|
||||
check_exp(!iswhite(x), l_setbit((x)->marked, BLACKBIT))
|
||||
|
||||
#define luaC_white(g) cast_byte((g)->currentwhite & WHITEBITS)
|
||||
|
||||
|
||||
/* object age in generational mode */
|
||||
#define G_NEW 0 /* created in current cycle */
|
||||
#define G_SURVIVAL 1 /* created in previous cycle */
|
||||
#define G_OLD0 2 /* marked old by frw. barrier in this cycle */
|
||||
#define G_OLD1 3 /* first full cycle as old */
|
||||
#define G_OLD 4 /* really old object (not to be visited) */
|
||||
#define G_TOUCHED1 5 /* old object touched this cycle */
|
||||
#define G_TOUCHED2 6 /* old object touched in previous cycle */
|
||||
|
||||
#define AGEBITS 7 /* all age bits (111) */
|
||||
|
||||
#define getage(o) ((o)->marked & AGEBITS)
|
||||
#define setage(o,a) ((o)->marked = cast_byte(((o)->marked & (~AGEBITS)) | a))
|
||||
#define isold(o) (getage(o) > G_SURVIVAL)
|
||||
|
||||
#define changeage(o,f,t) \
|
||||
check_exp(getage(o) == (f), (o)->marked ^= ((f)^(t)))
|
||||
|
||||
|
||||
/* Default Values for GC parameters */
|
||||
#define LUAI_GENMAJORMUL 100
|
||||
#define LUAI_GENMINORMUL 20
|
||||
|
||||
/* wait memory to double before starting new cycle */
|
||||
#define LUAI_GCPAUSE 200
|
||||
|
||||
/*
|
||||
** some gc parameters are stored divided by 4 to allow a maximum value
|
||||
** up to 1023 in a 'lu_byte'.
|
||||
*/
|
||||
#define getgcparam(p) ((p) * 4)
|
||||
#define setgcparam(p,v) ((p) = (v) / 4)
|
||||
|
||||
#define LUAI_GCMUL 100
|
||||
|
||||
/* how much to allocate before next GC step (log2) */
|
||||
#define LUAI_GCSTEPSIZE 13 /* 8 KB */
|
||||
|
||||
|
||||
/*
|
||||
** Check whether the declared GC mode is generational. While in
|
||||
** generational mode, the collector can go temporarily to incremental
|
||||
** mode to improve performance. This is signaled by 'g->lastatomic != 0'.
|
||||
*/
|
||||
#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0)
|
||||
|
||||
|
||||
/*
|
||||
** Control when GC is running:
|
||||
*/
|
||||
#define GCSTPUSR 1 /* bit true when GC stopped by user */
|
||||
#define GCSTPGC 2 /* bit true when GC stopped by itself */
|
||||
#define GCSTPCLS 4 /* bit true when closing Lua state */
|
||||
#define gcrunning(g) ((g)->gcstp == 0)
|
||||
|
||||
|
||||
/*
|
||||
** Does one step of collection when debt becomes positive. 'pre'/'pos'
|
||||
** allows some adjustments to be done only when needed. macro
|
||||
** 'condchangemem' is used only for heavy tests (forcing a full
|
||||
** GC cycle on every opportunity)
|
||||
*/
|
||||
#define luaC_condGC(L,pre,pos) \
|
||||
{ if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \
|
||||
condchangemem(L,pre,pos); }
|
||||
|
||||
/* more often than not, 'pre'/'pos' are empty */
|
||||
#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0)
|
||||
|
||||
|
||||
#define luaC_objbarrier(L,p,o) ( \
|
||||
(isblack(p) && iswhite(o)) ? \
|
||||
luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0))
|
||||
|
||||
#define luaC_barrier(L,p,v) ( \
|
||||
iscollectable(v) ? luaC_objbarrier(L,p,gcvalue(v)) : cast_void(0))
|
||||
|
||||
#define luaC_objbarrierback(L,p,o) ( \
|
||||
(isblack(p) && iswhite(o)) ? luaC_barrierback_(L,p) : cast_void(0))
|
||||
|
||||
#define luaC_barrierback(L,p,v) ( \
|
||||
iscollectable(v) ? luaC_objbarrierback(L, p, gcvalue(v)) : cast_void(0))
|
||||
|
||||
LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o);
|
||||
LUAI_FUNC void luaC_freeallobjects (lua_State *L);
|
||||
LUAI_FUNC void luaC_step (lua_State *L);
|
||||
LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask);
|
||||
LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency);
|
||||
LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz);
|
||||
LUAI_FUNC GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz,
|
||||
size_t offset);
|
||||
LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v);
|
||||
LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o);
|
||||
LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt);
|
||||
LUAI_FUNC void luaC_changemode (lua_State *L, int newmode);
|
||||
|
||||
|
||||
#endif
|
||||
65
crates/voltex_script/lua/linit.c
Normal file
65
crates/voltex_script/lua/linit.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
** $Id: linit.c $
|
||||
** Initialization of libraries for lua.c and other clients
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
|
||||
#define linit_c
|
||||
#define LUA_LIB
|
||||
|
||||
/*
|
||||
** If you embed Lua in your program and need to open the standard
|
||||
** libraries, call luaL_openlibs in your program. If you need a
|
||||
** different set of libraries, copy this file to your project and edit
|
||||
** it to suit your needs.
|
||||
**
|
||||
** You can also *preload* libraries, so that a later 'require' can
|
||||
** open the library, which is already linked to the application.
|
||||
** For that, do the following code:
|
||||
**
|
||||
** luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
|
||||
** lua_pushcfunction(L, luaopen_modname);
|
||||
** lua_setfield(L, -2, modname);
|
||||
** lua_pop(L, 1); // remove PRELOAD table
|
||||
*/
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
|
||||
/*
|
||||
** these libs are loaded by lua.c and are readily available to any Lua
|
||||
** program
|
||||
*/
|
||||
static const luaL_Reg loadedlibs[] = {
|
||||
{LUA_GNAME, luaopen_base},
|
||||
{LUA_LOADLIBNAME, luaopen_package},
|
||||
{LUA_COLIBNAME, luaopen_coroutine},
|
||||
{LUA_TABLIBNAME, luaopen_table},
|
||||
{LUA_IOLIBNAME, luaopen_io},
|
||||
{LUA_OSLIBNAME, luaopen_os},
|
||||
{LUA_STRLIBNAME, luaopen_string},
|
||||
{LUA_MATHLIBNAME, luaopen_math},
|
||||
{LUA_UTF8LIBNAME, luaopen_utf8},
|
||||
{LUA_DBLIBNAME, luaopen_debug},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
LUALIB_API void luaL_openlibs (lua_State *L) {
|
||||
const luaL_Reg *lib;
|
||||
/* "require" functions from 'loadedlibs' and set results to global table */
|
||||
for (lib = loadedlibs; lib->func; lib++) {
|
||||
luaL_requiref(L, lib->name, lib->func, 1);
|
||||
lua_pop(L, 1); /* remove lib */
|
||||
}
|
||||
}
|
||||
|
||||
841
crates/voltex_script/lua/liolib.c
Normal file
841
crates/voltex_script/lua/liolib.c
Normal file
@@ -0,0 +1,841 @@
|
||||
/*
|
||||
** $Id: liolib.c $
|
||||
** Standard I/O (and system) library
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define liolib_c
|
||||
#define LUA_LIB
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Change this macro to accept other modes for 'fopen' besides
|
||||
** the standard ones.
|
||||
*/
|
||||
#if !defined(l_checkmode)
|
||||
|
||||
/* accepted extensions to 'mode' in 'fopen' */
|
||||
#if !defined(L_MODEEXT)
|
||||
#define L_MODEEXT "b"
|
||||
#endif
|
||||
|
||||
/* Check whether 'mode' matches '[rwa]%+?[L_MODEEXT]*' */
|
||||
static int l_checkmode (const char *mode) {
|
||||
return (*mode != '\0' && strchr("rwa", *(mode++)) != NULL &&
|
||||
(*mode != '+' || ((void)(++mode), 1)) && /* skip if char is '+' */
|
||||
(strspn(mode, L_MODEEXT) == strlen(mode))); /* check extensions */
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** l_popen spawns a new process connected to the current
|
||||
** one through the file streams.
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
#if !defined(l_popen) /* { */
|
||||
|
||||
#if defined(LUA_USE_POSIX) /* { */
|
||||
|
||||
#define l_popen(L,c,m) (fflush(NULL), popen(c,m))
|
||||
#define l_pclose(L,file) (pclose(file))
|
||||
|
||||
#elif defined(LUA_USE_WINDOWS) /* }{ */
|
||||
|
||||
#define l_popen(L,c,m) (_popen(c,m))
|
||||
#define l_pclose(L,file) (_pclose(file))
|
||||
|
||||
#if !defined(l_checkmodep)
|
||||
/* Windows accepts "[rw][bt]?" as valid modes */
|
||||
#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && \
|
||||
(m[1] == '\0' || ((m[1] == 'b' || m[1] == 't') && m[2] == '\0')))
|
||||
#endif
|
||||
|
||||
#else /* }{ */
|
||||
|
||||
/* ISO C definitions */
|
||||
#define l_popen(L,c,m) \
|
||||
((void)c, (void)m, \
|
||||
luaL_error(L, "'popen' not supported"), \
|
||||
(FILE*)0)
|
||||
#define l_pclose(L,file) ((void)L, (void)file, -1)
|
||||
|
||||
#endif /* } */
|
||||
|
||||
#endif /* } */
|
||||
|
||||
|
||||
#if !defined(l_checkmodep)
|
||||
/* By default, Lua accepts only "r" or "w" as valid modes */
|
||||
#define l_checkmodep(m) ((m[0] == 'r' || m[0] == 'w') && m[1] == '\0')
|
||||
#endif
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
#if !defined(l_getc) /* { */
|
||||
|
||||
#if defined(LUA_USE_POSIX)
|
||||
#define l_getc(f) getc_unlocked(f)
|
||||
#define l_lockfile(f) flockfile(f)
|
||||
#define l_unlockfile(f) funlockfile(f)
|
||||
#else
|
||||
#define l_getc(f) getc(f)
|
||||
#define l_lockfile(f) ((void)0)
|
||||
#define l_unlockfile(f) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** l_fseek: configuration for longer offsets
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
#if !defined(l_fseek) /* { */
|
||||
|
||||
#if defined(LUA_USE_POSIX) /* { */
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#define l_fseek(f,o,w) fseeko(f,o,w)
|
||||
#define l_ftell(f) ftello(f)
|
||||
#define l_seeknum off_t
|
||||
|
||||
#elif defined(LUA_USE_WINDOWS) && !defined(_CRTIMP_TYPEINFO) \
|
||||
&& defined(_MSC_VER) && (_MSC_VER >= 1400) /* }{ */
|
||||
|
||||
/* Windows (but not DDK) and Visual C++ 2005 or higher */
|
||||
#define l_fseek(f,o,w) _fseeki64(f,o,w)
|
||||
#define l_ftell(f) _ftelli64(f)
|
||||
#define l_seeknum __int64
|
||||
|
||||
#else /* }{ */
|
||||
|
||||
/* ISO C definitions */
|
||||
#define l_fseek(f,o,w) fseek(f,o,w)
|
||||
#define l_ftell(f) ftell(f)
|
||||
#define l_seeknum long
|
||||
|
||||
#endif /* } */
|
||||
|
||||
#endif /* } */
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
|
||||
#define IO_PREFIX "_IO_"
|
||||
#define IOPREF_LEN (sizeof(IO_PREFIX)/sizeof(char) - 1)
|
||||
#define IO_INPUT (IO_PREFIX "input")
|
||||
#define IO_OUTPUT (IO_PREFIX "output")
|
||||
|
||||
|
||||
typedef luaL_Stream LStream;
|
||||
|
||||
|
||||
#define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE))
|
||||
|
||||
#define isclosed(p) ((p)->closef == NULL)
|
||||
|
||||
|
||||
static int io_type (lua_State *L) {
|
||||
LStream *p;
|
||||
luaL_checkany(L, 1);
|
||||
p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE);
|
||||
if (p == NULL)
|
||||
luaL_pushfail(L); /* not a file */
|
||||
else if (isclosed(p))
|
||||
lua_pushliteral(L, "closed file");
|
||||
else
|
||||
lua_pushliteral(L, "file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int f_tostring (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
if (isclosed(p))
|
||||
lua_pushliteral(L, "file (closed)");
|
||||
else
|
||||
lua_pushfstring(L, "file (%p)", p->f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static FILE *tofile (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
if (l_unlikely(isclosed(p)))
|
||||
luaL_error(L, "attempt to use a closed file");
|
||||
lua_assert(p->f);
|
||||
return p->f;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** When creating file handles, always creates a 'closed' file handle
|
||||
** before opening the actual file; so, if there is a memory error, the
|
||||
** handle is in a consistent state.
|
||||
*/
|
||||
static LStream *newprefile (lua_State *L) {
|
||||
LStream *p = (LStream *)lua_newuserdatauv(L, sizeof(LStream), 0);
|
||||
p->closef = NULL; /* mark file handle as 'closed' */
|
||||
luaL_setmetatable(L, LUA_FILEHANDLE);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Calls the 'close' function from a file handle. The 'volatile' avoids
|
||||
** a bug in some versions of the Clang compiler (e.g., clang 3.0 for
|
||||
** 32 bits).
|
||||
*/
|
||||
static int aux_close (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
volatile lua_CFunction cf = p->closef;
|
||||
p->closef = NULL; /* mark stream as closed */
|
||||
return (*cf)(L); /* close it */
|
||||
}
|
||||
|
||||
|
||||
static int f_close (lua_State *L) {
|
||||
tofile(L); /* make sure argument is an open stream */
|
||||
return aux_close(L);
|
||||
}
|
||||
|
||||
|
||||
static int io_close (lua_State *L) {
|
||||
if (lua_isnone(L, 1)) /* no argument? */
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT); /* use default output */
|
||||
return f_close(L);
|
||||
}
|
||||
|
||||
|
||||
static int f_gc (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
if (!isclosed(p) && p->f != NULL)
|
||||
aux_close(L); /* ignore closed and incompletely open files */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** function to close regular files
|
||||
*/
|
||||
static int io_fclose (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
errno = 0;
|
||||
return luaL_fileresult(L, (fclose(p->f) == 0), NULL);
|
||||
}
|
||||
|
||||
|
||||
static LStream *newfile (lua_State *L) {
|
||||
LStream *p = newprefile(L);
|
||||
p->f = NULL;
|
||||
p->closef = &io_fclose;
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
static void opencheck (lua_State *L, const char *fname, const char *mode) {
|
||||
LStream *p = newfile(L);
|
||||
p->f = fopen(fname, mode);
|
||||
if (l_unlikely(p->f == NULL))
|
||||
luaL_error(L, "cannot open file '%s' (%s)", fname, strerror(errno));
|
||||
}
|
||||
|
||||
|
||||
static int io_open (lua_State *L) {
|
||||
const char *filename = luaL_checkstring(L, 1);
|
||||
const char *mode = luaL_optstring(L, 2, "r");
|
||||
LStream *p = newfile(L);
|
||||
const char *md = mode; /* to traverse/check mode */
|
||||
luaL_argcheck(L, l_checkmode(md), 2, "invalid mode");
|
||||
errno = 0;
|
||||
p->f = fopen(filename, mode);
|
||||
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** function to close 'popen' files
|
||||
*/
|
||||
static int io_pclose (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
errno = 0;
|
||||
return luaL_execresult(L, l_pclose(L, p->f));
|
||||
}
|
||||
|
||||
|
||||
static int io_popen (lua_State *L) {
|
||||
const char *filename = luaL_checkstring(L, 1);
|
||||
const char *mode = luaL_optstring(L, 2, "r");
|
||||
LStream *p = newprefile(L);
|
||||
luaL_argcheck(L, l_checkmodep(mode), 2, "invalid mode");
|
||||
errno = 0;
|
||||
p->f = l_popen(L, filename, mode);
|
||||
p->closef = &io_pclose;
|
||||
return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
|
||||
}
|
||||
|
||||
|
||||
static int io_tmpfile (lua_State *L) {
|
||||
LStream *p = newfile(L);
|
||||
errno = 0;
|
||||
p->f = tmpfile();
|
||||
return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1;
|
||||
}
|
||||
|
||||
|
||||
static FILE *getiofile (lua_State *L, const char *findex) {
|
||||
LStream *p;
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, findex);
|
||||
p = (LStream *)lua_touserdata(L, -1);
|
||||
if (l_unlikely(isclosed(p)))
|
||||
luaL_error(L, "default %s file is closed", findex + IOPREF_LEN);
|
||||
return p->f;
|
||||
}
|
||||
|
||||
|
||||
static int g_iofile (lua_State *L, const char *f, const char *mode) {
|
||||
if (!lua_isnoneornil(L, 1)) {
|
||||
const char *filename = lua_tostring(L, 1);
|
||||
if (filename)
|
||||
opencheck(L, filename, mode);
|
||||
else {
|
||||
tofile(L); /* check that it's a valid file handle */
|
||||
lua_pushvalue(L, 1);
|
||||
}
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, f);
|
||||
}
|
||||
/* return current value */
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int io_input (lua_State *L) {
|
||||
return g_iofile(L, IO_INPUT, "r");
|
||||
}
|
||||
|
||||
|
||||
static int io_output (lua_State *L) {
|
||||
return g_iofile(L, IO_OUTPUT, "w");
|
||||
}
|
||||
|
||||
|
||||
static int io_readline (lua_State *L);
|
||||
|
||||
|
||||
/*
|
||||
** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit
|
||||
** in the limit for upvalues of a closure)
|
||||
*/
|
||||
#define MAXARGLINE 250
|
||||
|
||||
/*
|
||||
** Auxiliary function to create the iteration function for 'lines'.
|
||||
** The iteration function is a closure over 'io_readline', with
|
||||
** the following upvalues:
|
||||
** 1) The file being read (first value in the stack)
|
||||
** 2) the number of arguments to read
|
||||
** 3) a boolean, true iff file has to be closed when finished ('toclose')
|
||||
** *) a variable number of format arguments (rest of the stack)
|
||||
*/
|
||||
static void aux_lines (lua_State *L, int toclose) {
|
||||
int n = lua_gettop(L) - 1; /* number of arguments to read */
|
||||
luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments");
|
||||
lua_pushvalue(L, 1); /* file */
|
||||
lua_pushinteger(L, n); /* number of arguments to read */
|
||||
lua_pushboolean(L, toclose); /* close/not close file when finished */
|
||||
lua_rotate(L, 2, 3); /* move the three values to their positions */
|
||||
lua_pushcclosure(L, io_readline, 3 + n);
|
||||
}
|
||||
|
||||
|
||||
static int f_lines (lua_State *L) {
|
||||
tofile(L); /* check that it's a valid file handle */
|
||||
aux_lines(L, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Return an iteration function for 'io.lines'. If file has to be
|
||||
** closed, also returns the file itself as a second result (to be
|
||||
** closed as the state at the exit of a generic for).
|
||||
*/
|
||||
static int io_lines (lua_State *L) {
|
||||
int toclose;
|
||||
if (lua_isnone(L, 1)) lua_pushnil(L); /* at least one argument */
|
||||
if (lua_isnil(L, 1)) { /* no file name? */
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT); /* get default input */
|
||||
lua_replace(L, 1); /* put it at index 1 */
|
||||
tofile(L); /* check that it's a valid file handle */
|
||||
toclose = 0; /* do not close it after iteration */
|
||||
}
|
||||
else { /* open a new file */
|
||||
const char *filename = luaL_checkstring(L, 1);
|
||||
opencheck(L, filename, "r");
|
||||
lua_replace(L, 1); /* put file at index 1 */
|
||||
toclose = 1; /* close it after iteration */
|
||||
}
|
||||
aux_lines(L, toclose); /* push iteration function */
|
||||
if (toclose) {
|
||||
lua_pushnil(L); /* state */
|
||||
lua_pushnil(L); /* control */
|
||||
lua_pushvalue(L, 1); /* file is the to-be-closed variable (4th result) */
|
||||
return 4;
|
||||
}
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** READ
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
/* maximum length of a numeral */
|
||||
#if !defined (L_MAXLENNUM)
|
||||
#define L_MAXLENNUM 200
|
||||
#endif
|
||||
|
||||
|
||||
/* auxiliary structure used by 'read_number' */
|
||||
typedef struct {
|
||||
FILE *f; /* file being read */
|
||||
int c; /* current character (look ahead) */
|
||||
int n; /* number of elements in buffer 'buff' */
|
||||
char buff[L_MAXLENNUM + 1]; /* +1 for ending '\0' */
|
||||
} RN;
|
||||
|
||||
|
||||
/*
|
||||
** Add current char to buffer (if not out of space) and read next one
|
||||
*/
|
||||
static int nextc (RN *rn) {
|
||||
if (l_unlikely(rn->n >= L_MAXLENNUM)) { /* buffer overflow? */
|
||||
rn->buff[0] = '\0'; /* invalidate result */
|
||||
return 0; /* fail */
|
||||
}
|
||||
else {
|
||||
rn->buff[rn->n++] = rn->c; /* save current char */
|
||||
rn->c = l_getc(rn->f); /* read next one */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Accept current char if it is in 'set' (of size 2)
|
||||
*/
|
||||
static int test2 (RN *rn, const char *set) {
|
||||
if (rn->c == set[0] || rn->c == set[1])
|
||||
return nextc(rn);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Read a sequence of (hex)digits
|
||||
*/
|
||||
static int readdigits (RN *rn, int hex) {
|
||||
int count = 0;
|
||||
while ((hex ? isxdigit(rn->c) : isdigit(rn->c)) && nextc(rn))
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Read a number: first reads a valid prefix of a numeral into a buffer.
|
||||
** Then it calls 'lua_stringtonumber' to check whether the format is
|
||||
** correct and to convert it to a Lua number.
|
||||
*/
|
||||
static int read_number (lua_State *L, FILE *f) {
|
||||
RN rn;
|
||||
int count = 0;
|
||||
int hex = 0;
|
||||
char decp[2];
|
||||
rn.f = f; rn.n = 0;
|
||||
decp[0] = lua_getlocaledecpoint(); /* get decimal point from locale */
|
||||
decp[1] = '.'; /* always accept a dot */
|
||||
l_lockfile(rn.f);
|
||||
do { rn.c = l_getc(rn.f); } while (isspace(rn.c)); /* skip spaces */
|
||||
test2(&rn, "-+"); /* optional sign */
|
||||
if (test2(&rn, "00")) {
|
||||
if (test2(&rn, "xX")) hex = 1; /* numeral is hexadecimal */
|
||||
else count = 1; /* count initial '0' as a valid digit */
|
||||
}
|
||||
count += readdigits(&rn, hex); /* integral part */
|
||||
if (test2(&rn, decp)) /* decimal point? */
|
||||
count += readdigits(&rn, hex); /* fractional part */
|
||||
if (count > 0 && test2(&rn, (hex ? "pP" : "eE"))) { /* exponent mark? */
|
||||
test2(&rn, "-+"); /* exponent sign */
|
||||
readdigits(&rn, 0); /* exponent digits */
|
||||
}
|
||||
ungetc(rn.c, rn.f); /* unread look-ahead char */
|
||||
l_unlockfile(rn.f);
|
||||
rn.buff[rn.n] = '\0'; /* finish string */
|
||||
if (l_likely(lua_stringtonumber(L, rn.buff)))
|
||||
return 1; /* ok, it is a valid number */
|
||||
else { /* invalid format */
|
||||
lua_pushnil(L); /* "result" to be removed */
|
||||
return 0; /* read fails */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int test_eof (lua_State *L, FILE *f) {
|
||||
int c = getc(f);
|
||||
ungetc(c, f); /* no-op when c == EOF */
|
||||
lua_pushliteral(L, "");
|
||||
return (c != EOF);
|
||||
}
|
||||
|
||||
|
||||
static int read_line (lua_State *L, FILE *f, int chop) {
|
||||
luaL_Buffer b;
|
||||
int c;
|
||||
luaL_buffinit(L, &b);
|
||||
do { /* may need to read several chunks to get whole line */
|
||||
char *buff = luaL_prepbuffer(&b); /* preallocate buffer space */
|
||||
int i = 0;
|
||||
l_lockfile(f); /* no memory errors can happen inside the lock */
|
||||
while (i < LUAL_BUFFERSIZE && (c = l_getc(f)) != EOF && c != '\n')
|
||||
buff[i++] = c; /* read up to end of line or buffer limit */
|
||||
l_unlockfile(f);
|
||||
luaL_addsize(&b, i);
|
||||
} while (c != EOF && c != '\n'); /* repeat until end of line */
|
||||
if (!chop && c == '\n') /* want a newline and have one? */
|
||||
luaL_addchar(&b, c); /* add ending newline to result */
|
||||
luaL_pushresult(&b); /* close buffer */
|
||||
/* return ok if read something (either a newline or something else) */
|
||||
return (c == '\n' || lua_rawlen(L, -1) > 0);
|
||||
}
|
||||
|
||||
|
||||
static void read_all (lua_State *L, FILE *f) {
|
||||
size_t nr;
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
do { /* read file in chunks of LUAL_BUFFERSIZE bytes */
|
||||
char *p = luaL_prepbuffer(&b);
|
||||
nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, f);
|
||||
luaL_addsize(&b, nr);
|
||||
} while (nr == LUAL_BUFFERSIZE);
|
||||
luaL_pushresult(&b); /* close buffer */
|
||||
}
|
||||
|
||||
|
||||
static int read_chars (lua_State *L, FILE *f, size_t n) {
|
||||
size_t nr; /* number of chars actually read */
|
||||
char *p;
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
p = luaL_prepbuffsize(&b, n); /* prepare buffer to read whole block */
|
||||
nr = fread(p, sizeof(char), n, f); /* try to read 'n' chars */
|
||||
luaL_addsize(&b, nr);
|
||||
luaL_pushresult(&b); /* close buffer */
|
||||
return (nr > 0); /* true iff read something */
|
||||
}
|
||||
|
||||
|
||||
static int g_read (lua_State *L, FILE *f, int first) {
|
||||
int nargs = lua_gettop(L) - 1;
|
||||
int n, success;
|
||||
clearerr(f);
|
||||
errno = 0;
|
||||
if (nargs == 0) { /* no arguments? */
|
||||
success = read_line(L, f, 1);
|
||||
n = first + 1; /* to return 1 result */
|
||||
}
|
||||
else {
|
||||
/* ensure stack space for all results and for auxlib's buffer */
|
||||
luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments");
|
||||
success = 1;
|
||||
for (n = first; nargs-- && success; n++) {
|
||||
if (lua_type(L, n) == LUA_TNUMBER) {
|
||||
size_t l = (size_t)luaL_checkinteger(L, n);
|
||||
success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);
|
||||
}
|
||||
else {
|
||||
const char *p = luaL_checkstring(L, n);
|
||||
if (*p == '*') p++; /* skip optional '*' (for compatibility) */
|
||||
switch (*p) {
|
||||
case 'n': /* number */
|
||||
success = read_number(L, f);
|
||||
break;
|
||||
case 'l': /* line */
|
||||
success = read_line(L, f, 1);
|
||||
break;
|
||||
case 'L': /* line with end-of-line */
|
||||
success = read_line(L, f, 0);
|
||||
break;
|
||||
case 'a': /* file */
|
||||
read_all(L, f); /* read entire file */
|
||||
success = 1; /* always success */
|
||||
break;
|
||||
default:
|
||||
return luaL_argerror(L, n, "invalid format");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ferror(f))
|
||||
return luaL_fileresult(L, 0, NULL);
|
||||
if (!success) {
|
||||
lua_pop(L, 1); /* remove last result */
|
||||
luaL_pushfail(L); /* push nil instead */
|
||||
}
|
||||
return n - first;
|
||||
}
|
||||
|
||||
|
||||
static int io_read (lua_State *L) {
|
||||
return g_read(L, getiofile(L, IO_INPUT), 1);
|
||||
}
|
||||
|
||||
|
||||
static int f_read (lua_State *L) {
|
||||
return g_read(L, tofile(L), 2);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Iteration function for 'lines'.
|
||||
*/
|
||||
static int io_readline (lua_State *L) {
|
||||
LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1));
|
||||
int i;
|
||||
int n = (int)lua_tointeger(L, lua_upvalueindex(2));
|
||||
if (isclosed(p)) /* file is already closed? */
|
||||
return luaL_error(L, "file is already closed");
|
||||
lua_settop(L , 1);
|
||||
luaL_checkstack(L, n, "too many arguments");
|
||||
for (i = 1; i <= n; i++) /* push arguments to 'g_read' */
|
||||
lua_pushvalue(L, lua_upvalueindex(3 + i));
|
||||
n = g_read(L, p->f, 2); /* 'n' is number of results */
|
||||
lua_assert(n > 0); /* should return at least a nil */
|
||||
if (lua_toboolean(L, -n)) /* read at least one value? */
|
||||
return n; /* return them */
|
||||
else { /* first result is false: EOF or error */
|
||||
if (n > 1) { /* is there error information? */
|
||||
/* 2nd result is error message */
|
||||
return luaL_error(L, "%s", lua_tostring(L, -n + 1));
|
||||
}
|
||||
if (lua_toboolean(L, lua_upvalueindex(3))) { /* generator created file? */
|
||||
lua_settop(L, 0); /* clear stack */
|
||||
lua_pushvalue(L, lua_upvalueindex(1)); /* push file at index 1 */
|
||||
aux_close(L); /* close it */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
static int g_write (lua_State *L, FILE *f, int arg) {
|
||||
int nargs = lua_gettop(L) - arg;
|
||||
int status = 1;
|
||||
errno = 0;
|
||||
for (; nargs--; arg++) {
|
||||
if (lua_type(L, arg) == LUA_TNUMBER) {
|
||||
/* optimization: could be done exactly as for strings */
|
||||
int len = lua_isinteger(L, arg)
|
||||
? fprintf(f, LUA_INTEGER_FMT,
|
||||
(LUAI_UACINT)lua_tointeger(L, arg))
|
||||
: fprintf(f, LUA_NUMBER_FMT,
|
||||
(LUAI_UACNUMBER)lua_tonumber(L, arg));
|
||||
status = status && (len > 0);
|
||||
}
|
||||
else {
|
||||
size_t l;
|
||||
const char *s = luaL_checklstring(L, arg, &l);
|
||||
status = status && (fwrite(s, sizeof(char), l, f) == l);
|
||||
}
|
||||
}
|
||||
if (l_likely(status))
|
||||
return 1; /* file handle already on stack top */
|
||||
else
|
||||
return luaL_fileresult(L, status, NULL);
|
||||
}
|
||||
|
||||
|
||||
static int io_write (lua_State *L) {
|
||||
return g_write(L, getiofile(L, IO_OUTPUT), 1);
|
||||
}
|
||||
|
||||
|
||||
static int f_write (lua_State *L) {
|
||||
FILE *f = tofile(L);
|
||||
lua_pushvalue(L, 1); /* push file at the stack top (to be returned) */
|
||||
return g_write(L, f, 2);
|
||||
}
|
||||
|
||||
|
||||
static int f_seek (lua_State *L) {
|
||||
static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END};
|
||||
static const char *const modenames[] = {"set", "cur", "end", NULL};
|
||||
FILE *f = tofile(L);
|
||||
int op = luaL_checkoption(L, 2, "cur", modenames);
|
||||
lua_Integer p3 = luaL_optinteger(L, 3, 0);
|
||||
l_seeknum offset = (l_seeknum)p3;
|
||||
luaL_argcheck(L, (lua_Integer)offset == p3, 3,
|
||||
"not an integer in proper range");
|
||||
errno = 0;
|
||||
op = l_fseek(f, offset, mode[op]);
|
||||
if (l_unlikely(op))
|
||||
return luaL_fileresult(L, 0, NULL); /* error */
|
||||
else {
|
||||
lua_pushinteger(L, (lua_Integer)l_ftell(f));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int f_setvbuf (lua_State *L) {
|
||||
static const int mode[] = {_IONBF, _IOFBF, _IOLBF};
|
||||
static const char *const modenames[] = {"no", "full", "line", NULL};
|
||||
FILE *f = tofile(L);
|
||||
int op = luaL_checkoption(L, 2, NULL, modenames);
|
||||
lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE);
|
||||
int res;
|
||||
errno = 0;
|
||||
res = setvbuf(f, NULL, mode[op], (size_t)sz);
|
||||
return luaL_fileresult(L, res == 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int io_flush (lua_State *L) {
|
||||
FILE *f = getiofile(L, IO_OUTPUT);
|
||||
errno = 0;
|
||||
return luaL_fileresult(L, fflush(f) == 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
static int f_flush (lua_State *L) {
|
||||
FILE *f = tofile(L);
|
||||
errno = 0;
|
||||
return luaL_fileresult(L, fflush(f) == 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** functions for 'io' library
|
||||
*/
|
||||
static const luaL_Reg iolib[] = {
|
||||
{"close", io_close},
|
||||
{"flush", io_flush},
|
||||
{"input", io_input},
|
||||
{"lines", io_lines},
|
||||
{"open", io_open},
|
||||
{"output", io_output},
|
||||
{"popen", io_popen},
|
||||
{"read", io_read},
|
||||
{"tmpfile", io_tmpfile},
|
||||
{"type", io_type},
|
||||
{"write", io_write},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** methods for file handles
|
||||
*/
|
||||
static const luaL_Reg meth[] = {
|
||||
{"read", f_read},
|
||||
{"write", f_write},
|
||||
{"lines", f_lines},
|
||||
{"flush", f_flush},
|
||||
{"seek", f_seek},
|
||||
{"close", f_close},
|
||||
{"setvbuf", f_setvbuf},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** metamethods for file handles
|
||||
*/
|
||||
static const luaL_Reg metameth[] = {
|
||||
{"__index", NULL}, /* placeholder */
|
||||
{"__gc", f_gc},
|
||||
{"__close", f_gc},
|
||||
{"__tostring", f_tostring},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
static void createmeta (lua_State *L) {
|
||||
luaL_newmetatable(L, LUA_FILEHANDLE); /* metatable for file handles */
|
||||
luaL_setfuncs(L, metameth, 0); /* add metamethods to new metatable */
|
||||
luaL_newlibtable(L, meth); /* create method table */
|
||||
luaL_setfuncs(L, meth, 0); /* add file methods to method table */
|
||||
lua_setfield(L, -2, "__index"); /* metatable.__index = method table */
|
||||
lua_pop(L, 1); /* pop metatable */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** function to (not) close the standard files stdin, stdout, and stderr
|
||||
*/
|
||||
static int io_noclose (lua_State *L) {
|
||||
LStream *p = tolstream(L);
|
||||
p->closef = &io_noclose; /* keep file opened */
|
||||
luaL_pushfail(L);
|
||||
lua_pushliteral(L, "cannot close standard file");
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
static void createstdfile (lua_State *L, FILE *f, const char *k,
|
||||
const char *fname) {
|
||||
LStream *p = newprefile(L);
|
||||
p->f = f;
|
||||
p->closef = &io_noclose;
|
||||
if (k != NULL) {
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */
|
||||
}
|
||||
lua_setfield(L, -2, fname); /* add file to module */
|
||||
}
|
||||
|
||||
|
||||
LUAMOD_API int luaopen_io (lua_State *L) {
|
||||
luaL_newlib(L, iolib); /* new module */
|
||||
createmeta(L);
|
||||
/* create (and set) default files */
|
||||
createstdfile(L, stdin, IO_INPUT, "stdin");
|
||||
createstdfile(L, stdout, IO_OUTPUT, "stdout");
|
||||
createstdfile(L, stderr, NULL, "stderr");
|
||||
return 1;
|
||||
}
|
||||
|
||||
112
crates/voltex_script/lua/ljumptab.h
Normal file
112
crates/voltex_script/lua/ljumptab.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
** $Id: ljumptab.h $
|
||||
** Jump Table for the Lua interpreter
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
|
||||
#undef vmdispatch
|
||||
#undef vmcase
|
||||
#undef vmbreak
|
||||
|
||||
#define vmdispatch(x) goto *disptab[x];
|
||||
|
||||
#define vmcase(l) L_##l:
|
||||
|
||||
#define vmbreak vmfetch(); vmdispatch(GET_OPCODE(i));
|
||||
|
||||
|
||||
static const void *const disptab[NUM_OPCODES] = {
|
||||
|
||||
#if 0
|
||||
** you can update the following list with this command:
|
||||
**
|
||||
** sed -n '/^OP_/\!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h
|
||||
**
|
||||
#endif
|
||||
|
||||
&&L_OP_MOVE,
|
||||
&&L_OP_LOADI,
|
||||
&&L_OP_LOADF,
|
||||
&&L_OP_LOADK,
|
||||
&&L_OP_LOADKX,
|
||||
&&L_OP_LOADFALSE,
|
||||
&&L_OP_LFALSESKIP,
|
||||
&&L_OP_LOADTRUE,
|
||||
&&L_OP_LOADNIL,
|
||||
&&L_OP_GETUPVAL,
|
||||
&&L_OP_SETUPVAL,
|
||||
&&L_OP_GETTABUP,
|
||||
&&L_OP_GETTABLE,
|
||||
&&L_OP_GETI,
|
||||
&&L_OP_GETFIELD,
|
||||
&&L_OP_SETTABUP,
|
||||
&&L_OP_SETTABLE,
|
||||
&&L_OP_SETI,
|
||||
&&L_OP_SETFIELD,
|
||||
&&L_OP_NEWTABLE,
|
||||
&&L_OP_SELF,
|
||||
&&L_OP_ADDI,
|
||||
&&L_OP_ADDK,
|
||||
&&L_OP_SUBK,
|
||||
&&L_OP_MULK,
|
||||
&&L_OP_MODK,
|
||||
&&L_OP_POWK,
|
||||
&&L_OP_DIVK,
|
||||
&&L_OP_IDIVK,
|
||||
&&L_OP_BANDK,
|
||||
&&L_OP_BORK,
|
||||
&&L_OP_BXORK,
|
||||
&&L_OP_SHRI,
|
||||
&&L_OP_SHLI,
|
||||
&&L_OP_ADD,
|
||||
&&L_OP_SUB,
|
||||
&&L_OP_MUL,
|
||||
&&L_OP_MOD,
|
||||
&&L_OP_POW,
|
||||
&&L_OP_DIV,
|
||||
&&L_OP_IDIV,
|
||||
&&L_OP_BAND,
|
||||
&&L_OP_BOR,
|
||||
&&L_OP_BXOR,
|
||||
&&L_OP_SHL,
|
||||
&&L_OP_SHR,
|
||||
&&L_OP_MMBIN,
|
||||
&&L_OP_MMBINI,
|
||||
&&L_OP_MMBINK,
|
||||
&&L_OP_UNM,
|
||||
&&L_OP_BNOT,
|
||||
&&L_OP_NOT,
|
||||
&&L_OP_LEN,
|
||||
&&L_OP_CONCAT,
|
||||
&&L_OP_CLOSE,
|
||||
&&L_OP_TBC,
|
||||
&&L_OP_JMP,
|
||||
&&L_OP_EQ,
|
||||
&&L_OP_LT,
|
||||
&&L_OP_LE,
|
||||
&&L_OP_EQK,
|
||||
&&L_OP_EQI,
|
||||
&&L_OP_LTI,
|
||||
&&L_OP_LEI,
|
||||
&&L_OP_GTI,
|
||||
&&L_OP_GEI,
|
||||
&&L_OP_TEST,
|
||||
&&L_OP_TESTSET,
|
||||
&&L_OP_CALL,
|
||||
&&L_OP_TAILCALL,
|
||||
&&L_OP_RETURN,
|
||||
&&L_OP_RETURN0,
|
||||
&&L_OP_RETURN1,
|
||||
&&L_OP_FORLOOP,
|
||||
&&L_OP_FORPREP,
|
||||
&&L_OP_TFORPREP,
|
||||
&&L_OP_TFORCALL,
|
||||
&&L_OP_TFORLOOP,
|
||||
&&L_OP_SETLIST,
|
||||
&&L_OP_CLOSURE,
|
||||
&&L_OP_VARARG,
|
||||
&&L_OP_VARARGPREP,
|
||||
&&L_OP_EXTRAARG
|
||||
|
||||
};
|
||||
581
crates/voltex_script/lua/llex.c
Normal file
581
crates/voltex_script/lua/llex.c
Normal file
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
** $Id: llex.c $
|
||||
** Lexical Analyzer
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define llex_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <locale.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lctype.h"
|
||||
#include "ldebug.h"
|
||||
#include "ldo.h"
|
||||
#include "lgc.h"
|
||||
#include "llex.h"
|
||||
#include "lobject.h"
|
||||
#include "lparser.h"
|
||||
#include "lstate.h"
|
||||
#include "lstring.h"
|
||||
#include "ltable.h"
|
||||
#include "lzio.h"
|
||||
|
||||
|
||||
|
||||
#define next(ls) (ls->current = zgetc(ls->z))
|
||||
|
||||
|
||||
|
||||
#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r')
|
||||
|
||||
|
||||
/* ORDER RESERVED */
|
||||
static const char *const luaX_tokens [] = {
|
||||
"and", "break", "do", "else", "elseif",
|
||||
"end", "false", "for", "function", "goto", "if",
|
||||
"in", "local", "nil", "not", "or", "repeat",
|
||||
"return", "then", "true", "until", "while",
|
||||
"//", "..", "...", "==", ">=", "<=", "~=",
|
||||
"<<", ">>", "::", "<eof>",
|
||||
"<number>", "<integer>", "<name>", "<string>"
|
||||
};
|
||||
|
||||
|
||||
#define save_and_next(ls) (save(ls, ls->current), next(ls))
|
||||
|
||||
|
||||
static l_noret lexerror (LexState *ls, const char *msg, int token);
|
||||
|
||||
|
||||
static void save (LexState *ls, int c) {
|
||||
Mbuffer *b = ls->buff;
|
||||
if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) {
|
||||
size_t newsize;
|
||||
if (luaZ_sizebuffer(b) >= MAX_SIZE/2)
|
||||
lexerror(ls, "lexical element too long", 0);
|
||||
newsize = luaZ_sizebuffer(b) * 2;
|
||||
luaZ_resizebuffer(ls->L, b, newsize);
|
||||
}
|
||||
b->buffer[luaZ_bufflen(b)++] = cast_char(c);
|
||||
}
|
||||
|
||||
|
||||
void luaX_init (lua_State *L) {
|
||||
int i;
|
||||
TString *e = luaS_newliteral(L, LUA_ENV); /* create env name */
|
||||
luaC_fix(L, obj2gco(e)); /* never collect this name */
|
||||
for (i=0; i<NUM_RESERVED; i++) {
|
||||
TString *ts = luaS_new(L, luaX_tokens[i]);
|
||||
luaC_fix(L, obj2gco(ts)); /* reserved words are never collected */
|
||||
ts->extra = cast_byte(i+1); /* reserved word */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char *luaX_token2str (LexState *ls, int token) {
|
||||
if (token < FIRST_RESERVED) { /* single-byte symbols? */
|
||||
if (lisprint(token))
|
||||
return luaO_pushfstring(ls->L, "'%c'", token);
|
||||
else /* control character */
|
||||
return luaO_pushfstring(ls->L, "'<\\%d>'", token);
|
||||
}
|
||||
else {
|
||||
const char *s = luaX_tokens[token - FIRST_RESERVED];
|
||||
if (token < TK_EOS) /* fixed format (symbols and reserved words)? */
|
||||
return luaO_pushfstring(ls->L, "'%s'", s);
|
||||
else /* names, strings, and numerals */
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const char *txtToken (LexState *ls, int token) {
|
||||
switch (token) {
|
||||
case TK_NAME: case TK_STRING:
|
||||
case TK_FLT: case TK_INT:
|
||||
save(ls, '\0');
|
||||
return luaO_pushfstring(ls->L, "'%s'", luaZ_buffer(ls->buff));
|
||||
default:
|
||||
return luaX_token2str(ls, token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static l_noret lexerror (LexState *ls, const char *msg, int token) {
|
||||
msg = luaG_addinfo(ls->L, msg, ls->source, ls->linenumber);
|
||||
if (token)
|
||||
luaO_pushfstring(ls->L, "%s near %s", msg, txtToken(ls, token));
|
||||
luaD_throw(ls->L, LUA_ERRSYNTAX);
|
||||
}
|
||||
|
||||
|
||||
l_noret luaX_syntaxerror (LexState *ls, const char *msg) {
|
||||
lexerror(ls, msg, ls->t.token);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Creates a new string and anchors it in scanner's table so that it
|
||||
** will not be collected until the end of the compilation; by that time
|
||||
** it should be anchored somewhere. It also internalizes long strings,
|
||||
** ensuring there is only one copy of each unique string. The table
|
||||
** here is used as a set: the string enters as the key, while its value
|
||||
** is irrelevant. We use the string itself as the value only because it
|
||||
** is a TValue readily available. Later, the code generation can change
|
||||
** this value.
|
||||
*/
|
||||
TString *luaX_newstring (LexState *ls, const char *str, size_t l) {
|
||||
lua_State *L = ls->L;
|
||||
TString *ts = luaS_newlstr(L, str, l); /* create new string */
|
||||
const TValue *o = luaH_getstr(ls->h, ts);
|
||||
if (!ttisnil(o)) /* string already present? */
|
||||
ts = keystrval(nodefromval(o)); /* get saved copy */
|
||||
else { /* not in use yet */
|
||||
TValue *stv = s2v(L->top.p++); /* reserve stack space for string */
|
||||
setsvalue(L, stv, ts); /* temporarily anchor the string */
|
||||
luaH_finishset(L, ls->h, stv, o, stv); /* t[string] = string */
|
||||
/* table is not a metatable, so it does not need to invalidate cache */
|
||||
luaC_checkGC(L);
|
||||
L->top.p--; /* remove string from stack */
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** increment line number and skips newline sequence (any of
|
||||
** \n, \r, \n\r, or \r\n)
|
||||
*/
|
||||
static void inclinenumber (LexState *ls) {
|
||||
int old = ls->current;
|
||||
lua_assert(currIsNewline(ls));
|
||||
next(ls); /* skip '\n' or '\r' */
|
||||
if (currIsNewline(ls) && ls->current != old)
|
||||
next(ls); /* skip '\n\r' or '\r\n' */
|
||||
if (++ls->linenumber >= MAX_INT)
|
||||
lexerror(ls, "chunk has too many lines", 0);
|
||||
}
|
||||
|
||||
|
||||
void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source,
|
||||
int firstchar) {
|
||||
ls->t.token = 0;
|
||||
ls->L = L;
|
||||
ls->current = firstchar;
|
||||
ls->lookahead.token = TK_EOS; /* no look-ahead token */
|
||||
ls->z = z;
|
||||
ls->fs = NULL;
|
||||
ls->linenumber = 1;
|
||||
ls->lastline = 1;
|
||||
ls->source = source;
|
||||
ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */
|
||||
luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** =======================================================
|
||||
** LEXICAL ANALYZER
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
static int check_next1 (LexState *ls, int c) {
|
||||
if (ls->current == c) {
|
||||
next(ls);
|
||||
return 1;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Check whether current char is in set 'set' (with two chars) and
|
||||
** saves it
|
||||
*/
|
||||
static int check_next2 (LexState *ls, const char *set) {
|
||||
lua_assert(set[2] == '\0');
|
||||
if (ls->current == set[0] || ls->current == set[1]) {
|
||||
save_and_next(ls);
|
||||
return 1;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
/* LUA_NUMBER */
|
||||
/*
|
||||
** This function is quite liberal in what it accepts, as 'luaO_str2num'
|
||||
** will reject ill-formed numerals. Roughly, it accepts the following
|
||||
** pattern:
|
||||
**
|
||||
** %d(%x|%.|([Ee][+-]?))* | 0[Xx](%x|%.|([Pp][+-]?))*
|
||||
**
|
||||
** The only tricky part is to accept [+-] only after a valid exponent
|
||||
** mark, to avoid reading '3-4' or '0xe+1' as a single number.
|
||||
**
|
||||
** The caller might have already read an initial dot.
|
||||
*/
|
||||
static int read_numeral (LexState *ls, SemInfo *seminfo) {
|
||||
TValue obj;
|
||||
const char *expo = "Ee";
|
||||
int first = ls->current;
|
||||
lua_assert(lisdigit(ls->current));
|
||||
save_and_next(ls);
|
||||
if (first == '0' && check_next2(ls, "xX")) /* hexadecimal? */
|
||||
expo = "Pp";
|
||||
for (;;) {
|
||||
if (check_next2(ls, expo)) /* exponent mark? */
|
||||
check_next2(ls, "-+"); /* optional exponent sign */
|
||||
else if (lisxdigit(ls->current) || ls->current == '.') /* '%x|%.' */
|
||||
save_and_next(ls);
|
||||
else break;
|
||||
}
|
||||
if (lislalpha(ls->current)) /* is numeral touching a letter? */
|
||||
save_and_next(ls); /* force an error */
|
||||
save(ls, '\0');
|
||||
if (luaO_str2num(luaZ_buffer(ls->buff), &obj) == 0) /* format error? */
|
||||
lexerror(ls, "malformed number", TK_FLT);
|
||||
if (ttisinteger(&obj)) {
|
||||
seminfo->i = ivalue(&obj);
|
||||
return TK_INT;
|
||||
}
|
||||
else {
|
||||
lua_assert(ttisfloat(&obj));
|
||||
seminfo->r = fltvalue(&obj);
|
||||
return TK_FLT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** read a sequence '[=*[' or ']=*]', leaving the last bracket. If
|
||||
** sequence is well formed, return its number of '='s + 2; otherwise,
|
||||
** return 1 if it is a single bracket (no '='s and no 2nd bracket);
|
||||
** otherwise (an unfinished '[==...') return 0.
|
||||
*/
|
||||
static size_t skip_sep (LexState *ls) {
|
||||
size_t count = 0;
|
||||
int s = ls->current;
|
||||
lua_assert(s == '[' || s == ']');
|
||||
save_and_next(ls);
|
||||
while (ls->current == '=') {
|
||||
save_and_next(ls);
|
||||
count++;
|
||||
}
|
||||
return (ls->current == s) ? count + 2
|
||||
: (count == 0) ? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
||||
static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) {
|
||||
int line = ls->linenumber; /* initial line (for error message) */
|
||||
save_and_next(ls); /* skip 2nd '[' */
|
||||
if (currIsNewline(ls)) /* string starts with a newline? */
|
||||
inclinenumber(ls); /* skip it */
|
||||
for (;;) {
|
||||
switch (ls->current) {
|
||||
case EOZ: { /* error */
|
||||
const char *what = (seminfo ? "string" : "comment");
|
||||
const char *msg = luaO_pushfstring(ls->L,
|
||||
"unfinished long %s (starting at line %d)", what, line);
|
||||
lexerror(ls, msg, TK_EOS);
|
||||
break; /* to avoid warnings */
|
||||
}
|
||||
case ']': {
|
||||
if (skip_sep(ls) == sep) {
|
||||
save_and_next(ls); /* skip 2nd ']' */
|
||||
goto endloop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\n': case '\r': {
|
||||
save(ls, '\n');
|
||||
inclinenumber(ls);
|
||||
if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (seminfo) save_and_next(ls);
|
||||
else next(ls);
|
||||
}
|
||||
}
|
||||
} endloop:
|
||||
if (seminfo)
|
||||
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep,
|
||||
luaZ_bufflen(ls->buff) - 2 * sep);
|
||||
}
|
||||
|
||||
|
||||
static void esccheck (LexState *ls, int c, const char *msg) {
|
||||
if (!c) {
|
||||
if (ls->current != EOZ)
|
||||
save_and_next(ls); /* add current to buffer for error message */
|
||||
lexerror(ls, msg, TK_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int gethexa (LexState *ls) {
|
||||
save_and_next(ls);
|
||||
esccheck (ls, lisxdigit(ls->current), "hexadecimal digit expected");
|
||||
return luaO_hexavalue(ls->current);
|
||||
}
|
||||
|
||||
|
||||
static int readhexaesc (LexState *ls) {
|
||||
int r = gethexa(ls);
|
||||
r = (r << 4) + gethexa(ls);
|
||||
luaZ_buffremove(ls->buff, 2); /* remove saved chars from buffer */
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static unsigned long readutf8esc (LexState *ls) {
|
||||
unsigned long r;
|
||||
int i = 4; /* chars to be removed: '\', 'u', '{', and first digit */
|
||||
save_and_next(ls); /* skip 'u' */
|
||||
esccheck(ls, ls->current == '{', "missing '{'");
|
||||
r = gethexa(ls); /* must have at least one digit */
|
||||
while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) {
|
||||
i++;
|
||||
esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large");
|
||||
r = (r << 4) + luaO_hexavalue(ls->current);
|
||||
}
|
||||
esccheck(ls, ls->current == '}', "missing '}'");
|
||||
next(ls); /* skip '}' */
|
||||
luaZ_buffremove(ls->buff, i); /* remove saved chars from buffer */
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static void utf8esc (LexState *ls) {
|
||||
char buff[UTF8BUFFSZ];
|
||||
int n = luaO_utf8esc(buff, readutf8esc(ls));
|
||||
for (; n > 0; n--) /* add 'buff' to string */
|
||||
save(ls, buff[UTF8BUFFSZ - n]);
|
||||
}
|
||||
|
||||
|
||||
static int readdecesc (LexState *ls) {
|
||||
int i;
|
||||
int r = 0; /* result accumulator */
|
||||
for (i = 0; i < 3 && lisdigit(ls->current); i++) { /* read up to 3 digits */
|
||||
r = 10*r + ls->current - '0';
|
||||
save_and_next(ls);
|
||||
}
|
||||
esccheck(ls, r <= UCHAR_MAX, "decimal escape too large");
|
||||
luaZ_buffremove(ls->buff, i); /* remove read digits from buffer */
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static void read_string (LexState *ls, int del, SemInfo *seminfo) {
|
||||
save_and_next(ls); /* keep delimiter (for error messages) */
|
||||
while (ls->current != del) {
|
||||
switch (ls->current) {
|
||||
case EOZ:
|
||||
lexerror(ls, "unfinished string", TK_EOS);
|
||||
break; /* to avoid warnings */
|
||||
case '\n':
|
||||
case '\r':
|
||||
lexerror(ls, "unfinished string", TK_STRING);
|
||||
break; /* to avoid warnings */
|
||||
case '\\': { /* escape sequences */
|
||||
int c; /* final character to be saved */
|
||||
save_and_next(ls); /* keep '\\' for error messages */
|
||||
switch (ls->current) {
|
||||
case 'a': c = '\a'; goto read_save;
|
||||
case 'b': c = '\b'; goto read_save;
|
||||
case 'f': c = '\f'; goto read_save;
|
||||
case 'n': c = '\n'; goto read_save;
|
||||
case 'r': c = '\r'; goto read_save;
|
||||
case 't': c = '\t'; goto read_save;
|
||||
case 'v': c = '\v'; goto read_save;
|
||||
case 'x': c = readhexaesc(ls); goto read_save;
|
||||
case 'u': utf8esc(ls); goto no_save;
|
||||
case '\n': case '\r':
|
||||
inclinenumber(ls); c = '\n'; goto only_save;
|
||||
case '\\': case '\"': case '\'':
|
||||
c = ls->current; goto read_save;
|
||||
case EOZ: goto no_save; /* will raise an error next loop */
|
||||
case 'z': { /* zap following span of spaces */
|
||||
luaZ_buffremove(ls->buff, 1); /* remove '\\' */
|
||||
next(ls); /* skip the 'z' */
|
||||
while (lisspace(ls->current)) {
|
||||
if (currIsNewline(ls)) inclinenumber(ls);
|
||||
else next(ls);
|
||||
}
|
||||
goto no_save;
|
||||
}
|
||||
default: {
|
||||
esccheck(ls, lisdigit(ls->current), "invalid escape sequence");
|
||||
c = readdecesc(ls); /* digital escape '\ddd' */
|
||||
goto only_save;
|
||||
}
|
||||
}
|
||||
read_save:
|
||||
next(ls);
|
||||
/* go through */
|
||||
only_save:
|
||||
luaZ_buffremove(ls->buff, 1); /* remove '\\' */
|
||||
save(ls, c);
|
||||
/* go through */
|
||||
no_save: break;
|
||||
}
|
||||
default:
|
||||
save_and_next(ls);
|
||||
}
|
||||
}
|
||||
save_and_next(ls); /* skip delimiter */
|
||||
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1,
|
||||
luaZ_bufflen(ls->buff) - 2);
|
||||
}
|
||||
|
||||
|
||||
static int llex (LexState *ls, SemInfo *seminfo) {
|
||||
luaZ_resetbuffer(ls->buff);
|
||||
for (;;) {
|
||||
switch (ls->current) {
|
||||
case '\n': case '\r': { /* line breaks */
|
||||
inclinenumber(ls);
|
||||
break;
|
||||
}
|
||||
case ' ': case '\f': case '\t': case '\v': { /* spaces */
|
||||
next(ls);
|
||||
break;
|
||||
}
|
||||
case '-': { /* '-' or '--' (comment) */
|
||||
next(ls);
|
||||
if (ls->current != '-') return '-';
|
||||
/* else is a comment */
|
||||
next(ls);
|
||||
if (ls->current == '[') { /* long comment? */
|
||||
size_t sep = skip_sep(ls);
|
||||
luaZ_resetbuffer(ls->buff); /* 'skip_sep' may dirty the buffer */
|
||||
if (sep >= 2) {
|
||||
read_long_string(ls, NULL, sep); /* skip long comment */
|
||||
luaZ_resetbuffer(ls->buff); /* previous call may dirty the buff. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* else short comment */
|
||||
while (!currIsNewline(ls) && ls->current != EOZ)
|
||||
next(ls); /* skip until end of line (or end of file) */
|
||||
break;
|
||||
}
|
||||
case '[': { /* long string or simply '[' */
|
||||
size_t sep = skip_sep(ls);
|
||||
if (sep >= 2) {
|
||||
read_long_string(ls, seminfo, sep);
|
||||
return TK_STRING;
|
||||
}
|
||||
else if (sep == 0) /* '[=...' missing second bracket? */
|
||||
lexerror(ls, "invalid long string delimiter", TK_STRING);
|
||||
return '[';
|
||||
}
|
||||
case '=': {
|
||||
next(ls);
|
||||
if (check_next1(ls, '=')) return TK_EQ; /* '==' */
|
||||
else return '=';
|
||||
}
|
||||
case '<': {
|
||||
next(ls);
|
||||
if (check_next1(ls, '=')) return TK_LE; /* '<=' */
|
||||
else if (check_next1(ls, '<')) return TK_SHL; /* '<<' */
|
||||
else return '<';
|
||||
}
|
||||
case '>': {
|
||||
next(ls);
|
||||
if (check_next1(ls, '=')) return TK_GE; /* '>=' */
|
||||
else if (check_next1(ls, '>')) return TK_SHR; /* '>>' */
|
||||
else return '>';
|
||||
}
|
||||
case '/': {
|
||||
next(ls);
|
||||
if (check_next1(ls, '/')) return TK_IDIV; /* '//' */
|
||||
else return '/';
|
||||
}
|
||||
case '~': {
|
||||
next(ls);
|
||||
if (check_next1(ls, '=')) return TK_NE; /* '~=' */
|
||||
else return '~';
|
||||
}
|
||||
case ':': {
|
||||
next(ls);
|
||||
if (check_next1(ls, ':')) return TK_DBCOLON; /* '::' */
|
||||
else return ':';
|
||||
}
|
||||
case '"': case '\'': { /* short literal strings */
|
||||
read_string(ls, ls->current, seminfo);
|
||||
return TK_STRING;
|
||||
}
|
||||
case '.': { /* '.', '..', '...', or number */
|
||||
save_and_next(ls);
|
||||
if (check_next1(ls, '.')) {
|
||||
if (check_next1(ls, '.'))
|
||||
return TK_DOTS; /* '...' */
|
||||
else return TK_CONCAT; /* '..' */
|
||||
}
|
||||
else if (!lisdigit(ls->current)) return '.';
|
||||
else return read_numeral(ls, seminfo);
|
||||
}
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9': {
|
||||
return read_numeral(ls, seminfo);
|
||||
}
|
||||
case EOZ: {
|
||||
return TK_EOS;
|
||||
}
|
||||
default: {
|
||||
if (lislalpha(ls->current)) { /* identifier or reserved word? */
|
||||
TString *ts;
|
||||
do {
|
||||
save_and_next(ls);
|
||||
} while (lislalnum(ls->current));
|
||||
ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
|
||||
luaZ_bufflen(ls->buff));
|
||||
seminfo->ts = ts;
|
||||
if (isreserved(ts)) /* reserved word? */
|
||||
return ts->extra - 1 + FIRST_RESERVED;
|
||||
else {
|
||||
return TK_NAME;
|
||||
}
|
||||
}
|
||||
else { /* single-char tokens ('+', '*', '%', '{', '}', ...) */
|
||||
int c = ls->current;
|
||||
next(ls);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void luaX_next (LexState *ls) {
|
||||
ls->lastline = ls->linenumber;
|
||||
if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */
|
||||
ls->t = ls->lookahead; /* use this one */
|
||||
ls->lookahead.token = TK_EOS; /* and discharge it */
|
||||
}
|
||||
else
|
||||
ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */
|
||||
}
|
||||
|
||||
|
||||
int luaX_lookahead (LexState *ls) {
|
||||
lua_assert(ls->lookahead.token == TK_EOS);
|
||||
ls->lookahead.token = llex(ls, &ls->lookahead.seminfo);
|
||||
return ls->lookahead.token;
|
||||
}
|
||||
|
||||
91
crates/voltex_script/lua/llex.h
Normal file
91
crates/voltex_script/lua/llex.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
** $Id: llex.h $
|
||||
** Lexical Analyzer
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef llex_h
|
||||
#define llex_h
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "lobject.h"
|
||||
#include "lzio.h"
|
||||
|
||||
|
||||
/*
|
||||
** Single-char tokens (terminal symbols) are represented by their own
|
||||
** numeric code. Other tokens start at the following value.
|
||||
*/
|
||||
#define FIRST_RESERVED (UCHAR_MAX + 1)
|
||||
|
||||
|
||||
#if !defined(LUA_ENV)
|
||||
#define LUA_ENV "_ENV"
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* WARNING: if you change the order of this enumeration,
|
||||
* grep "ORDER RESERVED"
|
||||
*/
|
||||
enum RESERVED {
|
||||
/* terminal symbols denoted by reserved words */
|
||||
TK_AND = FIRST_RESERVED, TK_BREAK,
|
||||
TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,
|
||||
TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
|
||||
TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,
|
||||
/* other terminal symbols */
|
||||
TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE,
|
||||
TK_SHL, TK_SHR,
|
||||
TK_DBCOLON, TK_EOS,
|
||||
TK_FLT, TK_INT, TK_NAME, TK_STRING
|
||||
};
|
||||
|
||||
/* number of reserved words */
|
||||
#define NUM_RESERVED (cast_int(TK_WHILE-FIRST_RESERVED + 1))
|
||||
|
||||
|
||||
typedef union {
|
||||
lua_Number r;
|
||||
lua_Integer i;
|
||||
TString *ts;
|
||||
} SemInfo; /* semantics information */
|
||||
|
||||
|
||||
typedef struct Token {
|
||||
int token;
|
||||
SemInfo seminfo;
|
||||
} Token;
|
||||
|
||||
|
||||
/* state of the lexer plus state of the parser when shared by all
|
||||
functions */
|
||||
typedef struct LexState {
|
||||
int current; /* current character (charint) */
|
||||
int linenumber; /* input line counter */
|
||||
int lastline; /* line of last token 'consumed' */
|
||||
Token t; /* current token */
|
||||
Token lookahead; /* look ahead token */
|
||||
struct FuncState *fs; /* current function (parser) */
|
||||
struct lua_State *L;
|
||||
ZIO *z; /* input stream */
|
||||
Mbuffer *buff; /* buffer for tokens */
|
||||
Table *h; /* to avoid collection/reuse strings */
|
||||
struct Dyndata *dyd; /* dynamic structures used by the parser */
|
||||
TString *source; /* current source name */
|
||||
TString *envn; /* environment variable name */
|
||||
} LexState;
|
||||
|
||||
|
||||
LUAI_FUNC void luaX_init (lua_State *L);
|
||||
LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z,
|
||||
TString *source, int firstchar);
|
||||
LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l);
|
||||
LUAI_FUNC void luaX_next (LexState *ls);
|
||||
LUAI_FUNC int luaX_lookahead (LexState *ls);
|
||||
LUAI_FUNC l_noret luaX_syntaxerror (LexState *ls, const char *s);
|
||||
LUAI_FUNC const char *luaX_token2str (LexState *ls, int token);
|
||||
|
||||
|
||||
#endif
|
||||
380
crates/voltex_script/lua/llimits.h
Normal file
380
crates/voltex_script/lua/llimits.h
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
** $Id: llimits.h $
|
||||
** Limits, basic types, and some other 'installation-dependent' definitions
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef llimits_h
|
||||
#define llimits_h
|
||||
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
/*
|
||||
** 'lu_mem' and 'l_mem' are unsigned/signed integers big enough to count
|
||||
** the total memory used by Lua (in bytes). Usually, 'size_t' and
|
||||
** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines.
|
||||
*/
|
||||
#if defined(LUAI_MEM) /* { external definitions? */
|
||||
typedef LUAI_UMEM lu_mem;
|
||||
typedef LUAI_MEM l_mem;
|
||||
#elif LUAI_IS32INT /* }{ */
|
||||
typedef size_t lu_mem;
|
||||
typedef ptrdiff_t l_mem;
|
||||
#else /* 16-bit ints */ /* }{ */
|
||||
typedef unsigned long lu_mem;
|
||||
typedef long l_mem;
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/* chars used as small naturals (so that 'char' is reserved for characters) */
|
||||
typedef unsigned char lu_byte;
|
||||
typedef signed char ls_byte;
|
||||
|
||||
|
||||
/* maximum value for size_t */
|
||||
#define MAX_SIZET ((size_t)(~(size_t)0))
|
||||
|
||||
/* maximum size visible for Lua (must be representable in a lua_Integer) */
|
||||
#define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \
|
||||
: (size_t)(LUA_MAXINTEGER))
|
||||
|
||||
|
||||
#define MAX_LUMEM ((lu_mem)(~(lu_mem)0))
|
||||
|
||||
#define MAX_LMEM ((l_mem)(MAX_LUMEM >> 1))
|
||||
|
||||
|
||||
#define MAX_INT INT_MAX /* maximum value of an int */
|
||||
|
||||
|
||||
/*
|
||||
** floor of the log2 of the maximum signed value for integral type 't'.
|
||||
** (That is, maximum 'n' such that '2^n' fits in the given signed type.)
|
||||
*/
|
||||
#define log2maxs(t) (sizeof(t) * 8 - 2)
|
||||
|
||||
|
||||
/*
|
||||
** test whether an unsigned value is a power of 2 (or zero)
|
||||
*/
|
||||
#define ispow2(x) (((x) & ((x) - 1)) == 0)
|
||||
|
||||
|
||||
/* number of chars of a literal string without the ending \0 */
|
||||
#define LL(x) (sizeof(x)/sizeof(char) - 1)
|
||||
|
||||
|
||||
/*
|
||||
** conversion of pointer to unsigned integer: this is for hashing only;
|
||||
** there is no problem if the integer cannot hold the whole pointer
|
||||
** value. (In strict ISO C this may cause undefined behavior, but no
|
||||
** actual machine seems to bother.)
|
||||
*/
|
||||
#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \
|
||||
__STDC_VERSION__ >= 199901L
|
||||
#include <stdint.h>
|
||||
#if defined(UINTPTR_MAX) /* even in C99 this type is optional */
|
||||
#define L_P2I uintptr_t
|
||||
#else /* no 'intptr'? */
|
||||
#define L_P2I uintmax_t /* use the largest available integer */
|
||||
#endif
|
||||
#else /* C89 option */
|
||||
#define L_P2I size_t
|
||||
#endif
|
||||
|
||||
#define point2uint(p) ((unsigned int)((L_P2I)(p) & UINT_MAX))
|
||||
|
||||
|
||||
|
||||
/* types of 'usual argument conversions' for lua_Number and lua_Integer */
|
||||
typedef LUAI_UACNUMBER l_uacNumber;
|
||||
typedef LUAI_UACINT l_uacInt;
|
||||
|
||||
|
||||
/*
|
||||
** Internal assertions for in-house debugging
|
||||
*/
|
||||
#if defined LUAI_ASSERT
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#define lua_assert(c) assert(c)
|
||||
#endif
|
||||
|
||||
#if defined(lua_assert)
|
||||
#define check_exp(c,e) (lua_assert(c), (e))
|
||||
/* to avoid problems with conditions too long */
|
||||
#define lua_longassert(c) ((c) ? (void)0 : lua_assert(0))
|
||||
#else
|
||||
#define lua_assert(c) ((void)0)
|
||||
#define check_exp(c,e) (e)
|
||||
#define lua_longassert(c) ((void)0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** assertion for checking API calls
|
||||
*/
|
||||
#if !defined(luai_apicheck)
|
||||
#define luai_apicheck(l,e) ((void)l, lua_assert(e))
|
||||
#endif
|
||||
|
||||
#define api_check(l,e,msg) luai_apicheck(l,(e) && msg)
|
||||
|
||||
|
||||
/* macro to avoid warnings about unused variables */
|
||||
#if !defined(UNUSED)
|
||||
#define UNUSED(x) ((void)(x))
|
||||
#endif
|
||||
|
||||
|
||||
/* type casts (a macro highlights casts in the code) */
|
||||
#define cast(t, exp) ((t)(exp))
|
||||
|
||||
#define cast_void(i) cast(void, (i))
|
||||
#define cast_voidp(i) cast(void *, (i))
|
||||
#define cast_num(i) cast(lua_Number, (i))
|
||||
#define cast_int(i) cast(int, (i))
|
||||
#define cast_uint(i) cast(unsigned int, (i))
|
||||
#define cast_byte(i) cast(lu_byte, (i))
|
||||
#define cast_uchar(i) cast(unsigned char, (i))
|
||||
#define cast_char(i) cast(char, (i))
|
||||
#define cast_charp(i) cast(char *, (i))
|
||||
#define cast_sizet(i) cast(size_t, (i))
|
||||
|
||||
|
||||
/* cast a signed lua_Integer to lua_Unsigned */
|
||||
#if !defined(l_castS2U)
|
||||
#define l_castS2U(i) ((lua_Unsigned)(i))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** cast a lua_Unsigned to a signed lua_Integer; this cast is
|
||||
** not strict ISO C, but two-complement architectures should
|
||||
** work fine.
|
||||
*/
|
||||
#if !defined(l_castU2S)
|
||||
#define l_castU2S(i) ((lua_Integer)(i))
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** non-return type
|
||||
*/
|
||||
#if !defined(l_noret)
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define l_noret void __attribute__((noreturn))
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1200
|
||||
#define l_noret void __declspec(noreturn)
|
||||
#else
|
||||
#define l_noret void
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Inline functions
|
||||
*/
|
||||
#if !defined(LUA_USE_C89)
|
||||
#define l_inline inline
|
||||
#elif defined(__GNUC__)
|
||||
#define l_inline __inline__
|
||||
#else
|
||||
#define l_inline /* empty */
|
||||
#endif
|
||||
|
||||
#define l_sinline static l_inline
|
||||
|
||||
|
||||
/*
|
||||
** type for virtual-machine instructions;
|
||||
** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
|
||||
*/
|
||||
#if LUAI_IS32INT
|
||||
typedef unsigned int l_uint32;
|
||||
#else
|
||||
typedef unsigned long l_uint32;
|
||||
#endif
|
||||
|
||||
typedef l_uint32 Instruction;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Maximum length for short strings, that is, strings that are
|
||||
** internalized. (Cannot be smaller than reserved words or tags for
|
||||
** metamethods, as these strings must be internalized;
|
||||
** #("function") = 8, #("__newindex") = 10.)
|
||||
*/
|
||||
#if !defined(LUAI_MAXSHORTLEN)
|
||||
#define LUAI_MAXSHORTLEN 40
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Initial size for the string table (must be power of 2).
|
||||
** The Lua core alone registers ~50 strings (reserved words +
|
||||
** metaevent keys + a few others). Libraries would typically add
|
||||
** a few dozens more.
|
||||
*/
|
||||
#if !defined(MINSTRTABSIZE)
|
||||
#define MINSTRTABSIZE 128
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Size of cache for strings in the API. 'N' is the number of
|
||||
** sets (better be a prime) and "M" is the size of each set (M == 1
|
||||
** makes a direct cache.)
|
||||
*/
|
||||
#if !defined(STRCACHE_N)
|
||||
#define STRCACHE_N 53
|
||||
#define STRCACHE_M 2
|
||||
#endif
|
||||
|
||||
|
||||
/* minimum size for string buffer */
|
||||
#if !defined(LUA_MINBUFFER)
|
||||
#define LUA_MINBUFFER 32
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Maximum depth for nested C calls, syntactical nested non-terminals,
|
||||
** and other features implemented through recursion in C. (Value must
|
||||
** fit in a 16-bit unsigned integer. It must also be compatible with
|
||||
** the size of the C stack.)
|
||||
*/
|
||||
#if !defined(LUAI_MAXCCALLS)
|
||||
#define LUAI_MAXCCALLS 200
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** macros that are executed whenever program enters the Lua core
|
||||
** ('lua_lock') and leaves the core ('lua_unlock')
|
||||
*/
|
||||
#if !defined(lua_lock)
|
||||
#define lua_lock(L) ((void) 0)
|
||||
#define lua_unlock(L) ((void) 0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** macro executed during Lua functions at points where the
|
||||
** function can yield.
|
||||
*/
|
||||
#if !defined(luai_threadyield)
|
||||
#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** these macros allow user-specific actions when a thread is
|
||||
** created/deleted/resumed/yielded.
|
||||
*/
|
||||
#if !defined(luai_userstateopen)
|
||||
#define luai_userstateopen(L) ((void)L)
|
||||
#endif
|
||||
|
||||
#if !defined(luai_userstateclose)
|
||||
#define luai_userstateclose(L) ((void)L)
|
||||
#endif
|
||||
|
||||
#if !defined(luai_userstatethread)
|
||||
#define luai_userstatethread(L,L1) ((void)L)
|
||||
#endif
|
||||
|
||||
#if !defined(luai_userstatefree)
|
||||
#define luai_userstatefree(L,L1) ((void)L)
|
||||
#endif
|
||||
|
||||
#if !defined(luai_userstateresume)
|
||||
#define luai_userstateresume(L,n) ((void)L)
|
||||
#endif
|
||||
|
||||
#if !defined(luai_userstateyield)
|
||||
#define luai_userstateyield(L,n) ((void)L)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** The luai_num* macros define the primitive operations over numbers.
|
||||
*/
|
||||
|
||||
/* floor division (defined as 'floor(a/b)') */
|
||||
#if !defined(luai_numidiv)
|
||||
#define luai_numidiv(L,a,b) ((void)L, l_floor(luai_numdiv(L,a,b)))
|
||||
#endif
|
||||
|
||||
/* float division */
|
||||
#if !defined(luai_numdiv)
|
||||
#define luai_numdiv(L,a,b) ((a)/(b))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** modulo: defined as 'a - floor(a/b)*b'; the direct computation
|
||||
** using this definition has several problems with rounding errors,
|
||||
** so it is better to use 'fmod'. 'fmod' gives the result of
|
||||
** 'a - trunc(a/b)*b', and therefore must be corrected when
|
||||
** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a
|
||||
** non-integer negative result: non-integer result is equivalent to
|
||||
** a non-zero remainder 'm'; negative result is equivalent to 'a' and
|
||||
** 'b' with different signs, or 'm' and 'b' with different signs
|
||||
** (as the result 'm' of 'fmod' has the same sign of 'a').
|
||||
*/
|
||||
#if !defined(luai_nummod)
|
||||
#define luai_nummod(L,a,b,m) \
|
||||
{ (void)L; (m) = l_mathop(fmod)(a,b); \
|
||||
if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); }
|
||||
#endif
|
||||
|
||||
/* exponentiation */
|
||||
#if !defined(luai_numpow)
|
||||
#define luai_numpow(L,a,b) \
|
||||
((void)L, (b == 2) ? (a)*(a) : l_mathop(pow)(a,b))
|
||||
#endif
|
||||
|
||||
/* the others are quite standard operations */
|
||||
#if !defined(luai_numadd)
|
||||
#define luai_numadd(L,a,b) ((a)+(b))
|
||||
#define luai_numsub(L,a,b) ((a)-(b))
|
||||
#define luai_nummul(L,a,b) ((a)*(b))
|
||||
#define luai_numunm(L,a) (-(a))
|
||||
#define luai_numeq(a,b) ((a)==(b))
|
||||
#define luai_numlt(a,b) ((a)<(b))
|
||||
#define luai_numle(a,b) ((a)<=(b))
|
||||
#define luai_numgt(a,b) ((a)>(b))
|
||||
#define luai_numge(a,b) ((a)>=(b))
|
||||
#define luai_numisnan(a) (!luai_numeq((a), (a)))
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** macro to control inclusion of some hard tests on stack reallocation
|
||||
*/
|
||||
#if !defined(HARDSTACKTESTS)
|
||||
#define condmovestack(L,pre,pos) ((void)0)
|
||||
#else
|
||||
/* realloc stack keeping its size */
|
||||
#define condmovestack(L,pre,pos) \
|
||||
{ int sz_ = stacksize(L); pre; luaD_reallocstack((L), sz_, 0); pos; }
|
||||
#endif
|
||||
|
||||
#if !defined(HARDMEMTESTS)
|
||||
#define condchangemem(L,pre,pos) ((void)0)
|
||||
#else
|
||||
#define condchangemem(L,pre,pos) \
|
||||
{ if (gcrunning(G(L))) { pre; luaC_fullgc(L, 0); pos; } }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
781
crates/voltex_script/lua/lmathlib.c
Normal file
781
crates/voltex_script/lua/lmathlib.c
Normal file
@@ -0,0 +1,781 @@
|
||||
/*
|
||||
** $Id: lmathlib.c $
|
||||
** Standard mathematical library
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lmathlib_c
|
||||
#define LUA_LIB
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <float.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
#undef PI
|
||||
#define PI (l_mathop(3.141592653589793238462643383279502884))
|
||||
|
||||
|
||||
static int math_abs (lua_State *L) {
|
||||
if (lua_isinteger(L, 1)) {
|
||||
lua_Integer n = lua_tointeger(L, 1);
|
||||
if (n < 0) n = (lua_Integer)(0u - (lua_Unsigned)n);
|
||||
lua_pushinteger(L, n);
|
||||
}
|
||||
else
|
||||
lua_pushnumber(L, l_mathop(fabs)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_sin (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_cos (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_tan (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_asin (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_acos (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(acos)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_atan (lua_State *L) {
|
||||
lua_Number y = luaL_checknumber(L, 1);
|
||||
lua_Number x = luaL_optnumber(L, 2, 1);
|
||||
lua_pushnumber(L, l_mathop(atan2)(y, x));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_toint (lua_State *L) {
|
||||
int valid;
|
||||
lua_Integer n = lua_tointegerx(L, 1, &valid);
|
||||
if (l_likely(valid))
|
||||
lua_pushinteger(L, n);
|
||||
else {
|
||||
luaL_checkany(L, 1);
|
||||
luaL_pushfail(L); /* value is not convertible to integer */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void pushnumint (lua_State *L, lua_Number d) {
|
||||
lua_Integer n;
|
||||
if (lua_numbertointeger(d, &n)) /* does 'd' fit in an integer? */
|
||||
lua_pushinteger(L, n); /* result is integer */
|
||||
else
|
||||
lua_pushnumber(L, d); /* result is float */
|
||||
}
|
||||
|
||||
|
||||
static int math_floor (lua_State *L) {
|
||||
if (lua_isinteger(L, 1))
|
||||
lua_settop(L, 1); /* integer is its own floor */
|
||||
else {
|
||||
lua_Number d = l_mathop(floor)(luaL_checknumber(L, 1));
|
||||
pushnumint(L, d);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_ceil (lua_State *L) {
|
||||
if (lua_isinteger(L, 1))
|
||||
lua_settop(L, 1); /* integer is its own ceil */
|
||||
else {
|
||||
lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1));
|
||||
pushnumint(L, d);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_fmod (lua_State *L) {
|
||||
if (lua_isinteger(L, 1) && lua_isinteger(L, 2)) {
|
||||
lua_Integer d = lua_tointeger(L, 2);
|
||||
if ((lua_Unsigned)d + 1u <= 1u) { /* special cases: -1 or 0 */
|
||||
luaL_argcheck(L, d != 0, 2, "zero");
|
||||
lua_pushinteger(L, 0); /* avoid overflow with 0x80000... / -1 */
|
||||
}
|
||||
else
|
||||
lua_pushinteger(L, lua_tointeger(L, 1) % d);
|
||||
}
|
||||
else
|
||||
lua_pushnumber(L, l_mathop(fmod)(luaL_checknumber(L, 1),
|
||||
luaL_checknumber(L, 2)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** next function does not use 'modf', avoiding problems with 'double*'
|
||||
** (which is not compatible with 'float*') when lua_Number is not
|
||||
** 'double'.
|
||||
*/
|
||||
static int math_modf (lua_State *L) {
|
||||
if (lua_isinteger(L ,1)) {
|
||||
lua_settop(L, 1); /* number is its own integer part */
|
||||
lua_pushnumber(L, 0); /* no fractional part */
|
||||
}
|
||||
else {
|
||||
lua_Number n = luaL_checknumber(L, 1);
|
||||
/* integer part (rounds toward zero) */
|
||||
lua_Number ip = (n < 0) ? l_mathop(ceil)(n) : l_mathop(floor)(n);
|
||||
pushnumint(L, ip);
|
||||
/* fractional part (test needed for inf/-inf) */
|
||||
lua_pushnumber(L, (n == ip) ? l_mathop(0.0) : (n - ip));
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
static int math_sqrt (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(sqrt)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_ult (lua_State *L) {
|
||||
lua_Integer a = luaL_checkinteger(L, 1);
|
||||
lua_Integer b = luaL_checkinteger(L, 2);
|
||||
lua_pushboolean(L, (lua_Unsigned)a < (lua_Unsigned)b);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_log (lua_State *L) {
|
||||
lua_Number x = luaL_checknumber(L, 1);
|
||||
lua_Number res;
|
||||
if (lua_isnoneornil(L, 2))
|
||||
res = l_mathop(log)(x);
|
||||
else {
|
||||
lua_Number base = luaL_checknumber(L, 2);
|
||||
#if !defined(LUA_USE_C89)
|
||||
if (base == l_mathop(2.0))
|
||||
res = l_mathop(log2)(x);
|
||||
else
|
||||
#endif
|
||||
if (base == l_mathop(10.0))
|
||||
res = l_mathop(log10)(x);
|
||||
else
|
||||
res = l_mathop(log)(x)/l_mathop(log)(base);
|
||||
}
|
||||
lua_pushnumber(L, res);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_exp (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_deg (lua_State *L) {
|
||||
lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_rad (lua_State *L) {
|
||||
lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_min (lua_State *L) {
|
||||
int n = lua_gettop(L); /* number of arguments */
|
||||
int imin = 1; /* index of current minimum value */
|
||||
int i;
|
||||
luaL_argcheck(L, n >= 1, 1, "value expected");
|
||||
for (i = 2; i <= n; i++) {
|
||||
if (lua_compare(L, i, imin, LUA_OPLT))
|
||||
imin = i;
|
||||
}
|
||||
lua_pushvalue(L, imin);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_max (lua_State *L) {
|
||||
int n = lua_gettop(L); /* number of arguments */
|
||||
int imax = 1; /* index of current maximum value */
|
||||
int i;
|
||||
luaL_argcheck(L, n >= 1, 1, "value expected");
|
||||
for (i = 2; i <= n; i++) {
|
||||
if (lua_compare(L, imax, i, LUA_OPLT))
|
||||
imax = i;
|
||||
}
|
||||
lua_pushvalue(L, imax);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int math_type (lua_State *L) {
|
||||
if (lua_type(L, 1) == LUA_TNUMBER)
|
||||
lua_pushstring(L, (lua_isinteger(L, 1)) ? "integer" : "float");
|
||||
else {
|
||||
luaL_checkany(L, 1);
|
||||
luaL_pushfail(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Pseudo-Random Number Generator based on 'xoshiro256**'.
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
** This code uses lots of shifts. ANSI C does not allow shifts greater
|
||||
** than or equal to the width of the type being shifted, so some shifts
|
||||
** are written in convoluted ways to match that restriction. For
|
||||
** preprocessor tests, it assumes a width of 32 bits, so the maximum
|
||||
** shift there is 31 bits.
|
||||
*/
|
||||
|
||||
|
||||
/* number of binary digits in the mantissa of a float */
|
||||
#define FIGS l_floatatt(MANT_DIG)
|
||||
|
||||
#if FIGS > 64
|
||||
/* there are only 64 random bits; use them all */
|
||||
#undef FIGS
|
||||
#define FIGS 64
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** LUA_RAND32 forces the use of 32-bit integers in the implementation
|
||||
** of the PRN generator (mainly for testing).
|
||||
*/
|
||||
#if !defined(LUA_RAND32) && !defined(Rand64)
|
||||
|
||||
/* try to find an integer type with at least 64 bits */
|
||||
|
||||
#if ((ULONG_MAX >> 31) >> 31) >= 3
|
||||
|
||||
/* 'long' has at least 64 bits */
|
||||
#define Rand64 unsigned long
|
||||
#define SRand64 long
|
||||
|
||||
#elif !defined(LUA_USE_C89) && defined(LLONG_MAX)
|
||||
|
||||
/* there is a 'long long' type (which must have at least 64 bits) */
|
||||
#define Rand64 unsigned long long
|
||||
#define SRand64 long long
|
||||
|
||||
#elif ((LUA_MAXUNSIGNED >> 31) >> 31) >= 3
|
||||
|
||||
/* 'lua_Unsigned' has at least 64 bits */
|
||||
#define Rand64 lua_Unsigned
|
||||
#define SRand64 lua_Integer
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(Rand64) /* { */
|
||||
|
||||
/*
|
||||
** Standard implementation, using 64-bit integers.
|
||||
** If 'Rand64' has more than 64 bits, the extra bits do not interfere
|
||||
** with the 64 initial bits, except in a right shift. Moreover, the
|
||||
** final result has to discard the extra bits.
|
||||
*/
|
||||
|
||||
/* avoid using extra bits when needed */
|
||||
#define trim64(x) ((x) & 0xffffffffffffffffu)
|
||||
|
||||
|
||||
/* rotate left 'x' by 'n' bits */
|
||||
static Rand64 rotl (Rand64 x, int n) {
|
||||
return (x << n) | (trim64(x) >> (64 - n));
|
||||
}
|
||||
|
||||
static Rand64 nextrand (Rand64 *state) {
|
||||
Rand64 state0 = state[0];
|
||||
Rand64 state1 = state[1];
|
||||
Rand64 state2 = state[2] ^ state0;
|
||||
Rand64 state3 = state[3] ^ state1;
|
||||
Rand64 res = rotl(state1 * 5, 7) * 9;
|
||||
state[0] = state0 ^ state3;
|
||||
state[1] = state1 ^ state2;
|
||||
state[2] = state2 ^ (state1 << 17);
|
||||
state[3] = rotl(state3, 45);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Convert bits from a random integer into a float in the
|
||||
** interval [0,1), getting the higher FIG bits from the
|
||||
** random unsigned integer and converting that to a float.
|
||||
** Some old Microsoft compilers cannot cast an unsigned long
|
||||
** to a floating-point number, so we use a signed long as an
|
||||
** intermediary. When lua_Number is float or double, the shift ensures
|
||||
** that 'sx' is non negative; in that case, a good compiler will remove
|
||||
** the correction.
|
||||
*/
|
||||
|
||||
/* must throw out the extra (64 - FIGS) bits */
|
||||
#define shift64_FIG (64 - FIGS)
|
||||
|
||||
/* 2^(-FIGS) == 2^-1 / 2^(FIGS-1) */
|
||||
#define scaleFIG (l_mathop(0.5) / ((Rand64)1 << (FIGS - 1)))
|
||||
|
||||
static lua_Number I2d (Rand64 x) {
|
||||
SRand64 sx = (SRand64)(trim64(x) >> shift64_FIG);
|
||||
lua_Number res = (lua_Number)(sx) * scaleFIG;
|
||||
if (sx < 0)
|
||||
res += l_mathop(1.0); /* correct the two's complement if negative */
|
||||
lua_assert(0 <= res && res < 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* convert a 'Rand64' to a 'lua_Unsigned' */
|
||||
#define I2UInt(x) ((lua_Unsigned)trim64(x))
|
||||
|
||||
/* convert a 'lua_Unsigned' to a 'Rand64' */
|
||||
#define Int2I(x) ((Rand64)(x))
|
||||
|
||||
|
||||
#else /* no 'Rand64' }{ */
|
||||
|
||||
/* get an integer with at least 32 bits */
|
||||
#if LUAI_IS32INT
|
||||
typedef unsigned int lu_int32;
|
||||
#else
|
||||
typedef unsigned long lu_int32;
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** Use two 32-bit integers to represent a 64-bit quantity.
|
||||
*/
|
||||
typedef struct Rand64 {
|
||||
lu_int32 h; /* higher half */
|
||||
lu_int32 l; /* lower half */
|
||||
} Rand64;
|
||||
|
||||
|
||||
/*
|
||||
** If 'lu_int32' has more than 32 bits, the extra bits do not interfere
|
||||
** with the 32 initial bits, except in a right shift and comparisons.
|
||||
** Moreover, the final result has to discard the extra bits.
|
||||
*/
|
||||
|
||||
/* avoid using extra bits when needed */
|
||||
#define trim32(x) ((x) & 0xffffffffu)
|
||||
|
||||
|
||||
/*
|
||||
** basic operations on 'Rand64' values
|
||||
*/
|
||||
|
||||
/* build a new Rand64 value */
|
||||
static Rand64 packI (lu_int32 h, lu_int32 l) {
|
||||
Rand64 result;
|
||||
result.h = h;
|
||||
result.l = l;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* return i << n */
|
||||
static Rand64 Ishl (Rand64 i, int n) {
|
||||
lua_assert(n > 0 && n < 32);
|
||||
return packI((i.h << n) | (trim32(i.l) >> (32 - n)), i.l << n);
|
||||
}
|
||||
|
||||
/* i1 ^= i2 */
|
||||
static void Ixor (Rand64 *i1, Rand64 i2) {
|
||||
i1->h ^= i2.h;
|
||||
i1->l ^= i2.l;
|
||||
}
|
||||
|
||||
/* return i1 + i2 */
|
||||
static Rand64 Iadd (Rand64 i1, Rand64 i2) {
|
||||
Rand64 result = packI(i1.h + i2.h, i1.l + i2.l);
|
||||
if (trim32(result.l) < trim32(i1.l)) /* carry? */
|
||||
result.h++;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* return i * 5 */
|
||||
static Rand64 times5 (Rand64 i) {
|
||||
return Iadd(Ishl(i, 2), i); /* i * 5 == (i << 2) + i */
|
||||
}
|
||||
|
||||
/* return i * 9 */
|
||||
static Rand64 times9 (Rand64 i) {
|
||||
return Iadd(Ishl(i, 3), i); /* i * 9 == (i << 3) + i */
|
||||
}
|
||||
|
||||
/* return 'i' rotated left 'n' bits */
|
||||
static Rand64 rotl (Rand64 i, int n) {
|
||||
lua_assert(n > 0 && n < 32);
|
||||
return packI((i.h << n) | (trim32(i.l) >> (32 - n)),
|
||||
(trim32(i.h) >> (32 - n)) | (i.l << n));
|
||||
}
|
||||
|
||||
/* for offsets larger than 32, rotate right by 64 - offset */
|
||||
static Rand64 rotl1 (Rand64 i, int n) {
|
||||
lua_assert(n > 32 && n < 64);
|
||||
n = 64 - n;
|
||||
return packI((trim32(i.h) >> n) | (i.l << (32 - n)),
|
||||
(i.h << (32 - n)) | (trim32(i.l) >> n));
|
||||
}
|
||||
|
||||
/*
|
||||
** implementation of 'xoshiro256**' algorithm on 'Rand64' values
|
||||
*/
|
||||
static Rand64 nextrand (Rand64 *state) {
|
||||
Rand64 res = times9(rotl(times5(state[1]), 7));
|
||||
Rand64 t = Ishl(state[1], 17);
|
||||
Ixor(&state[2], state[0]);
|
||||
Ixor(&state[3], state[1]);
|
||||
Ixor(&state[1], state[2]);
|
||||
Ixor(&state[0], state[3]);
|
||||
Ixor(&state[2], t);
|
||||
state[3] = rotl1(state[3], 45);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Converts a 'Rand64' into a float.
|
||||
*/
|
||||
|
||||
/* an unsigned 1 with proper type */
|
||||
#define UONE ((lu_int32)1)
|
||||
|
||||
|
||||
#if FIGS <= 32
|
||||
|
||||
/* 2^(-FIGS) */
|
||||
#define scaleFIG (l_mathop(0.5) / (UONE << (FIGS - 1)))
|
||||
|
||||
/*
|
||||
** get up to 32 bits from higher half, shifting right to
|
||||
** throw out the extra bits.
|
||||
*/
|
||||
static lua_Number I2d (Rand64 x) {
|
||||
lua_Number h = (lua_Number)(trim32(x.h) >> (32 - FIGS));
|
||||
return h * scaleFIG;
|
||||
}
|
||||
|
||||
#else /* 32 < FIGS <= 64 */
|
||||
|
||||
/* 2^(-FIGS) = 1.0 / 2^30 / 2^3 / 2^(FIGS-33) */
|
||||
#define scaleFIG \
|
||||
(l_mathop(1.0) / (UONE << 30) / l_mathop(8.0) / (UONE << (FIGS - 33)))
|
||||
|
||||
/*
|
||||
** use FIGS - 32 bits from lower half, throwing out the other
|
||||
** (32 - (FIGS - 32)) = (64 - FIGS) bits
|
||||
*/
|
||||
#define shiftLOW (64 - FIGS)
|
||||
|
||||
/*
|
||||
** higher 32 bits go after those (FIGS - 32) bits: shiftHI = 2^(FIGS - 32)
|
||||
*/
|
||||
#define shiftHI ((lua_Number)(UONE << (FIGS - 33)) * l_mathop(2.0))
|
||||
|
||||
|
||||
static lua_Number I2d (Rand64 x) {
|
||||
lua_Number h = (lua_Number)trim32(x.h) * shiftHI;
|
||||
lua_Number l = (lua_Number)(trim32(x.l) >> shiftLOW);
|
||||
return (h + l) * scaleFIG;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* convert a 'Rand64' to a 'lua_Unsigned' */
|
||||
static lua_Unsigned I2UInt (Rand64 x) {
|
||||
return (((lua_Unsigned)trim32(x.h) << 31) << 1) | (lua_Unsigned)trim32(x.l);
|
||||
}
|
||||
|
||||
/* convert a 'lua_Unsigned' to a 'Rand64' */
|
||||
static Rand64 Int2I (lua_Unsigned n) {
|
||||
return packI((lu_int32)((n >> 31) >> 1), (lu_int32)n);
|
||||
}
|
||||
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/*
|
||||
** A state uses four 'Rand64' values.
|
||||
*/
|
||||
typedef struct {
|
||||
Rand64 s[4];
|
||||
} RanState;
|
||||
|
||||
|
||||
/*
|
||||
** Project the random integer 'ran' into the interval [0, n].
|
||||
** Because 'ran' has 2^B possible values, the projection can only be
|
||||
** uniform when the size of the interval is a power of 2 (exact
|
||||
** division). Otherwise, to get a uniform projection into [0, n], we
|
||||
** first compute 'lim', the smallest Mersenne number not smaller than
|
||||
** 'n'. We then project 'ran' into the interval [0, lim]. If the result
|
||||
** is inside [0, n], we are done. Otherwise, we try with another 'ran',
|
||||
** until we have a result inside the interval.
|
||||
*/
|
||||
static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n,
|
||||
RanState *state) {
|
||||
if ((n & (n + 1)) == 0) /* is 'n + 1' a power of 2? */
|
||||
return ran & n; /* no bias */
|
||||
else {
|
||||
lua_Unsigned lim = n;
|
||||
/* compute the smallest (2^b - 1) not smaller than 'n' */
|
||||
lim |= (lim >> 1);
|
||||
lim |= (lim >> 2);
|
||||
lim |= (lim >> 4);
|
||||
lim |= (lim >> 8);
|
||||
lim |= (lim >> 16);
|
||||
#if (LUA_MAXUNSIGNED >> 31) >= 3
|
||||
lim |= (lim >> 32); /* integer type has more than 32 bits */
|
||||
#endif
|
||||
lua_assert((lim & (lim + 1)) == 0 /* 'lim + 1' is a power of 2, */
|
||||
&& lim >= n /* not smaller than 'n', */
|
||||
&& (lim >> 1) < n); /* and it is the smallest one */
|
||||
while ((ran &= lim) > n) /* project 'ran' into [0..lim] */
|
||||
ran = I2UInt(nextrand(state->s)); /* not inside [0..n]? try again */
|
||||
return ran;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int math_random (lua_State *L) {
|
||||
lua_Integer low, up;
|
||||
lua_Unsigned p;
|
||||
RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));
|
||||
Rand64 rv = nextrand(state->s); /* next pseudo-random value */
|
||||
switch (lua_gettop(L)) { /* check number of arguments */
|
||||
case 0: { /* no arguments */
|
||||
lua_pushnumber(L, I2d(rv)); /* float between 0 and 1 */
|
||||
return 1;
|
||||
}
|
||||
case 1: { /* only upper limit */
|
||||
low = 1;
|
||||
up = luaL_checkinteger(L, 1);
|
||||
if (up == 0) { /* single 0 as argument? */
|
||||
lua_pushinteger(L, I2UInt(rv)); /* full random integer */
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: { /* lower and upper limits */
|
||||
low = luaL_checkinteger(L, 1);
|
||||
up = luaL_checkinteger(L, 2);
|
||||
break;
|
||||
}
|
||||
default: return luaL_error(L, "wrong number of arguments");
|
||||
}
|
||||
/* random integer in the interval [low, up] */
|
||||
luaL_argcheck(L, low <= up, 1, "interval is empty");
|
||||
/* project random integer into the interval [0, up - low] */
|
||||
p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state);
|
||||
lua_pushinteger(L, p + (lua_Unsigned)low);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void setseed (lua_State *L, Rand64 *state,
|
||||
lua_Unsigned n1, lua_Unsigned n2) {
|
||||
int i;
|
||||
state[0] = Int2I(n1);
|
||||
state[1] = Int2I(0xff); /* avoid a zero state */
|
||||
state[2] = Int2I(n2);
|
||||
state[3] = Int2I(0);
|
||||
for (i = 0; i < 16; i++)
|
||||
nextrand(state); /* discard initial values to "spread" seed */
|
||||
lua_pushinteger(L, n1);
|
||||
lua_pushinteger(L, n2);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Set a "random" seed. To get some randomness, use the current time
|
||||
** and the address of 'L' (in case the machine does address space layout
|
||||
** randomization).
|
||||
*/
|
||||
static void randseed (lua_State *L, RanState *state) {
|
||||
lua_Unsigned seed1 = (lua_Unsigned)time(NULL);
|
||||
lua_Unsigned seed2 = (lua_Unsigned)(size_t)L;
|
||||
setseed(L, state->s, seed1, seed2);
|
||||
}
|
||||
|
||||
|
||||
static int math_randomseed (lua_State *L) {
|
||||
RanState *state = (RanState *)lua_touserdata(L, lua_upvalueindex(1));
|
||||
if (lua_isnone(L, 1)) {
|
||||
randseed(L, state);
|
||||
}
|
||||
else {
|
||||
lua_Integer n1 = luaL_checkinteger(L, 1);
|
||||
lua_Integer n2 = luaL_optinteger(L, 2, 0);
|
||||
setseed(L, state->s, n1, n2);
|
||||
}
|
||||
return 2; /* return seeds */
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg randfuncs[] = {
|
||||
{"random", math_random},
|
||||
{"randomseed", math_randomseed},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** Register the random functions and initialize their state.
|
||||
*/
|
||||
static void setrandfunc (lua_State *L) {
|
||||
RanState *state = (RanState *)lua_newuserdatauv(L, sizeof(RanState), 0);
|
||||
randseed(L, state); /* initialize with a "random" seed */
|
||||
lua_pop(L, 2); /* remove pushed seeds */
|
||||
luaL_setfuncs(L, randfuncs, 1);
|
||||
}
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Deprecated functions (for compatibility only)
|
||||
** ===================================================================
|
||||
*/
|
||||
#if defined(LUA_COMPAT_MATHLIB)
|
||||
|
||||
static int math_cosh (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(cosh)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_sinh (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(sinh)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_tanh (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(tanh)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_pow (lua_State *L) {
|
||||
lua_Number x = luaL_checknumber(L, 1);
|
||||
lua_Number y = luaL_checknumber(L, 2);
|
||||
lua_pushnumber(L, l_mathop(pow)(x, y));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_frexp (lua_State *L) {
|
||||
int e;
|
||||
lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e));
|
||||
lua_pushinteger(L, e);
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int math_ldexp (lua_State *L) {
|
||||
lua_Number x = luaL_checknumber(L, 1);
|
||||
int ep = (int)luaL_checkinteger(L, 2);
|
||||
lua_pushnumber(L, l_mathop(ldexp)(x, ep));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int math_log10 (lua_State *L) {
|
||||
lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
|
||||
static const luaL_Reg mathlib[] = {
|
||||
{"abs", math_abs},
|
||||
{"acos", math_acos},
|
||||
{"asin", math_asin},
|
||||
{"atan", math_atan},
|
||||
{"ceil", math_ceil},
|
||||
{"cos", math_cos},
|
||||
{"deg", math_deg},
|
||||
{"exp", math_exp},
|
||||
{"tointeger", math_toint},
|
||||
{"floor", math_floor},
|
||||
{"fmod", math_fmod},
|
||||
{"ult", math_ult},
|
||||
{"log", math_log},
|
||||
{"max", math_max},
|
||||
{"min", math_min},
|
||||
{"modf", math_modf},
|
||||
{"rad", math_rad},
|
||||
{"sin", math_sin},
|
||||
{"sqrt", math_sqrt},
|
||||
{"tan", math_tan},
|
||||
{"type", math_type},
|
||||
#if defined(LUA_COMPAT_MATHLIB)
|
||||
{"atan2", math_atan},
|
||||
{"cosh", math_cosh},
|
||||
{"sinh", math_sinh},
|
||||
{"tanh", math_tanh},
|
||||
{"pow", math_pow},
|
||||
{"frexp", math_frexp},
|
||||
{"ldexp", math_ldexp},
|
||||
{"log10", math_log10},
|
||||
#endif
|
||||
/* placeholders */
|
||||
{"random", NULL},
|
||||
{"randomseed", NULL},
|
||||
{"pi", NULL},
|
||||
{"huge", NULL},
|
||||
{"maxinteger", NULL},
|
||||
{"mininteger", NULL},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
** Open math library
|
||||
*/
|
||||
LUAMOD_API int luaopen_math (lua_State *L) {
|
||||
luaL_newlib(L, mathlib);
|
||||
lua_pushnumber(L, PI);
|
||||
lua_setfield(L, -2, "pi");
|
||||
lua_pushnumber(L, (lua_Number)HUGE_VAL);
|
||||
lua_setfield(L, -2, "huge");
|
||||
lua_pushinteger(L, LUA_MAXINTEGER);
|
||||
lua_setfield(L, -2, "maxinteger");
|
||||
lua_pushinteger(L, LUA_MININTEGER);
|
||||
lua_setfield(L, -2, "mininteger");
|
||||
setrandfunc(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
215
crates/voltex_script/lua/lmem.c
Normal file
215
crates/voltex_script/lua/lmem.c
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
** $Id: lmem.c $
|
||||
** Interface to Memory Manager
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lmem_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "ldebug.h"
|
||||
#include "ldo.h"
|
||||
#include "lgc.h"
|
||||
#include "lmem.h"
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** About the realloc function:
|
||||
** void *frealloc (void *ud, void *ptr, size_t osize, size_t nsize);
|
||||
** ('osize' is the old size, 'nsize' is the new size)
|
||||
**
|
||||
** - frealloc(ud, p, x, 0) frees the block 'p' and returns NULL.
|
||||
** Particularly, frealloc(ud, NULL, 0, 0) does nothing,
|
||||
** which is equivalent to free(NULL) in ISO C.
|
||||
**
|
||||
** - frealloc(ud, NULL, x, s) creates a new block of size 's'
|
||||
** (no matter 'x'). Returns NULL if it cannot create the new block.
|
||||
**
|
||||
** - otherwise, frealloc(ud, b, x, y) reallocates the block 'b' from
|
||||
** size 'x' to size 'y'. Returns NULL if it cannot reallocate the
|
||||
** block to the new size.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** Macro to call the allocation function.
|
||||
*/
|
||||
#define callfrealloc(g,block,os,ns) ((*g->frealloc)(g->ud, block, os, ns))
|
||||
|
||||
|
||||
/*
|
||||
** When an allocation fails, it will try again after an emergency
|
||||
** collection, except when it cannot run a collection. The GC should
|
||||
** not be called while the state is not fully built, as the collector
|
||||
** is not yet fully initialized. Also, it should not be called when
|
||||
** 'gcstopem' is true, because then the interpreter is in the middle of
|
||||
** a collection step.
|
||||
*/
|
||||
#define cantryagain(g) (completestate(g) && !g->gcstopem)
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(EMERGENCYGCTESTS)
|
||||
/*
|
||||
** First allocation will fail except when freeing a block (frees never
|
||||
** fail) and when it cannot try again; this fail will trigger 'tryagain'
|
||||
** and a full GC cycle at every allocation.
|
||||
*/
|
||||
static void *firsttry (global_State *g, void *block, size_t os, size_t ns) {
|
||||
if (ns > 0 && cantryagain(g))
|
||||
return NULL; /* fail */
|
||||
else /* normal allocation */
|
||||
return callfrealloc(g, block, os, ns);
|
||||
}
|
||||
#else
|
||||
#define firsttry(g,block,os,ns) callfrealloc(g, block, os, ns)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Functions to allocate/deallocate arrays for the Parser
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
** Minimum size for arrays during parsing, to avoid overhead of
|
||||
** reallocating to size 1, then 2, and then 4. All these arrays
|
||||
** will be reallocated to exact sizes or erased when parsing ends.
|
||||
*/
|
||||
#define MINSIZEARRAY 4
|
||||
|
||||
|
||||
void *luaM_growaux_ (lua_State *L, void *block, int nelems, int *psize,
|
||||
int size_elems, int limit, const char *what) {
|
||||
void *newblock;
|
||||
int size = *psize;
|
||||
if (nelems + 1 <= size) /* does one extra element still fit? */
|
||||
return block; /* nothing to be done */
|
||||
if (size >= limit / 2) { /* cannot double it? */
|
||||
if (l_unlikely(size >= limit)) /* cannot grow even a little? */
|
||||
luaG_runerror(L, "too many %s (limit is %d)", what, limit);
|
||||
size = limit; /* still have at least one free place */
|
||||
}
|
||||
else {
|
||||
size *= 2;
|
||||
if (size < MINSIZEARRAY)
|
||||
size = MINSIZEARRAY; /* minimum size */
|
||||
}
|
||||
lua_assert(nelems + 1 <= size && size <= limit);
|
||||
/* 'limit' ensures that multiplication will not overflow */
|
||||
newblock = luaM_saferealloc_(L, block, cast_sizet(*psize) * size_elems,
|
||||
cast_sizet(size) * size_elems);
|
||||
*psize = size; /* update only when everything else is OK */
|
||||
return newblock;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** In prototypes, the size of the array is also its number of
|
||||
** elements (to save memory). So, if it cannot shrink an array
|
||||
** to its number of elements, the only option is to raise an
|
||||
** error.
|
||||
*/
|
||||
void *luaM_shrinkvector_ (lua_State *L, void *block, int *size,
|
||||
int final_n, int size_elem) {
|
||||
void *newblock;
|
||||
size_t oldsize = cast_sizet((*size) * size_elem);
|
||||
size_t newsize = cast_sizet(final_n * size_elem);
|
||||
lua_assert(newsize <= oldsize);
|
||||
newblock = luaM_saferealloc_(L, block, oldsize, newsize);
|
||||
*size = final_n;
|
||||
return newblock;
|
||||
}
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
l_noret luaM_toobig (lua_State *L) {
|
||||
luaG_runerror(L, "memory allocation error: block too big");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Free memory
|
||||
*/
|
||||
void luaM_free_ (lua_State *L, void *block, size_t osize) {
|
||||
global_State *g = G(L);
|
||||
lua_assert((osize == 0) == (block == NULL));
|
||||
callfrealloc(g, block, osize, 0);
|
||||
g->GCdebt -= osize;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** In case of allocation fail, this function will do an emergency
|
||||
** collection to free some memory and then try the allocation again.
|
||||
*/
|
||||
static void *tryagain (lua_State *L, void *block,
|
||||
size_t osize, size_t nsize) {
|
||||
global_State *g = G(L);
|
||||
if (cantryagain(g)) {
|
||||
luaC_fullgc(L, 1); /* try to free some memory... */
|
||||
return callfrealloc(g, block, osize, nsize); /* try again */
|
||||
}
|
||||
else return NULL; /* cannot run an emergency collection */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Generic allocation routine.
|
||||
*/
|
||||
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
|
||||
void *newblock;
|
||||
global_State *g = G(L);
|
||||
lua_assert((osize == 0) == (block == NULL));
|
||||
newblock = firsttry(g, block, osize, nsize);
|
||||
if (l_unlikely(newblock == NULL && nsize > 0)) {
|
||||
newblock = tryagain(L, block, osize, nsize);
|
||||
if (newblock == NULL) /* still no memory? */
|
||||
return NULL; /* do not update 'GCdebt' */
|
||||
}
|
||||
lua_assert((nsize == 0) == (newblock == NULL));
|
||||
g->GCdebt = (g->GCdebt + nsize) - osize;
|
||||
return newblock;
|
||||
}
|
||||
|
||||
|
||||
void *luaM_saferealloc_ (lua_State *L, void *block, size_t osize,
|
||||
size_t nsize) {
|
||||
void *newblock = luaM_realloc_(L, block, osize, nsize);
|
||||
if (l_unlikely(newblock == NULL && nsize > 0)) /* allocation failed? */
|
||||
luaM_error(L);
|
||||
return newblock;
|
||||
}
|
||||
|
||||
|
||||
void *luaM_malloc_ (lua_State *L, size_t size, int tag) {
|
||||
if (size == 0)
|
||||
return NULL; /* that's all */
|
||||
else {
|
||||
global_State *g = G(L);
|
||||
void *newblock = firsttry(g, NULL, tag, size);
|
||||
if (l_unlikely(newblock == NULL)) {
|
||||
newblock = tryagain(L, NULL, tag, size);
|
||||
if (newblock == NULL)
|
||||
luaM_error(L);
|
||||
}
|
||||
g->GCdebt += size;
|
||||
return newblock;
|
||||
}
|
||||
}
|
||||
93
crates/voltex_script/lua/lmem.h
Normal file
93
crates/voltex_script/lua/lmem.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
** $Id: lmem.h $
|
||||
** Interface to Memory Manager
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#ifndef lmem_h
|
||||
#define lmem_h
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "llimits.h"
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
#define luaM_error(L) luaD_throw(L, LUA_ERRMEM)
|
||||
|
||||
|
||||
/*
|
||||
** This macro tests whether it is safe to multiply 'n' by the size of
|
||||
** type 't' without overflows. Because 'e' is always constant, it avoids
|
||||
** the runtime division MAX_SIZET/(e).
|
||||
** (The macro is somewhat complex to avoid warnings: The 'sizeof'
|
||||
** comparison avoids a runtime comparison when overflow cannot occur.
|
||||
** The compiler should be able to optimize the real test by itself, but
|
||||
** when it does it, it may give a warning about "comparison is always
|
||||
** false due to limited range of data type"; the +1 tricks the compiler,
|
||||
** avoiding this warning but also this optimization.)
|
||||
*/
|
||||
#define luaM_testsize(n,e) \
|
||||
(sizeof(n) >= sizeof(size_t) && cast_sizet((n)) + 1 > MAX_SIZET/(e))
|
||||
|
||||
#define luaM_checksize(L,n,e) \
|
||||
(luaM_testsize(n,e) ? luaM_toobig(L) : cast_void(0))
|
||||
|
||||
|
||||
/*
|
||||
** Computes the minimum between 'n' and 'MAX_SIZET/sizeof(t)', so that
|
||||
** the result is not larger than 'n' and cannot overflow a 'size_t'
|
||||
** when multiplied by the size of type 't'. (Assumes that 'n' is an
|
||||
** 'int' or 'unsigned int' and that 'int' is not larger than 'size_t'.)
|
||||
*/
|
||||
#define luaM_limitN(n,t) \
|
||||
((cast_sizet(n) <= MAX_SIZET/sizeof(t)) ? (n) : \
|
||||
cast_uint((MAX_SIZET/sizeof(t))))
|
||||
|
||||
|
||||
/*
|
||||
** Arrays of chars do not need any test
|
||||
*/
|
||||
#define luaM_reallocvchar(L,b,on,n) \
|
||||
cast_charp(luaM_saferealloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char)))
|
||||
|
||||
#define luaM_freemem(L, b, s) luaM_free_(L, (b), (s))
|
||||
#define luaM_free(L, b) luaM_free_(L, (b), sizeof(*(b)))
|
||||
#define luaM_freearray(L, b, n) luaM_free_(L, (b), (n)*sizeof(*(b)))
|
||||
|
||||
#define luaM_new(L,t) cast(t*, luaM_malloc_(L, sizeof(t), 0))
|
||||
#define luaM_newvector(L,n,t) cast(t*, luaM_malloc_(L, (n)*sizeof(t), 0))
|
||||
#define luaM_newvectorchecked(L,n,t) \
|
||||
(luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t))
|
||||
|
||||
#define luaM_newobject(L,tag,s) luaM_malloc_(L, (s), tag)
|
||||
|
||||
#define luaM_growvector(L,v,nelems,size,t,limit,e) \
|
||||
((v)=cast(t *, luaM_growaux_(L,v,nelems,&(size),sizeof(t), \
|
||||
luaM_limitN(limit,t),e)))
|
||||
|
||||
#define luaM_reallocvector(L, v,oldn,n,t) \
|
||||
(cast(t *, luaM_realloc_(L, v, cast_sizet(oldn) * sizeof(t), \
|
||||
cast_sizet(n) * sizeof(t))))
|
||||
|
||||
#define luaM_shrinkvector(L,v,size,fs,t) \
|
||||
((v)=cast(t *, luaM_shrinkvector_(L, v, &(size), fs, sizeof(t))))
|
||||
|
||||
LUAI_FUNC l_noret luaM_toobig (lua_State *L);
|
||||
|
||||
/* not to be called directly */
|
||||
LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize,
|
||||
size_t size);
|
||||
LUAI_FUNC void *luaM_saferealloc_ (lua_State *L, void *block, size_t oldsize,
|
||||
size_t size);
|
||||
LUAI_FUNC void luaM_free_ (lua_State *L, void *block, size_t osize);
|
||||
LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int nelems,
|
||||
int *size, int size_elem, int limit,
|
||||
const char *what);
|
||||
LUAI_FUNC void *luaM_shrinkvector_ (lua_State *L, void *block, int *nelem,
|
||||
int final_n, int size_elem);
|
||||
LUAI_FUNC void *luaM_malloc_ (lua_State *L, size_t size, int tag);
|
||||
|
||||
#endif
|
||||
|
||||
758
crates/voltex_script/lua/loadlib.c
Normal file
758
crates/voltex_script/lua/loadlib.c
Normal file
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
** $Id: loadlib.c $
|
||||
** Dynamic library loader for Lua
|
||||
** See Copyright Notice in lua.h
|
||||
**
|
||||
** This module contains an implementation of loadlib for Unix systems
|
||||
** that have dlfcn, an implementation for Windows, and a stub for other
|
||||
** systems.
|
||||
*/
|
||||
|
||||
#define loadlib_c
|
||||
#define LUA_LIB
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
|
||||
/*
|
||||
** LUA_CSUBSEP is the character that replaces dots in submodule names
|
||||
** when searching for a C loader.
|
||||
** LUA_LSUBSEP is the character that replaces dots in submodule names
|
||||
** when searching for a Lua loader.
|
||||
*/
|
||||
#if !defined(LUA_CSUBSEP)
|
||||
#define LUA_CSUBSEP LUA_DIRSEP
|
||||
#endif
|
||||
|
||||
#if !defined(LUA_LSUBSEP)
|
||||
#define LUA_LSUBSEP LUA_DIRSEP
|
||||
#endif
|
||||
|
||||
|
||||
/* prefix for open functions in C libraries */
|
||||
#define LUA_POF "luaopen_"
|
||||
|
||||
/* separator for open functions in C libraries */
|
||||
#define LUA_OFSEP "_"
|
||||
|
||||
|
||||
/*
|
||||
** key for table in the registry that keeps handles
|
||||
** for all loaded C libraries
|
||||
*/
|
||||
static const char *const CLIBS = "_CLIBS";
|
||||
|
||||
#define LIB_FAIL "open"
|
||||
|
||||
|
||||
#define setprogdir(L) ((void)0)
|
||||
|
||||
|
||||
/*
|
||||
** Special type equivalent to '(void*)' for functions in gcc
|
||||
** (to suppress warnings when converting function pointers)
|
||||
*/
|
||||
typedef void (*voidf)(void);
|
||||
|
||||
|
||||
/*
|
||||
** system-dependent functions
|
||||
*/
|
||||
|
||||
/*
|
||||
** unload library 'lib'
|
||||
*/
|
||||
static void lsys_unloadlib (void *lib);
|
||||
|
||||
/*
|
||||
** load C library in file 'path'. If 'seeglb', load with all names in
|
||||
** the library global.
|
||||
** Returns the library; in case of error, returns NULL plus an
|
||||
** error string in the stack.
|
||||
*/
|
||||
static void *lsys_load (lua_State *L, const char *path, int seeglb);
|
||||
|
||||
/*
|
||||
** Try to find a function named 'sym' in library 'lib'.
|
||||
** Returns the function; in case of error, returns NULL plus an
|
||||
** error string in the stack.
|
||||
*/
|
||||
static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym);
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(LUA_USE_DLOPEN) /* { */
|
||||
/*
|
||||
** {========================================================================
|
||||
** This is an implementation of loadlib based on the dlfcn interface.
|
||||
** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
|
||||
** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
|
||||
** as an emulation layer on top of native functions.
|
||||
** =========================================================================
|
||||
*/
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
/*
|
||||
** Macro to convert pointer-to-void* to pointer-to-function. This cast
|
||||
** is undefined according to ISO C, but POSIX assumes that it works.
|
||||
** (The '__extension__' in gnu compilers is only to avoid warnings.)
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
#define cast_func(p) (__extension__ (lua_CFunction)(p))
|
||||
#else
|
||||
#define cast_func(p) ((lua_CFunction)(p))
|
||||
#endif
|
||||
|
||||
|
||||
static void lsys_unloadlib (void *lib) {
|
||||
dlclose(lib);
|
||||
}
|
||||
|
||||
|
||||
static void *lsys_load (lua_State *L, const char *path, int seeglb) {
|
||||
void *lib = dlopen(path, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL));
|
||||
if (l_unlikely(lib == NULL))
|
||||
lua_pushstring(L, dlerror());
|
||||
return lib;
|
||||
}
|
||||
|
||||
|
||||
static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) {
|
||||
lua_CFunction f = cast_func(dlsym(lib, sym));
|
||||
if (l_unlikely(f == NULL))
|
||||
lua_pushstring(L, dlerror());
|
||||
return f;
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
|
||||
#elif defined(LUA_DL_DLL) /* }{ */
|
||||
/*
|
||||
** {======================================================================
|
||||
** This is an implementation of loadlib for Windows using native functions.
|
||||
** =======================================================================
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
|
||||
/*
|
||||
** optional flags for LoadLibraryEx
|
||||
*/
|
||||
#if !defined(LUA_LLE_FLAGS)
|
||||
#define LUA_LLE_FLAGS 0
|
||||
#endif
|
||||
|
||||
|
||||
#undef setprogdir
|
||||
|
||||
|
||||
/*
|
||||
** Replace in the path (on the top of the stack) any occurrence
|
||||
** of LUA_EXEC_DIR with the executable's path.
|
||||
*/
|
||||
static void setprogdir (lua_State *L) {
|
||||
char buff[MAX_PATH + 1];
|
||||
char *lb;
|
||||
DWORD nsize = sizeof(buff)/sizeof(char);
|
||||
DWORD n = GetModuleFileNameA(NULL, buff, nsize); /* get exec. name */
|
||||
if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL)
|
||||
luaL_error(L, "unable to get ModuleFileName");
|
||||
else {
|
||||
*lb = '\0'; /* cut name on the last '\\' to get the path */
|
||||
luaL_gsub(L, lua_tostring(L, -1), LUA_EXEC_DIR, buff);
|
||||
lua_remove(L, -2); /* remove original string */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void pusherror (lua_State *L) {
|
||||
int error = GetLastError();
|
||||
char buffer[128];
|
||||
if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
NULL, error, 0, buffer, sizeof(buffer)/sizeof(char), NULL))
|
||||
lua_pushstring(L, buffer);
|
||||
else
|
||||
lua_pushfstring(L, "system error %d\n", error);
|
||||
}
|
||||
|
||||
static void lsys_unloadlib (void *lib) {
|
||||
FreeLibrary((HMODULE)lib);
|
||||
}
|
||||
|
||||
|
||||
static void *lsys_load (lua_State *L, const char *path, int seeglb) {
|
||||
HMODULE lib = LoadLibraryExA(path, NULL, LUA_LLE_FLAGS);
|
||||
(void)(seeglb); /* not used: symbols are 'global' by default */
|
||||
if (lib == NULL) pusherror(L);
|
||||
return lib;
|
||||
}
|
||||
|
||||
|
||||
static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) {
|
||||
lua_CFunction f = (lua_CFunction)(voidf)GetProcAddress((HMODULE)lib, sym);
|
||||
if (f == NULL) pusherror(L);
|
||||
return f;
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
#else /* }{ */
|
||||
/*
|
||||
** {======================================================
|
||||
** Fallback for other systems
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
#undef LIB_FAIL
|
||||
#define LIB_FAIL "absent"
|
||||
|
||||
|
||||
#define DLMSG "dynamic libraries not enabled; check your Lua installation"
|
||||
|
||||
|
||||
static void lsys_unloadlib (void *lib) {
|
||||
(void)(lib); /* not used */
|
||||
}
|
||||
|
||||
|
||||
static void *lsys_load (lua_State *L, const char *path, int seeglb) {
|
||||
(void)(path); (void)(seeglb); /* not used */
|
||||
lua_pushliteral(L, DLMSG);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static lua_CFunction lsys_sym (lua_State *L, void *lib, const char *sym) {
|
||||
(void)(lib); (void)(sym); /* not used */
|
||||
lua_pushliteral(L, DLMSG);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
#endif /* } */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Set Paths
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
** LUA_PATH_VAR and LUA_CPATH_VAR are the names of the environment
|
||||
** variables that Lua check to set its paths.
|
||||
*/
|
||||
#if !defined(LUA_PATH_VAR)
|
||||
#define LUA_PATH_VAR "LUA_PATH"
|
||||
#endif
|
||||
|
||||
#if !defined(LUA_CPATH_VAR)
|
||||
#define LUA_CPATH_VAR "LUA_CPATH"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** return registry.LUA_NOENV as a boolean
|
||||
*/
|
||||
static int noenv (lua_State *L) {
|
||||
int b;
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
|
||||
b = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1); /* remove value */
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Set a path
|
||||
*/
|
||||
static void setpath (lua_State *L, const char *fieldname,
|
||||
const char *envname,
|
||||
const char *dft) {
|
||||
const char *dftmark;
|
||||
const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX);
|
||||
const char *path = getenv(nver); /* try versioned name */
|
||||
if (path == NULL) /* no versioned environment variable? */
|
||||
path = getenv(envname); /* try unversioned name */
|
||||
if (path == NULL || noenv(L)) /* no environment variable? */
|
||||
lua_pushstring(L, dft); /* use default */
|
||||
else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL)
|
||||
lua_pushstring(L, path); /* nothing to change */
|
||||
else { /* path contains a ";;": insert default path in its place */
|
||||
size_t len = strlen(path);
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
if (path < dftmark) { /* is there a prefix before ';;'? */
|
||||
luaL_addlstring(&b, path, dftmark - path); /* add it */
|
||||
luaL_addchar(&b, *LUA_PATH_SEP);
|
||||
}
|
||||
luaL_addstring(&b, dft); /* add default */
|
||||
if (dftmark < path + len - 2) { /* is there a suffix after ';;'? */
|
||||
luaL_addchar(&b, *LUA_PATH_SEP);
|
||||
luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark);
|
||||
}
|
||||
luaL_pushresult(&b);
|
||||
}
|
||||
setprogdir(L);
|
||||
lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */
|
||||
lua_pop(L, 1); /* pop versioned variable name ('nver') */
|
||||
}
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** return registry.CLIBS[path]
|
||||
*/
|
||||
static void *checkclib (lua_State *L, const char *path) {
|
||||
void *plib;
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, CLIBS);
|
||||
lua_getfield(L, -1, path);
|
||||
plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */
|
||||
lua_pop(L, 2); /* pop CLIBS table and 'plib' */
|
||||
return plib;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** registry.CLIBS[path] = plib -- for queries
|
||||
** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries
|
||||
*/
|
||||
static void addtoclib (lua_State *L, const char *path, void *plib) {
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, CLIBS);
|
||||
lua_pushlightuserdata(L, plib);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -3, path); /* CLIBS[path] = plib */
|
||||
lua_rawseti(L, -2, luaL_len(L, -2) + 1); /* CLIBS[#CLIBS + 1] = plib */
|
||||
lua_pop(L, 1); /* pop CLIBS table */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib
|
||||
** handles in list CLIBS
|
||||
*/
|
||||
static int gctm (lua_State *L) {
|
||||
lua_Integer n = luaL_len(L, 1);
|
||||
for (; n >= 1; n--) { /* for each handle, in reverse order */
|
||||
lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */
|
||||
lsys_unloadlib(lua_touserdata(L, -1));
|
||||
lua_pop(L, 1); /* pop handle */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* error codes for 'lookforfunc' */
|
||||
#define ERRLIB 1
|
||||
#define ERRFUNC 2
|
||||
|
||||
/*
|
||||
** Look for a C function named 'sym' in a dynamically loaded library
|
||||
** 'path'.
|
||||
** First, check whether the library is already loaded; if not, try
|
||||
** to load it.
|
||||
** Then, if 'sym' is '*', return true (as library has been loaded).
|
||||
** Otherwise, look for symbol 'sym' in the library and push a
|
||||
** C function with that symbol.
|
||||
** Return 0 and 'true' or a function in the stack; in case of
|
||||
** errors, return an error code and an error message in the stack.
|
||||
*/
|
||||
static int lookforfunc (lua_State *L, const char *path, const char *sym) {
|
||||
void *reg = checkclib(L, path); /* check loaded C libraries */
|
||||
if (reg == NULL) { /* must load library? */
|
||||
reg = lsys_load(L, path, *sym == '*'); /* global symbols if 'sym'=='*' */
|
||||
if (reg == NULL) return ERRLIB; /* unable to load library */
|
||||
addtoclib(L, path, reg);
|
||||
}
|
||||
if (*sym == '*') { /* loading only library (no function)? */
|
||||
lua_pushboolean(L, 1); /* return 'true' */
|
||||
return 0; /* no errors */
|
||||
}
|
||||
else {
|
||||
lua_CFunction f = lsys_sym(L, reg, sym);
|
||||
if (f == NULL)
|
||||
return ERRFUNC; /* unable to find function */
|
||||
lua_pushcfunction(L, f); /* else create new function */
|
||||
return 0; /* no errors */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int ll_loadlib (lua_State *L) {
|
||||
const char *path = luaL_checkstring(L, 1);
|
||||
const char *init = luaL_checkstring(L, 2);
|
||||
int stat = lookforfunc(L, path, init);
|
||||
if (l_likely(stat == 0)) /* no errors? */
|
||||
return 1; /* return the loaded function */
|
||||
else { /* error; error message is on stack top */
|
||||
luaL_pushfail(L);
|
||||
lua_insert(L, -2);
|
||||
lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init");
|
||||
return 3; /* return fail, error message, and where */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** 'require' function
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
|
||||
static int readable (const char *filename) {
|
||||
FILE *f = fopen(filename, "r"); /* try to open file */
|
||||
if (f == NULL) return 0; /* open failed */
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Get the next name in '*path' = 'name1;name2;name3;...', changing
|
||||
** the ending ';' to '\0' to create a zero-terminated string. Return
|
||||
** NULL when list ends.
|
||||
*/
|
||||
static const char *getnextfilename (char **path, char *end) {
|
||||
char *sep;
|
||||
char *name = *path;
|
||||
if (name == end)
|
||||
return NULL; /* no more names */
|
||||
else if (*name == '\0') { /* from previous iteration? */
|
||||
*name = *LUA_PATH_SEP; /* restore separator */
|
||||
name++; /* skip it */
|
||||
}
|
||||
sep = strchr(name, *LUA_PATH_SEP); /* find next separator */
|
||||
if (sep == NULL) /* separator not found? */
|
||||
sep = end; /* name goes until the end */
|
||||
*sep = '\0'; /* finish file name */
|
||||
*path = sep; /* will start next search from here */
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Given a path such as ";blabla.so;blublu.so", pushes the string
|
||||
**
|
||||
** no file 'blabla.so'
|
||||
** no file 'blublu.so'
|
||||
*/
|
||||
static void pusherrornotfound (lua_State *L, const char *path) {
|
||||
luaL_Buffer b;
|
||||
luaL_buffinit(L, &b);
|
||||
luaL_addstring(&b, "no file '");
|
||||
luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '");
|
||||
luaL_addstring(&b, "'");
|
||||
luaL_pushresult(&b);
|
||||
}
|
||||
|
||||
|
||||
static const char *searchpath (lua_State *L, const char *name,
|
||||
const char *path,
|
||||
const char *sep,
|
||||
const char *dirsep) {
|
||||
luaL_Buffer buff;
|
||||
char *pathname; /* path with name inserted */
|
||||
char *endpathname; /* its end */
|
||||
const char *filename;
|
||||
/* separator is non-empty and appears in 'name'? */
|
||||
if (*sep != '\0' && strchr(name, *sep) != NULL)
|
||||
name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */
|
||||
luaL_buffinit(L, &buff);
|
||||
/* add path to the buffer, replacing marks ('?') with the file name */
|
||||
luaL_addgsub(&buff, path, LUA_PATH_MARK, name);
|
||||
luaL_addchar(&buff, '\0');
|
||||
pathname = luaL_buffaddr(&buff); /* writable list of file names */
|
||||
endpathname = pathname + luaL_bufflen(&buff) - 1;
|
||||
while ((filename = getnextfilename(&pathname, endpathname)) != NULL) {
|
||||
if (readable(filename)) /* does file exist and is readable? */
|
||||
return lua_pushstring(L, filename); /* save and return name */
|
||||
}
|
||||
luaL_pushresult(&buff); /* push path to create error message */
|
||||
pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */
|
||||
return NULL; /* not found */
|
||||
}
|
||||
|
||||
|
||||
static int ll_searchpath (lua_State *L) {
|
||||
const char *f = searchpath(L, luaL_checkstring(L, 1),
|
||||
luaL_checkstring(L, 2),
|
||||
luaL_optstring(L, 3, "."),
|
||||
luaL_optstring(L, 4, LUA_DIRSEP));
|
||||
if (f != NULL) return 1;
|
||||
else { /* error message is on top of the stack */
|
||||
luaL_pushfail(L);
|
||||
lua_insert(L, -2);
|
||||
return 2; /* return fail + error message */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const char *findfile (lua_State *L, const char *name,
|
||||
const char *pname,
|
||||
const char *dirsep) {
|
||||
const char *path;
|
||||
lua_getfield(L, lua_upvalueindex(1), pname);
|
||||
path = lua_tostring(L, -1);
|
||||
if (l_unlikely(path == NULL))
|
||||
luaL_error(L, "'package.%s' must be a string", pname);
|
||||
return searchpath(L, name, path, ".", dirsep);
|
||||
}
|
||||
|
||||
|
||||
static int checkload (lua_State *L, int stat, const char *filename) {
|
||||
if (l_likely(stat)) { /* module loaded successfully? */
|
||||
lua_pushstring(L, filename); /* will be 2nd argument to module */
|
||||
return 2; /* return open function and file name */
|
||||
}
|
||||
else
|
||||
return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s",
|
||||
lua_tostring(L, 1), filename, lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
|
||||
static int searcher_Lua (lua_State *L) {
|
||||
const char *filename;
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
filename = findfile(L, name, "path", LUA_LSUBSEP);
|
||||
if (filename == NULL) return 1; /* module not found in this path */
|
||||
return checkload(L, (luaL_loadfile(L, filename) == LUA_OK), filename);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Try to find a load function for module 'modname' at file 'filename'.
|
||||
** First, change '.' to '_' in 'modname'; then, if 'modname' has
|
||||
** the form X-Y (that is, it has an "ignore mark"), build a function
|
||||
** name "luaopen_X" and look for it. (For compatibility, if that
|
||||
** fails, it also tries "luaopen_Y".) If there is no ignore mark,
|
||||
** look for a function named "luaopen_modname".
|
||||
*/
|
||||
static int loadfunc (lua_State *L, const char *filename, const char *modname) {
|
||||
const char *openfunc;
|
||||
const char *mark;
|
||||
modname = luaL_gsub(L, modname, ".", LUA_OFSEP);
|
||||
mark = strchr(modname, *LUA_IGMARK);
|
||||
if (mark) {
|
||||
int stat;
|
||||
openfunc = lua_pushlstring(L, modname, mark - modname);
|
||||
openfunc = lua_pushfstring(L, LUA_POF"%s", openfunc);
|
||||
stat = lookforfunc(L, filename, openfunc);
|
||||
if (stat != ERRFUNC) return stat;
|
||||
modname = mark + 1; /* else go ahead and try old-style name */
|
||||
}
|
||||
openfunc = lua_pushfstring(L, LUA_POF"%s", modname);
|
||||
return lookforfunc(L, filename, openfunc);
|
||||
}
|
||||
|
||||
|
||||
static int searcher_C (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
const char *filename = findfile(L, name, "cpath", LUA_CSUBSEP);
|
||||
if (filename == NULL) return 1; /* module not found in this path */
|
||||
return checkload(L, (loadfunc(L, filename, name) == 0), filename);
|
||||
}
|
||||
|
||||
|
||||
static int searcher_Croot (lua_State *L) {
|
||||
const char *filename;
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
const char *p = strchr(name, '.');
|
||||
int stat;
|
||||
if (p == NULL) return 0; /* is root */
|
||||
lua_pushlstring(L, name, p - name);
|
||||
filename = findfile(L, lua_tostring(L, -1), "cpath", LUA_CSUBSEP);
|
||||
if (filename == NULL) return 1; /* root not found */
|
||||
if ((stat = loadfunc(L, filename, name)) != 0) {
|
||||
if (stat != ERRFUNC)
|
||||
return checkload(L, 0, filename); /* real error */
|
||||
else { /* open function not found */
|
||||
lua_pushfstring(L, "no module '%s' in file '%s'", name, filename);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
lua_pushstring(L, filename); /* will be 2nd argument to module */
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
static int searcher_preload (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
|
||||
if (lua_getfield(L, -1, name) == LUA_TNIL) { /* not found? */
|
||||
lua_pushfstring(L, "no field package.preload['%s']", name);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
lua_pushliteral(L, ":preload:");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void findloader (lua_State *L, const char *name) {
|
||||
int i;
|
||||
luaL_Buffer msg; /* to build error message */
|
||||
/* push 'package.searchers' to index 3 in the stack */
|
||||
if (l_unlikely(lua_getfield(L, lua_upvalueindex(1), "searchers")
|
||||
!= LUA_TTABLE))
|
||||
luaL_error(L, "'package.searchers' must be a table");
|
||||
luaL_buffinit(L, &msg);
|
||||
/* iterate over available searchers to find a loader */
|
||||
for (i = 1; ; i++) {
|
||||
luaL_addstring(&msg, "\n\t"); /* error-message prefix */
|
||||
if (l_unlikely(lua_rawgeti(L, 3, i) == LUA_TNIL)) { /* no more searchers? */
|
||||
lua_pop(L, 1); /* remove nil */
|
||||
luaL_buffsub(&msg, 2); /* remove prefix */
|
||||
luaL_pushresult(&msg); /* create error message */
|
||||
luaL_error(L, "module '%s' not found:%s", name, lua_tostring(L, -1));
|
||||
}
|
||||
lua_pushstring(L, name);
|
||||
lua_call(L, 1, 2); /* call it */
|
||||
if (lua_isfunction(L, -2)) /* did it find a loader? */
|
||||
return; /* module loader found */
|
||||
else if (lua_isstring(L, -2)) { /* searcher returned error message? */
|
||||
lua_pop(L, 1); /* remove extra return */
|
||||
luaL_addvalue(&msg); /* concatenate error message */
|
||||
}
|
||||
else { /* no error message */
|
||||
lua_pop(L, 2); /* remove both returns */
|
||||
luaL_buffsub(&msg, 2); /* remove prefix */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int ll_require (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
lua_settop(L, 1); /* LOADED table will be at index 2 */
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
|
||||
lua_getfield(L, 2, name); /* LOADED[name] */
|
||||
if (lua_toboolean(L, -1)) /* is it there? */
|
||||
return 1; /* package is already loaded */
|
||||
/* else must load package */
|
||||
lua_pop(L, 1); /* remove 'getfield' result */
|
||||
findloader(L, name);
|
||||
lua_rotate(L, -2, 1); /* function <-> loader data */
|
||||
lua_pushvalue(L, 1); /* name is 1st argument to module loader */
|
||||
lua_pushvalue(L, -3); /* loader data is 2nd argument */
|
||||
/* stack: ...; loader data; loader function; mod. name; loader data */
|
||||
lua_call(L, 2, 1); /* run loader to load module */
|
||||
/* stack: ...; loader data; result from loader */
|
||||
if (!lua_isnil(L, -1)) /* non-nil return? */
|
||||
lua_setfield(L, 2, name); /* LOADED[name] = returned value */
|
||||
else
|
||||
lua_pop(L, 1); /* pop nil */
|
||||
if (lua_getfield(L, 2, name) == LUA_TNIL) { /* module set no value? */
|
||||
lua_pushboolean(L, 1); /* use true as result */
|
||||
lua_copy(L, -1, -2); /* replace loader result */
|
||||
lua_setfield(L, 2, name); /* LOADED[name] = true */
|
||||
}
|
||||
lua_rotate(L, -2, 1); /* loader data <-> module result */
|
||||
return 2; /* return module result and loader data */
|
||||
}
|
||||
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
|
||||
|
||||
static const luaL_Reg pk_funcs[] = {
|
||||
{"loadlib", ll_loadlib},
|
||||
{"searchpath", ll_searchpath},
|
||||
/* placeholders */
|
||||
{"preload", NULL},
|
||||
{"cpath", NULL},
|
||||
{"path", NULL},
|
||||
{"searchers", NULL},
|
||||
{"loaded", NULL},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
static const luaL_Reg ll_funcs[] = {
|
||||
{"require", ll_require},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
static void createsearcherstable (lua_State *L) {
|
||||
static const lua_CFunction searchers[] = {
|
||||
searcher_preload,
|
||||
searcher_Lua,
|
||||
searcher_C,
|
||||
searcher_Croot,
|
||||
NULL
|
||||
};
|
||||
int i;
|
||||
/* create 'searchers' table */
|
||||
lua_createtable(L, sizeof(searchers)/sizeof(searchers[0]) - 1, 0);
|
||||
/* fill it with predefined searchers */
|
||||
for (i=0; searchers[i] != NULL; i++) {
|
||||
lua_pushvalue(L, -2); /* set 'package' as upvalue for all searchers */
|
||||
lua_pushcclosure(L, searchers[i], 1);
|
||||
lua_rawseti(L, -2, i+1);
|
||||
}
|
||||
lua_setfield(L, -2, "searchers"); /* put it in field 'searchers' */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** create table CLIBS to keep track of loaded C libraries,
|
||||
** setting a finalizer to close all libraries when closing state.
|
||||
*/
|
||||
static void createclibstable (lua_State *L) {
|
||||
luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); /* create CLIBS table */
|
||||
lua_createtable(L, 0, 1); /* create metatable for CLIBS */
|
||||
lua_pushcfunction(L, gctm);
|
||||
lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
|
||||
LUAMOD_API int luaopen_package (lua_State *L) {
|
||||
createclibstable(L);
|
||||
luaL_newlib(L, pk_funcs); /* create 'package' table */
|
||||
createsearcherstable(L);
|
||||
/* set paths */
|
||||
setpath(L, "path", LUA_PATH_VAR, LUA_PATH_DEFAULT);
|
||||
setpath(L, "cpath", LUA_CPATH_VAR, LUA_CPATH_DEFAULT);
|
||||
/* store config information */
|
||||
lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATH_SEP "\n" LUA_PATH_MARK "\n"
|
||||
LUA_EXEC_DIR "\n" LUA_IGMARK "\n");
|
||||
lua_setfield(L, -2, "config");
|
||||
/* set field 'loaded' */
|
||||
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
|
||||
lua_setfield(L, -2, "loaded");
|
||||
/* set field 'preload' */
|
||||
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
|
||||
lua_setfield(L, -2, "preload");
|
||||
lua_pushglobaltable(L);
|
||||
lua_pushvalue(L, -2); /* set 'package' as upvalue for next lib */
|
||||
luaL_setfuncs(L, ll_funcs, 1); /* open lib into global table */
|
||||
lua_pop(L, 1); /* pop global table */
|
||||
return 1; /* return 'package' table */
|
||||
}
|
||||
|
||||
602
crates/voltex_script/lua/lobject.c
Normal file
602
crates/voltex_script/lua/lobject.c
Normal file
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
** $Id: lobject.c $
|
||||
** Some generic functions over Lua objects
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lobject_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include <locale.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
#include "lctype.h"
|
||||
#include "ldebug.h"
|
||||
#include "ldo.h"
|
||||
#include "lmem.h"
|
||||
#include "lobject.h"
|
||||
#include "lstate.h"
|
||||
#include "lstring.h"
|
||||
#include "lvm.h"
|
||||
|
||||
|
||||
/*
|
||||
** Computes ceil(log2(x))
|
||||
*/
|
||||
int luaO_ceillog2 (unsigned int x) {
|
||||
static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */
|
||||
0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
|
||||
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
|
||||
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
|
||||
};
|
||||
int l = 0;
|
||||
x--;
|
||||
while (x >= 256) { l += 8; x >>= 8; }
|
||||
return l + log_2[x];
|
||||
}
|
||||
|
||||
|
||||
static lua_Integer intarith (lua_State *L, int op, lua_Integer v1,
|
||||
lua_Integer v2) {
|
||||
switch (op) {
|
||||
case LUA_OPADD: return intop(+, v1, v2);
|
||||
case LUA_OPSUB:return intop(-, v1, v2);
|
||||
case LUA_OPMUL:return intop(*, v1, v2);
|
||||
case LUA_OPMOD: return luaV_mod(L, v1, v2);
|
||||
case LUA_OPIDIV: return luaV_idiv(L, v1, v2);
|
||||
case LUA_OPBAND: return intop(&, v1, v2);
|
||||
case LUA_OPBOR: return intop(|, v1, v2);
|
||||
case LUA_OPBXOR: return intop(^, v1, v2);
|
||||
case LUA_OPSHL: return luaV_shiftl(v1, v2);
|
||||
case LUA_OPSHR: return luaV_shiftr(v1, v2);
|
||||
case LUA_OPUNM: return intop(-, 0, v1);
|
||||
case LUA_OPBNOT: return intop(^, ~l_castS2U(0), v1);
|
||||
default: lua_assert(0); return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static lua_Number numarith (lua_State *L, int op, lua_Number v1,
|
||||
lua_Number v2) {
|
||||
switch (op) {
|
||||
case LUA_OPADD: return luai_numadd(L, v1, v2);
|
||||
case LUA_OPSUB: return luai_numsub(L, v1, v2);
|
||||
case LUA_OPMUL: return luai_nummul(L, v1, v2);
|
||||
case LUA_OPDIV: return luai_numdiv(L, v1, v2);
|
||||
case LUA_OPPOW: return luai_numpow(L, v1, v2);
|
||||
case LUA_OPIDIV: return luai_numidiv(L, v1, v2);
|
||||
case LUA_OPUNM: return luai_numunm(L, v1);
|
||||
case LUA_OPMOD: return luaV_modf(L, v1, v2);
|
||||
default: lua_assert(0); return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int luaO_rawarith (lua_State *L, int op, const TValue *p1, const TValue *p2,
|
||||
TValue *res) {
|
||||
switch (op) {
|
||||
case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR:
|
||||
case LUA_OPSHL: case LUA_OPSHR:
|
||||
case LUA_OPBNOT: { /* operate only on integers */
|
||||
lua_Integer i1; lua_Integer i2;
|
||||
if (tointegerns(p1, &i1) && tointegerns(p2, &i2)) {
|
||||
setivalue(res, intarith(L, op, i1, i2));
|
||||
return 1;
|
||||
}
|
||||
else return 0; /* fail */
|
||||
}
|
||||
case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */
|
||||
lua_Number n1; lua_Number n2;
|
||||
if (tonumberns(p1, n1) && tonumberns(p2, n2)) {
|
||||
setfltvalue(res, numarith(L, op, n1, n2));
|
||||
return 1;
|
||||
}
|
||||
else return 0; /* fail */
|
||||
}
|
||||
default: { /* other operations */
|
||||
lua_Number n1; lua_Number n2;
|
||||
if (ttisinteger(p1) && ttisinteger(p2)) {
|
||||
setivalue(res, intarith(L, op, ivalue(p1), ivalue(p2)));
|
||||
return 1;
|
||||
}
|
||||
else if (tonumberns(p1, n1) && tonumberns(p2, n2)) {
|
||||
setfltvalue(res, numarith(L, op, n1, n2));
|
||||
return 1;
|
||||
}
|
||||
else return 0; /* fail */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2,
|
||||
StkId res) {
|
||||
if (!luaO_rawarith(L, op, p1, p2, s2v(res))) {
|
||||
/* could not perform raw operation; try metamethod */
|
||||
luaT_trybinTM(L, p1, p2, res, cast(TMS, (op - LUA_OPADD) + TM_ADD));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int luaO_hexavalue (int c) {
|
||||
if (lisdigit(c)) return c - '0';
|
||||
else return (ltolower(c) - 'a') + 10;
|
||||
}
|
||||
|
||||
|
||||
static int isneg (const char **s) {
|
||||
if (**s == '-') { (*s)++; return 1; }
|
||||
else if (**s == '+') (*s)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Lua's implementation for 'lua_strx2number'
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#if !defined(lua_strx2number)
|
||||
|
||||
/* maximum number of significant digits to read (to avoid overflows
|
||||
even with single floats) */
|
||||
#define MAXSIGDIG 30
|
||||
|
||||
/*
|
||||
** convert a hexadecimal numeric string to a number, following
|
||||
** C99 specification for 'strtod'
|
||||
*/
|
||||
static lua_Number lua_strx2number (const char *s, char **endptr) {
|
||||
int dot = lua_getlocaledecpoint();
|
||||
lua_Number r = l_mathop(0.0); /* result (accumulator) */
|
||||
int sigdig = 0; /* number of significant digits */
|
||||
int nosigdig = 0; /* number of non-significant digits */
|
||||
int e = 0; /* exponent correction */
|
||||
int neg; /* 1 if number is negative */
|
||||
int hasdot = 0; /* true after seen a dot */
|
||||
*endptr = cast_charp(s); /* nothing is valid yet */
|
||||
while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */
|
||||
neg = isneg(&s); /* check sign */
|
||||
if (!(*s == '0' && (*(s + 1) == 'x' || *(s + 1) == 'X'))) /* check '0x' */
|
||||
return l_mathop(0.0); /* invalid format (no '0x') */
|
||||
for (s += 2; ; s++) { /* skip '0x' and read numeral */
|
||||
if (*s == dot) {
|
||||
if (hasdot) break; /* second dot? stop loop */
|
||||
else hasdot = 1;
|
||||
}
|
||||
else if (lisxdigit(cast_uchar(*s))) {
|
||||
if (sigdig == 0 && *s == '0') /* non-significant digit (zero)? */
|
||||
nosigdig++;
|
||||
else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */
|
||||
r = (r * l_mathop(16.0)) + luaO_hexavalue(*s);
|
||||
else e++; /* too many digits; ignore, but still count for exponent */
|
||||
if (hasdot) e--; /* decimal digit? correct exponent */
|
||||
}
|
||||
else break; /* neither a dot nor a digit */
|
||||
}
|
||||
if (nosigdig + sigdig == 0) /* no digits? */
|
||||
return l_mathop(0.0); /* invalid format */
|
||||
*endptr = cast_charp(s); /* valid up to here */
|
||||
e *= 4; /* each digit multiplies/divides value by 2^4 */
|
||||
if (*s == 'p' || *s == 'P') { /* exponent part? */
|
||||
int exp1 = 0; /* exponent value */
|
||||
int neg1; /* exponent sign */
|
||||
s++; /* skip 'p' */
|
||||
neg1 = isneg(&s); /* sign */
|
||||
if (!lisdigit(cast_uchar(*s)))
|
||||
return l_mathop(0.0); /* invalid; must have at least one digit */
|
||||
while (lisdigit(cast_uchar(*s))) /* read exponent */
|
||||
exp1 = exp1 * 10 + *(s++) - '0';
|
||||
if (neg1) exp1 = -exp1;
|
||||
e += exp1;
|
||||
*endptr = cast_charp(s); /* valid up to here */
|
||||
}
|
||||
if (neg) r = -r;
|
||||
return l_mathop(ldexp)(r, e);
|
||||
}
|
||||
|
||||
#endif
|
||||
/* }====================================================== */
|
||||
|
||||
|
||||
/* maximum length of a numeral to be converted to a number */
|
||||
#if !defined (L_MAXLENNUM)
|
||||
#define L_MAXLENNUM 200
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Convert string 's' to a Lua number (put in 'result'). Return NULL on
|
||||
** fail or the address of the ending '\0' on success. ('mode' == 'x')
|
||||
** means a hexadecimal numeral.
|
||||
*/
|
||||
static const char *l_str2dloc (const char *s, lua_Number *result, int mode) {
|
||||
char *endptr;
|
||||
*result = (mode == 'x') ? lua_strx2number(s, &endptr) /* try to convert */
|
||||
: lua_str2number(s, &endptr);
|
||||
if (endptr == s) return NULL; /* nothing recognized? */
|
||||
while (lisspace(cast_uchar(*endptr))) endptr++; /* skip trailing spaces */
|
||||
return (*endptr == '\0') ? endptr : NULL; /* OK iff no trailing chars */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Convert string 's' to a Lua number (put in 'result') handling the
|
||||
** current locale.
|
||||
** This function accepts both the current locale or a dot as the radix
|
||||
** mark. If the conversion fails, it may mean number has a dot but
|
||||
** locale accepts something else. In that case, the code copies 's'
|
||||
** to a buffer (because 's' is read-only), changes the dot to the
|
||||
** current locale radix mark, and tries to convert again.
|
||||
** The variable 'mode' checks for special characters in the string:
|
||||
** - 'n' means 'inf' or 'nan' (which should be rejected)
|
||||
** - 'x' means a hexadecimal numeral
|
||||
** - '.' just optimizes the search for the common case (no special chars)
|
||||
*/
|
||||
static const char *l_str2d (const char *s, lua_Number *result) {
|
||||
const char *endptr;
|
||||
const char *pmode = strpbrk(s, ".xXnN"); /* look for special chars */
|
||||
int mode = pmode ? ltolower(cast_uchar(*pmode)) : 0;
|
||||
if (mode == 'n') /* reject 'inf' and 'nan' */
|
||||
return NULL;
|
||||
endptr = l_str2dloc(s, result, mode); /* try to convert */
|
||||
if (endptr == NULL) { /* failed? may be a different locale */
|
||||
char buff[L_MAXLENNUM + 1];
|
||||
const char *pdot = strchr(s, '.');
|
||||
if (pdot == NULL || strlen(s) > L_MAXLENNUM)
|
||||
return NULL; /* string too long or no dot; fail */
|
||||
strcpy(buff, s); /* copy string to buffer */
|
||||
buff[pdot - s] = lua_getlocaledecpoint(); /* correct decimal point */
|
||||
endptr = l_str2dloc(buff, result, mode); /* try again */
|
||||
if (endptr != NULL)
|
||||
endptr = s + (endptr - buff); /* make relative to 's' */
|
||||
}
|
||||
return endptr;
|
||||
}
|
||||
|
||||
|
||||
#define MAXBY10 cast(lua_Unsigned, LUA_MAXINTEGER / 10)
|
||||
#define MAXLASTD cast_int(LUA_MAXINTEGER % 10)
|
||||
|
||||
static const char *l_str2int (const char *s, lua_Integer *result) {
|
||||
lua_Unsigned a = 0;
|
||||
int empty = 1;
|
||||
int neg;
|
||||
while (lisspace(cast_uchar(*s))) s++; /* skip initial spaces */
|
||||
neg = isneg(&s);
|
||||
if (s[0] == '0' &&
|
||||
(s[1] == 'x' || s[1] == 'X')) { /* hex? */
|
||||
s += 2; /* skip '0x' */
|
||||
for (; lisxdigit(cast_uchar(*s)); s++) {
|
||||
a = a * 16 + luaO_hexavalue(*s);
|
||||
empty = 0;
|
||||
}
|
||||
}
|
||||
else { /* decimal */
|
||||
for (; lisdigit(cast_uchar(*s)); s++) {
|
||||
int d = *s - '0';
|
||||
if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */
|
||||
return NULL; /* do not accept it (as integer) */
|
||||
a = a * 10 + d;
|
||||
empty = 0;
|
||||
}
|
||||
}
|
||||
while (lisspace(cast_uchar(*s))) s++; /* skip trailing spaces */
|
||||
if (empty || *s != '\0') return NULL; /* something wrong in the numeral */
|
||||
else {
|
||||
*result = l_castU2S((neg) ? 0u - a : a);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
size_t luaO_str2num (const char *s, TValue *o) {
|
||||
lua_Integer i; lua_Number n;
|
||||
const char *e;
|
||||
if ((e = l_str2int(s, &i)) != NULL) { /* try as an integer */
|
||||
setivalue(o, i);
|
||||
}
|
||||
else if ((e = l_str2d(s, &n)) != NULL) { /* else try as a float */
|
||||
setfltvalue(o, n);
|
||||
}
|
||||
else
|
||||
return 0; /* conversion failed */
|
||||
return (e - s) + 1; /* success; return string size */
|
||||
}
|
||||
|
||||
|
||||
int luaO_utf8esc (char *buff, unsigned long x) {
|
||||
int n = 1; /* number of bytes put in buffer (backwards) */
|
||||
lua_assert(x <= 0x7FFFFFFFu);
|
||||
if (x < 0x80) /* ascii? */
|
||||
buff[UTF8BUFFSZ - 1] = cast_char(x);
|
||||
else { /* need continuation bytes */
|
||||
unsigned int mfb = 0x3f; /* maximum that fits in first byte */
|
||||
do { /* add continuation bytes */
|
||||
buff[UTF8BUFFSZ - (n++)] = cast_char(0x80 | (x & 0x3f));
|
||||
x >>= 6; /* remove added bits */
|
||||
mfb >>= 1; /* now there is one less bit available in first byte */
|
||||
} while (x > mfb); /* still needs continuation byte? */
|
||||
buff[UTF8BUFFSZ - n] = cast_char((~mfb << 1) | x); /* add first byte */
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Maximum length of the conversion of a number to a string. Must be
|
||||
** enough to accommodate both LUA_INTEGER_FMT and LUA_NUMBER_FMT.
|
||||
** (For a long long int, this is 19 digits plus a sign and a final '\0',
|
||||
** adding to 21. For a long double, it can go to a sign, 33 digits,
|
||||
** the dot, an exponent letter, an exponent sign, 5 exponent digits,
|
||||
** and a final '\0', adding to 43.)
|
||||
*/
|
||||
#define MAXNUMBER2STR 44
|
||||
|
||||
|
||||
/*
|
||||
** Convert a number object to a string, adding it to a buffer
|
||||
*/
|
||||
static int tostringbuff (TValue *obj, char *buff) {
|
||||
int len;
|
||||
lua_assert(ttisnumber(obj));
|
||||
if (ttisinteger(obj))
|
||||
len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));
|
||||
else {
|
||||
len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj));
|
||||
if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */
|
||||
buff[len++] = lua_getlocaledecpoint();
|
||||
buff[len++] = '0'; /* adds '.0' to result */
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Convert a number object to a Lua string, replacing the value at 'obj'
|
||||
*/
|
||||
void luaO_tostring (lua_State *L, TValue *obj) {
|
||||
char buff[MAXNUMBER2STR];
|
||||
int len = tostringbuff(obj, buff);
|
||||
setsvalue(L, obj, luaS_newlstr(L, buff, len));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** 'luaO_pushvfstring'
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
** Size for buffer space used by 'luaO_pushvfstring'. It should be
|
||||
** (LUA_IDSIZE + MAXNUMBER2STR) + a minimal space for basic messages,
|
||||
** so that 'luaG_addinfo' can work directly on the buffer.
|
||||
*/
|
||||
#define BUFVFS (LUA_IDSIZE + MAXNUMBER2STR + 95)
|
||||
|
||||
/* buffer used by 'luaO_pushvfstring' */
|
||||
typedef struct BuffFS {
|
||||
lua_State *L;
|
||||
int pushed; /* true if there is a part of the result on the stack */
|
||||
int blen; /* length of partial string in 'space' */
|
||||
char space[BUFVFS]; /* holds last part of the result */
|
||||
} BuffFS;
|
||||
|
||||
|
||||
/*
|
||||
** Push given string to the stack, as part of the result, and
|
||||
** join it to previous partial result if there is one.
|
||||
** It may call 'luaV_concat' while using one slot from EXTRA_STACK.
|
||||
** This call cannot invoke metamethods, as both operands must be
|
||||
** strings. It can, however, raise an error if the result is too
|
||||
** long. In that case, 'luaV_concat' frees the extra slot before
|
||||
** raising the error.
|
||||
*/
|
||||
static void pushstr (BuffFS *buff, const char *str, size_t lstr) {
|
||||
lua_State *L = buff->L;
|
||||
setsvalue2s(L, L->top.p, luaS_newlstr(L, str, lstr));
|
||||
L->top.p++; /* may use one slot from EXTRA_STACK */
|
||||
if (!buff->pushed) /* no previous string on the stack? */
|
||||
buff->pushed = 1; /* now there is one */
|
||||
else /* join previous string with new one */
|
||||
luaV_concat(L, 2);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** empty the buffer space into the stack
|
||||
*/
|
||||
static void clearbuff (BuffFS *buff) {
|
||||
pushstr(buff, buff->space, buff->blen); /* push buffer contents */
|
||||
buff->blen = 0; /* space now is empty */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Get a space of size 'sz' in the buffer. If buffer has not enough
|
||||
** space, empty it. 'sz' must fit in an empty buffer.
|
||||
*/
|
||||
static char *getbuff (BuffFS *buff, int sz) {
|
||||
lua_assert(buff->blen <= BUFVFS); lua_assert(sz <= BUFVFS);
|
||||
if (sz > BUFVFS - buff->blen) /* not enough space? */
|
||||
clearbuff(buff);
|
||||
return buff->space + buff->blen;
|
||||
}
|
||||
|
||||
|
||||
#define addsize(b,sz) ((b)->blen += (sz))
|
||||
|
||||
|
||||
/*
|
||||
** Add 'str' to the buffer. If string is larger than the buffer space,
|
||||
** push the string directly to the stack.
|
||||
*/
|
||||
static void addstr2buff (BuffFS *buff, const char *str, size_t slen) {
|
||||
if (slen <= BUFVFS) { /* does string fit into buffer? */
|
||||
char *bf = getbuff(buff, cast_int(slen));
|
||||
memcpy(bf, str, slen); /* add string to buffer */
|
||||
addsize(buff, cast_int(slen));
|
||||
}
|
||||
else { /* string larger than buffer */
|
||||
clearbuff(buff); /* string comes after buffer's content */
|
||||
pushstr(buff, str, slen); /* push string */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Add a numeral to the buffer.
|
||||
*/
|
||||
static void addnum2buff (BuffFS *buff, TValue *num) {
|
||||
char *numbuff = getbuff(buff, MAXNUMBER2STR);
|
||||
int len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
|
||||
addsize(buff, len);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** this function handles only '%d', '%c', '%f', '%p', '%s', and '%%'
|
||||
conventional formats, plus Lua-specific '%I' and '%U'
|
||||
*/
|
||||
const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
|
||||
BuffFS buff; /* holds last part of the result */
|
||||
const char *e; /* points to next '%' */
|
||||
buff.pushed = buff.blen = 0;
|
||||
buff.L = L;
|
||||
while ((e = strchr(fmt, '%')) != NULL) {
|
||||
addstr2buff(&buff, fmt, e - fmt); /* add 'fmt' up to '%' */
|
||||
switch (*(e + 1)) { /* conversion specifier */
|
||||
case 's': { /* zero-terminated string */
|
||||
const char *s = va_arg(argp, char *);
|
||||
if (s == NULL) s = "(null)";
|
||||
addstr2buff(&buff, s, strlen(s));
|
||||
break;
|
||||
}
|
||||
case 'c': { /* an 'int' as a character */
|
||||
char c = cast_uchar(va_arg(argp, int));
|
||||
addstr2buff(&buff, &c, sizeof(char));
|
||||
break;
|
||||
}
|
||||
case 'd': { /* an 'int' */
|
||||
TValue num;
|
||||
setivalue(&num, va_arg(argp, int));
|
||||
addnum2buff(&buff, &num);
|
||||
break;
|
||||
}
|
||||
case 'I': { /* a 'lua_Integer' */
|
||||
TValue num;
|
||||
setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
|
||||
addnum2buff(&buff, &num);
|
||||
break;
|
||||
}
|
||||
case 'f': { /* a 'lua_Number' */
|
||||
TValue num;
|
||||
setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
|
||||
addnum2buff(&buff, &num);
|
||||
break;
|
||||
}
|
||||
case 'p': { /* a pointer */
|
||||
const int sz = 3 * sizeof(void*) + 8; /* enough space for '%p' */
|
||||
char *bf = getbuff(&buff, sz);
|
||||
void *p = va_arg(argp, void *);
|
||||
int len = lua_pointer2str(bf, sz, p);
|
||||
addsize(&buff, len);
|
||||
break;
|
||||
}
|
||||
case 'U': { /* a 'long' as a UTF-8 sequence */
|
||||
char bf[UTF8BUFFSZ];
|
||||
int len = luaO_utf8esc(bf, va_arg(argp, long));
|
||||
addstr2buff(&buff, bf + UTF8BUFFSZ - len, len);
|
||||
break;
|
||||
}
|
||||
case '%': {
|
||||
addstr2buff(&buff, "%", 1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'",
|
||||
*(e + 1));
|
||||
}
|
||||
}
|
||||
fmt = e + 2; /* skip '%' and the specifier */
|
||||
}
|
||||
addstr2buff(&buff, fmt, strlen(fmt)); /* rest of 'fmt' */
|
||||
clearbuff(&buff); /* empty buffer into the stack */
|
||||
lua_assert(buff.pushed == 1);
|
||||
return getstr(tsvalue(s2v(L->top.p - 1)));
|
||||
}
|
||||
|
||||
|
||||
const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) {
|
||||
const char *msg;
|
||||
va_list argp;
|
||||
va_start(argp, fmt);
|
||||
msg = luaO_pushvfstring(L, fmt, argp);
|
||||
va_end(argp);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
#define RETS "..."
|
||||
#define PRE "[string \""
|
||||
#define POS "\"]"
|
||||
|
||||
#define addstr(a,b,l) ( memcpy(a,b,(l) * sizeof(char)), a += (l) )
|
||||
|
||||
void luaO_chunkid (char *out, const char *source, size_t srclen) {
|
||||
size_t bufflen = LUA_IDSIZE; /* free space in buffer */
|
||||
if (*source == '=') { /* 'literal' source */
|
||||
if (srclen <= bufflen) /* small enough? */
|
||||
memcpy(out, source + 1, srclen * sizeof(char));
|
||||
else { /* truncate it */
|
||||
addstr(out, source + 1, bufflen - 1);
|
||||
*out = '\0';
|
||||
}
|
||||
}
|
||||
else if (*source == '@') { /* file name */
|
||||
if (srclen <= bufflen) /* small enough? */
|
||||
memcpy(out, source + 1, srclen * sizeof(char));
|
||||
else { /* add '...' before rest of name */
|
||||
addstr(out, RETS, LL(RETS));
|
||||
bufflen -= LL(RETS);
|
||||
memcpy(out, source + 1 + srclen - bufflen, bufflen * sizeof(char));
|
||||
}
|
||||
}
|
||||
else { /* string; format as [string "source"] */
|
||||
const char *nl = strchr(source, '\n'); /* find first new line (if any) */
|
||||
addstr(out, PRE, LL(PRE)); /* add prefix */
|
||||
bufflen -= LL(PRE RETS POS) + 1; /* save space for prefix+suffix+'\0' */
|
||||
if (srclen < bufflen && nl == NULL) { /* small one-line source? */
|
||||
addstr(out, source, srclen); /* keep it */
|
||||
}
|
||||
else {
|
||||
if (nl != NULL) srclen = nl - source; /* stop at first newline */
|
||||
if (srclen > bufflen) srclen = bufflen;
|
||||
addstr(out, source, srclen);
|
||||
addstr(out, RETS, LL(RETS));
|
||||
}
|
||||
memcpy(out, POS, (LL(POS) + 1) * sizeof(char));
|
||||
}
|
||||
}
|
||||
|
||||
813
crates/voltex_script/lua/lobject.h
Normal file
813
crates/voltex_script/lua/lobject.h
Normal file
@@ -0,0 +1,813 @@
|
||||
/*
|
||||
** $Id: lobject.h $
|
||||
** Type definitions for Lua objects
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
|
||||
#ifndef lobject_h
|
||||
#define lobject_h
|
||||
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
#include "llimits.h"
|
||||
#include "lua.h"
|
||||
|
||||
|
||||
/*
|
||||
** Extra types for collectable non-values
|
||||
*/
|
||||
#define LUA_TUPVAL LUA_NUMTYPES /* upvalues */
|
||||
#define LUA_TPROTO (LUA_NUMTYPES+1) /* function prototypes */
|
||||
#define LUA_TDEADKEY (LUA_NUMTYPES+2) /* removed keys in tables */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** number of all possible types (including LUA_TNONE but excluding DEADKEY)
|
||||
*/
|
||||
#define LUA_TOTALTYPES (LUA_TPROTO + 2)
|
||||
|
||||
|
||||
/*
|
||||
** tags for Tagged Values have the following use of bits:
|
||||
** bits 0-3: actual tag (a LUA_T* constant)
|
||||
** bits 4-5: variant bits
|
||||
** bit 6: whether value is collectable
|
||||
*/
|
||||
|
||||
/* add variant bits to a type */
|
||||
#define makevariant(t,v) ((t) | ((v) << 4))
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Union of all Lua values
|
||||
*/
|
||||
typedef union Value {
|
||||
struct GCObject *gc; /* collectable objects */
|
||||
void *p; /* light userdata */
|
||||
lua_CFunction f; /* light C functions */
|
||||
lua_Integer i; /* integer numbers */
|
||||
lua_Number n; /* float numbers */
|
||||
/* not used, but may avoid warnings for uninitialized value */
|
||||
lu_byte ub;
|
||||
} Value;
|
||||
|
||||
|
||||
/*
|
||||
** Tagged Values. This is the basic representation of values in Lua:
|
||||
** an actual value plus a tag with its type.
|
||||
*/
|
||||
|
||||
#define TValuefields Value value_; lu_byte tt_
|
||||
|
||||
typedef struct TValue {
|
||||
TValuefields;
|
||||
} TValue;
|
||||
|
||||
|
||||
#define val_(o) ((o)->value_)
|
||||
#define valraw(o) (val_(o))
|
||||
|
||||
|
||||
/* raw type tag of a TValue */
|
||||
#define rawtt(o) ((o)->tt_)
|
||||
|
||||
/* tag with no variants (bits 0-3) */
|
||||
#define novariant(t) ((t) & 0x0F)
|
||||
|
||||
/* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */
|
||||
#define withvariant(t) ((t) & 0x3F)
|
||||
#define ttypetag(o) withvariant(rawtt(o))
|
||||
|
||||
/* type of a TValue */
|
||||
#define ttype(o) (novariant(rawtt(o)))
|
||||
|
||||
|
||||
/* Macros to test type */
|
||||
#define checktag(o,t) (rawtt(o) == (t))
|
||||
#define checktype(o,t) (ttype(o) == (t))
|
||||
|
||||
|
||||
/* Macros for internal tests */
|
||||
|
||||
/* collectable object has the same tag as the original value */
|
||||
#define righttt(obj) (ttypetag(obj) == gcvalue(obj)->tt)
|
||||
|
||||
/*
|
||||
** Any value being manipulated by the program either is non
|
||||
** collectable, or the collectable object has the right tag
|
||||
** and it is not dead. The option 'L == NULL' allows other
|
||||
** macros using this one to be used where L is not available.
|
||||
*/
|
||||
#define checkliveness(L,obj) \
|
||||
((void)L, lua_longassert(!iscollectable(obj) || \
|
||||
(righttt(obj) && (L == NULL || !isdead(G(L),gcvalue(obj))))))
|
||||
|
||||
|
||||
/* Macros to set values */
|
||||
|
||||
/* set a value's tag */
|
||||
#define settt_(o,t) ((o)->tt_=(t))
|
||||
|
||||
|
||||
/* main macro to copy values (from 'obj2' to 'obj1') */
|
||||
#define setobj(L,obj1,obj2) \
|
||||
{ TValue *io1=(obj1); const TValue *io2=(obj2); \
|
||||
io1->value_ = io2->value_; settt_(io1, io2->tt_); \
|
||||
checkliveness(L,io1); lua_assert(!isnonstrictnil(io1)); }
|
||||
|
||||
/*
|
||||
** Different types of assignments, according to source and destination.
|
||||
** (They are mostly equal now, but may be different in the future.)
|
||||
*/
|
||||
|
||||
/* from stack to stack */
|
||||
#define setobjs2s(L,o1,o2) setobj(L,s2v(o1),s2v(o2))
|
||||
/* to stack (not from same stack) */
|
||||
#define setobj2s(L,o1,o2) setobj(L,s2v(o1),o2)
|
||||
/* from table to same table */
|
||||
#define setobjt2t setobj
|
||||
/* to new object */
|
||||
#define setobj2n setobj
|
||||
/* to table */
|
||||
#define setobj2t setobj
|
||||
|
||||
|
||||
/*
|
||||
** Entries in a Lua stack. Field 'tbclist' forms a list of all
|
||||
** to-be-closed variables active in this stack. Dummy entries are
|
||||
** used when the distance between two tbc variables does not fit
|
||||
** in an unsigned short. They are represented by delta==0, and
|
||||
** their real delta is always the maximum value that fits in
|
||||
** that field.
|
||||
*/
|
||||
typedef union StackValue {
|
||||
TValue val;
|
||||
struct {
|
||||
TValuefields;
|
||||
unsigned short delta;
|
||||
} tbclist;
|
||||
} StackValue;
|
||||
|
||||
|
||||
/* index to stack elements */
|
||||
typedef StackValue *StkId;
|
||||
|
||||
|
||||
/*
|
||||
** When reallocating the stack, change all pointers to the stack into
|
||||
** proper offsets.
|
||||
*/
|
||||
typedef union {
|
||||
StkId p; /* actual pointer */
|
||||
ptrdiff_t offset; /* used while the stack is being reallocated */
|
||||
} StkIdRel;
|
||||
|
||||
|
||||
/* convert a 'StackValue' to a 'TValue' */
|
||||
#define s2v(o) (&(o)->val)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Nil
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/* Standard nil */
|
||||
#define LUA_VNIL makevariant(LUA_TNIL, 0)
|
||||
|
||||
/* Empty slot (which might be different from a slot containing nil) */
|
||||
#define LUA_VEMPTY makevariant(LUA_TNIL, 1)
|
||||
|
||||
/* Value returned for a key not found in a table (absent key) */
|
||||
#define LUA_VABSTKEY makevariant(LUA_TNIL, 2)
|
||||
|
||||
|
||||
/* macro to test for (any kind of) nil */
|
||||
#define ttisnil(v) checktype((v), LUA_TNIL)
|
||||
|
||||
|
||||
/* macro to test for a standard nil */
|
||||
#define ttisstrictnil(o) checktag((o), LUA_VNIL)
|
||||
|
||||
|
||||
#define setnilvalue(obj) settt_(obj, LUA_VNIL)
|
||||
|
||||
|
||||
#define isabstkey(v) checktag((v), LUA_VABSTKEY)
|
||||
|
||||
|
||||
/*
|
||||
** macro to detect non-standard nils (used only in assertions)
|
||||
*/
|
||||
#define isnonstrictnil(v) (ttisnil(v) && !ttisstrictnil(v))
|
||||
|
||||
|
||||
/*
|
||||
** By default, entries with any kind of nil are considered empty.
|
||||
** (In any definition, values associated with absent keys must also
|
||||
** be accepted as empty.)
|
||||
*/
|
||||
#define isempty(v) ttisnil(v)
|
||||
|
||||
|
||||
/* macro defining a value corresponding to an absent key */
|
||||
#define ABSTKEYCONSTANT {NULL}, LUA_VABSTKEY
|
||||
|
||||
|
||||
/* mark an entry as empty */
|
||||
#define setempty(v) settt_(v, LUA_VEMPTY)
|
||||
|
||||
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Booleans
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
|
||||
#define LUA_VFALSE makevariant(LUA_TBOOLEAN, 0)
|
||||
#define LUA_VTRUE makevariant(LUA_TBOOLEAN, 1)
|
||||
|
||||
#define ttisboolean(o) checktype((o), LUA_TBOOLEAN)
|
||||
#define ttisfalse(o) checktag((o), LUA_VFALSE)
|
||||
#define ttistrue(o) checktag((o), LUA_VTRUE)
|
||||
|
||||
|
||||
#define l_isfalse(o) (ttisfalse(o) || ttisnil(o))
|
||||
|
||||
|
||||
#define setbfvalue(obj) settt_(obj, LUA_VFALSE)
|
||||
#define setbtvalue(obj) settt_(obj, LUA_VTRUE)
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Threads
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#define LUA_VTHREAD makevariant(LUA_TTHREAD, 0)
|
||||
|
||||
#define ttisthread(o) checktag((o), ctb(LUA_VTHREAD))
|
||||
|
||||
#define thvalue(o) check_exp(ttisthread(o), gco2th(val_(o).gc))
|
||||
|
||||
#define setthvalue(L,obj,x) \
|
||||
{ TValue *io = (obj); lua_State *x_ = (x); \
|
||||
val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTHREAD)); \
|
||||
checkliveness(L,io); }
|
||||
|
||||
#define setthvalue2s(L,o,t) setthvalue(L,s2v(o),t)
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Collectable Objects
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
** Common Header for all collectable objects (in macro form, to be
|
||||
** included in other objects)
|
||||
*/
|
||||
#define CommonHeader struct GCObject *next; lu_byte tt; lu_byte marked
|
||||
|
||||
|
||||
/* Common type for all collectable objects */
|
||||
typedef struct GCObject {
|
||||
CommonHeader;
|
||||
} GCObject;
|
||||
|
||||
|
||||
/* Bit mark for collectable types */
|
||||
#define BIT_ISCOLLECTABLE (1 << 6)
|
||||
|
||||
#define iscollectable(o) (rawtt(o) & BIT_ISCOLLECTABLE)
|
||||
|
||||
/* mark a tag as collectable */
|
||||
#define ctb(t) ((t) | BIT_ISCOLLECTABLE)
|
||||
|
||||
#define gcvalue(o) check_exp(iscollectable(o), val_(o).gc)
|
||||
|
||||
#define gcvalueraw(v) ((v).gc)
|
||||
|
||||
#define setgcovalue(L,obj,x) \
|
||||
{ TValue *io = (obj); GCObject *i_g=(x); \
|
||||
val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); }
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Numbers
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/* Variant tags for numbers */
|
||||
#define LUA_VNUMINT makevariant(LUA_TNUMBER, 0) /* integer numbers */
|
||||
#define LUA_VNUMFLT makevariant(LUA_TNUMBER, 1) /* float numbers */
|
||||
|
||||
#define ttisnumber(o) checktype((o), LUA_TNUMBER)
|
||||
#define ttisfloat(o) checktag((o), LUA_VNUMFLT)
|
||||
#define ttisinteger(o) checktag((o), LUA_VNUMINT)
|
||||
|
||||
#define nvalue(o) check_exp(ttisnumber(o), \
|
||||
(ttisinteger(o) ? cast_num(ivalue(o)) : fltvalue(o)))
|
||||
#define fltvalue(o) check_exp(ttisfloat(o), val_(o).n)
|
||||
#define ivalue(o) check_exp(ttisinteger(o), val_(o).i)
|
||||
|
||||
#define fltvalueraw(v) ((v).n)
|
||||
#define ivalueraw(v) ((v).i)
|
||||
|
||||
#define setfltvalue(obj,x) \
|
||||
{ TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_VNUMFLT); }
|
||||
|
||||
#define chgfltvalue(obj,x) \
|
||||
{ TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); }
|
||||
|
||||
#define setivalue(obj,x) \
|
||||
{ TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_VNUMINT); }
|
||||
|
||||
#define chgivalue(obj,x) \
|
||||
{ TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); }
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Strings
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
/* Variant tags for strings */
|
||||
#define LUA_VSHRSTR makevariant(LUA_TSTRING, 0) /* short strings */
|
||||
#define LUA_VLNGSTR makevariant(LUA_TSTRING, 1) /* long strings */
|
||||
|
||||
#define ttisstring(o) checktype((o), LUA_TSTRING)
|
||||
#define ttisshrstring(o) checktag((o), ctb(LUA_VSHRSTR))
|
||||
#define ttislngstring(o) checktag((o), ctb(LUA_VLNGSTR))
|
||||
|
||||
#define tsvalueraw(v) (gco2ts((v).gc))
|
||||
|
||||
#define tsvalue(o) check_exp(ttisstring(o), gco2ts(val_(o).gc))
|
||||
|
||||
#define setsvalue(L,obj,x) \
|
||||
{ TValue *io = (obj); TString *x_ = (x); \
|
||||
val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \
|
||||
checkliveness(L,io); }
|
||||
|
||||
/* set a string to the stack */
|
||||
#define setsvalue2s(L,o,s) setsvalue(L,s2v(o),s)
|
||||
|
||||
/* set a string to a new object */
|
||||
#define setsvalue2n setsvalue
|
||||
|
||||
|
||||
/*
|
||||
** Header for a string value.
|
||||
*/
|
||||
typedef struct TString {
|
||||
CommonHeader;
|
||||
lu_byte extra; /* reserved words for short strings; "has hash" for longs */
|
||||
lu_byte shrlen; /* length for short strings, 0xFF for long strings */
|
||||
unsigned int hash;
|
||||
union {
|
||||
size_t lnglen; /* length for long strings */
|
||||
struct TString *hnext; /* linked list for hash table */
|
||||
} u;
|
||||
char contents[1];
|
||||
} TString;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Get the actual string (array of bytes) from a 'TString'. (Generic
|
||||
** version and specialized versions for long and short strings.)
|
||||
*/
|
||||
#define getstr(ts) ((ts)->contents)
|
||||
#define getlngstr(ts) check_exp((ts)->shrlen == 0xFF, (ts)->contents)
|
||||
#define getshrstr(ts) check_exp((ts)->shrlen != 0xFF, (ts)->contents)
|
||||
|
||||
|
||||
/* get string length from 'TString *s' */
|
||||
#define tsslen(s) \
|
||||
((s)->shrlen != 0xFF ? (s)->shrlen : (s)->u.lnglen)
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Userdata
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** Light userdata should be a variant of userdata, but for compatibility
|
||||
** reasons they are also different types.
|
||||
*/
|
||||
#define LUA_VLIGHTUSERDATA makevariant(LUA_TLIGHTUSERDATA, 0)
|
||||
|
||||
#define LUA_VUSERDATA makevariant(LUA_TUSERDATA, 0)
|
||||
|
||||
#define ttislightuserdata(o) checktag((o), LUA_VLIGHTUSERDATA)
|
||||
#define ttisfulluserdata(o) checktag((o), ctb(LUA_VUSERDATA))
|
||||
|
||||
#define pvalue(o) check_exp(ttislightuserdata(o), val_(o).p)
|
||||
#define uvalue(o) check_exp(ttisfulluserdata(o), gco2u(val_(o).gc))
|
||||
|
||||
#define pvalueraw(v) ((v).p)
|
||||
|
||||
#define setpvalue(obj,x) \
|
||||
{ TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_VLIGHTUSERDATA); }
|
||||
|
||||
#define setuvalue(L,obj,x) \
|
||||
{ TValue *io = (obj); Udata *x_ = (x); \
|
||||
val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VUSERDATA)); \
|
||||
checkliveness(L,io); }
|
||||
|
||||
|
||||
/* Ensures that addresses after this type are always fully aligned. */
|
||||
typedef union UValue {
|
||||
TValue uv;
|
||||
LUAI_MAXALIGN; /* ensures maximum alignment for udata bytes */
|
||||
} UValue;
|
||||
|
||||
|
||||
/*
|
||||
** Header for userdata with user values;
|
||||
** memory area follows the end of this structure.
|
||||
*/
|
||||
typedef struct Udata {
|
||||
CommonHeader;
|
||||
unsigned short nuvalue; /* number of user values */
|
||||
size_t len; /* number of bytes */
|
||||
struct Table *metatable;
|
||||
GCObject *gclist;
|
||||
UValue uv[1]; /* user values */
|
||||
} Udata;
|
||||
|
||||
|
||||
/*
|
||||
** Header for userdata with no user values. These userdata do not need
|
||||
** to be gray during GC, and therefore do not need a 'gclist' field.
|
||||
** To simplify, the code always use 'Udata' for both kinds of userdata,
|
||||
** making sure it never accesses 'gclist' on userdata with no user values.
|
||||
** This structure here is used only to compute the correct size for
|
||||
** this representation. (The 'bindata' field in its end ensures correct
|
||||
** alignment for binary data following this header.)
|
||||
*/
|
||||
typedef struct Udata0 {
|
||||
CommonHeader;
|
||||
unsigned short nuvalue; /* number of user values */
|
||||
size_t len; /* number of bytes */
|
||||
struct Table *metatable;
|
||||
union {LUAI_MAXALIGN;} bindata;
|
||||
} Udata0;
|
||||
|
||||
|
||||
/* compute the offset of the memory area of a userdata */
|
||||
#define udatamemoffset(nuv) \
|
||||
((nuv) == 0 ? offsetof(Udata0, bindata) \
|
||||
: offsetof(Udata, uv) + (sizeof(UValue) * (nuv)))
|
||||
|
||||
/* get the address of the memory block inside 'Udata' */
|
||||
#define getudatamem(u) (cast_charp(u) + udatamemoffset((u)->nuvalue))
|
||||
|
||||
/* compute the size of a userdata */
|
||||
#define sizeudata(nuv,nb) (udatamemoffset(nuv) + (nb))
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Prototypes
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#define LUA_VPROTO makevariant(LUA_TPROTO, 0)
|
||||
|
||||
|
||||
/*
|
||||
** Description of an upvalue for function prototypes
|
||||
*/
|
||||
typedef struct Upvaldesc {
|
||||
TString *name; /* upvalue name (for debug information) */
|
||||
lu_byte instack; /* whether it is in stack (register) */
|
||||
lu_byte idx; /* index of upvalue (in stack or in outer function's list) */
|
||||
lu_byte kind; /* kind of corresponding variable */
|
||||
} Upvaldesc;
|
||||
|
||||
|
||||
/*
|
||||
** Description of a local variable for function prototypes
|
||||
** (used for debug information)
|
||||
*/
|
||||
typedef struct LocVar {
|
||||
TString *varname;
|
||||
int startpc; /* first point where variable is active */
|
||||
int endpc; /* first point where variable is dead */
|
||||
} LocVar;
|
||||
|
||||
|
||||
/*
|
||||
** Associates the absolute line source for a given instruction ('pc').
|
||||
** The array 'lineinfo' gives, for each instruction, the difference in
|
||||
** lines from the previous instruction. When that difference does not
|
||||
** fit into a byte, Lua saves the absolute line for that instruction.
|
||||
** (Lua also saves the absolute line periodically, to speed up the
|
||||
** computation of a line number: we can use binary search in the
|
||||
** absolute-line array, but we must traverse the 'lineinfo' array
|
||||
** linearly to compute a line.)
|
||||
*/
|
||||
typedef struct AbsLineInfo {
|
||||
int pc;
|
||||
int line;
|
||||
} AbsLineInfo;
|
||||
|
||||
/*
|
||||
** Function Prototypes
|
||||
*/
|
||||
typedef struct Proto {
|
||||
CommonHeader;
|
||||
lu_byte numparams; /* number of fixed (named) parameters */
|
||||
lu_byte is_vararg;
|
||||
lu_byte maxstacksize; /* number of registers needed by this function */
|
||||
int sizeupvalues; /* size of 'upvalues' */
|
||||
int sizek; /* size of 'k' */
|
||||
int sizecode;
|
||||
int sizelineinfo;
|
||||
int sizep; /* size of 'p' */
|
||||
int sizelocvars;
|
||||
int sizeabslineinfo; /* size of 'abslineinfo' */
|
||||
int linedefined; /* debug information */
|
||||
int lastlinedefined; /* debug information */
|
||||
TValue *k; /* constants used by the function */
|
||||
Instruction *code; /* opcodes */
|
||||
struct Proto **p; /* functions defined inside the function */
|
||||
Upvaldesc *upvalues; /* upvalue information */
|
||||
ls_byte *lineinfo; /* information about source lines (debug information) */
|
||||
AbsLineInfo *abslineinfo; /* idem */
|
||||
LocVar *locvars; /* information about local variables (debug information) */
|
||||
TString *source; /* used for debug information */
|
||||
GCObject *gclist;
|
||||
} Proto;
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Functions
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#define LUA_VUPVAL makevariant(LUA_TUPVAL, 0)
|
||||
|
||||
|
||||
/* Variant tags for functions */
|
||||
#define LUA_VLCL makevariant(LUA_TFUNCTION, 0) /* Lua closure */
|
||||
#define LUA_VLCF makevariant(LUA_TFUNCTION, 1) /* light C function */
|
||||
#define LUA_VCCL makevariant(LUA_TFUNCTION, 2) /* C closure */
|
||||
|
||||
#define ttisfunction(o) checktype(o, LUA_TFUNCTION)
|
||||
#define ttisLclosure(o) checktag((o), ctb(LUA_VLCL))
|
||||
#define ttislcf(o) checktag((o), LUA_VLCF)
|
||||
#define ttisCclosure(o) checktag((o), ctb(LUA_VCCL))
|
||||
#define ttisclosure(o) (ttisLclosure(o) || ttisCclosure(o))
|
||||
|
||||
|
||||
#define isLfunction(o) ttisLclosure(o)
|
||||
|
||||
#define clvalue(o) check_exp(ttisclosure(o), gco2cl(val_(o).gc))
|
||||
#define clLvalue(o) check_exp(ttisLclosure(o), gco2lcl(val_(o).gc))
|
||||
#define fvalue(o) check_exp(ttislcf(o), val_(o).f)
|
||||
#define clCvalue(o) check_exp(ttisCclosure(o), gco2ccl(val_(o).gc))
|
||||
|
||||
#define fvalueraw(v) ((v).f)
|
||||
|
||||
#define setclLvalue(L,obj,x) \
|
||||
{ TValue *io = (obj); LClosure *x_ = (x); \
|
||||
val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VLCL)); \
|
||||
checkliveness(L,io); }
|
||||
|
||||
#define setclLvalue2s(L,o,cl) setclLvalue(L,s2v(o),cl)
|
||||
|
||||
#define setfvalue(obj,x) \
|
||||
{ TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_VLCF); }
|
||||
|
||||
#define setclCvalue(L,obj,x) \
|
||||
{ TValue *io = (obj); CClosure *x_ = (x); \
|
||||
val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VCCL)); \
|
||||
checkliveness(L,io); }
|
||||
|
||||
|
||||
/*
|
||||
** Upvalues for Lua closures
|
||||
*/
|
||||
typedef struct UpVal {
|
||||
CommonHeader;
|
||||
union {
|
||||
TValue *p; /* points to stack or to its own value */
|
||||
ptrdiff_t offset; /* used while the stack is being reallocated */
|
||||
} v;
|
||||
union {
|
||||
struct { /* (when open) */
|
||||
struct UpVal *next; /* linked list */
|
||||
struct UpVal **previous;
|
||||
} open;
|
||||
TValue value; /* the value (when closed) */
|
||||
} u;
|
||||
} UpVal;
|
||||
|
||||
|
||||
|
||||
#define ClosureHeader \
|
||||
CommonHeader; lu_byte nupvalues; GCObject *gclist
|
||||
|
||||
typedef struct CClosure {
|
||||
ClosureHeader;
|
||||
lua_CFunction f;
|
||||
TValue upvalue[1]; /* list of upvalues */
|
||||
} CClosure;
|
||||
|
||||
|
||||
typedef struct LClosure {
|
||||
ClosureHeader;
|
||||
struct Proto *p;
|
||||
UpVal *upvals[1]; /* list of upvalues */
|
||||
} LClosure;
|
||||
|
||||
|
||||
typedef union Closure {
|
||||
CClosure c;
|
||||
LClosure l;
|
||||
} Closure;
|
||||
|
||||
|
||||
#define getproto(o) (clLvalue(o)->p)
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
/*
|
||||
** {==================================================================
|
||||
** Tables
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#define LUA_VTABLE makevariant(LUA_TTABLE, 0)
|
||||
|
||||
#define ttistable(o) checktag((o), ctb(LUA_VTABLE))
|
||||
|
||||
#define hvalue(o) check_exp(ttistable(o), gco2t(val_(o).gc))
|
||||
|
||||
#define sethvalue(L,obj,x) \
|
||||
{ TValue *io = (obj); Table *x_ = (x); \
|
||||
val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VTABLE)); \
|
||||
checkliveness(L,io); }
|
||||
|
||||
#define sethvalue2s(L,o,h) sethvalue(L,s2v(o),h)
|
||||
|
||||
|
||||
/*
|
||||
** Nodes for Hash tables: A pack of two TValue's (key-value pairs)
|
||||
** plus a 'next' field to link colliding entries. The distribution
|
||||
** of the key's fields ('key_tt' and 'key_val') not forming a proper
|
||||
** 'TValue' allows for a smaller size for 'Node' both in 4-byte
|
||||
** and 8-byte alignments.
|
||||
*/
|
||||
typedef union Node {
|
||||
struct NodeKey {
|
||||
TValuefields; /* fields for value */
|
||||
lu_byte key_tt; /* key type */
|
||||
int next; /* for chaining */
|
||||
Value key_val; /* key value */
|
||||
} u;
|
||||
TValue i_val; /* direct access to node's value as a proper 'TValue' */
|
||||
} Node;
|
||||
|
||||
|
||||
/* copy a value into a key */
|
||||
#define setnodekey(L,node,obj) \
|
||||
{ Node *n_=(node); const TValue *io_=(obj); \
|
||||
n_->u.key_val = io_->value_; n_->u.key_tt = io_->tt_; \
|
||||
checkliveness(L,io_); }
|
||||
|
||||
|
||||
/* copy a value from a key */
|
||||
#define getnodekey(L,obj,node) \
|
||||
{ TValue *io_=(obj); const Node *n_=(node); \
|
||||
io_->value_ = n_->u.key_val; io_->tt_ = n_->u.key_tt; \
|
||||
checkliveness(L,io_); }
|
||||
|
||||
|
||||
/*
|
||||
** About 'alimit': if 'isrealasize(t)' is true, then 'alimit' is the
|
||||
** real size of 'array'. Otherwise, the real size of 'array' is the
|
||||
** smallest power of two not smaller than 'alimit' (or zero iff 'alimit'
|
||||
** is zero); 'alimit' is then used as a hint for #t.
|
||||
*/
|
||||
|
||||
#define BITRAS (1 << 7)
|
||||
#define isrealasize(t) (!((t)->flags & BITRAS))
|
||||
#define setrealasize(t) ((t)->flags &= cast_byte(~BITRAS))
|
||||
#define setnorealasize(t) ((t)->flags |= BITRAS)
|
||||
|
||||
|
||||
typedef struct Table {
|
||||
CommonHeader;
|
||||
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
|
||||
lu_byte lsizenode; /* log2 of size of 'node' array */
|
||||
unsigned int alimit; /* "limit" of 'array' array */
|
||||
TValue *array; /* array part */
|
||||
Node *node;
|
||||
Node *lastfree; /* any free position is before this position */
|
||||
struct Table *metatable;
|
||||
GCObject *gclist;
|
||||
} Table;
|
||||
|
||||
|
||||
/*
|
||||
** Macros to manipulate keys inserted in nodes
|
||||
*/
|
||||
#define keytt(node) ((node)->u.key_tt)
|
||||
#define keyval(node) ((node)->u.key_val)
|
||||
|
||||
#define keyisnil(node) (keytt(node) == LUA_TNIL)
|
||||
#define keyisinteger(node) (keytt(node) == LUA_VNUMINT)
|
||||
#define keyival(node) (keyval(node).i)
|
||||
#define keyisshrstr(node) (keytt(node) == ctb(LUA_VSHRSTR))
|
||||
#define keystrval(node) (gco2ts(keyval(node).gc))
|
||||
|
||||
#define setnilkey(node) (keytt(node) = LUA_TNIL)
|
||||
|
||||
#define keyiscollectable(n) (keytt(n) & BIT_ISCOLLECTABLE)
|
||||
|
||||
#define gckey(n) (keyval(n).gc)
|
||||
#define gckeyN(n) (keyiscollectable(n) ? gckey(n) : NULL)
|
||||
|
||||
|
||||
/*
|
||||
** Dead keys in tables have the tag DEADKEY but keep their original
|
||||
** gcvalue. This distinguishes them from regular keys but allows them to
|
||||
** be found when searched in a special way. ('next' needs that to find
|
||||
** keys removed from a table during a traversal.)
|
||||
*/
|
||||
#define setdeadkey(node) (keytt(node) = LUA_TDEADKEY)
|
||||
#define keyisdead(node) (keytt(node) == LUA_TDEADKEY)
|
||||
|
||||
/* }================================================================== */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** 'module' operation for hashing (size is always a power of 2)
|
||||
*/
|
||||
#define lmod(s,size) \
|
||||
(check_exp((size&(size-1))==0, (cast_int((s) & ((size)-1)))))
|
||||
|
||||
|
||||
#define twoto(x) (1<<(x))
|
||||
#define sizenode(t) (twoto((t)->lsizenode))
|
||||
|
||||
|
||||
/* size of buffer for 'luaO_utf8esc' function */
|
||||
#define UTF8BUFFSZ 8
|
||||
|
||||
LUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x);
|
||||
LUAI_FUNC int luaO_ceillog2 (unsigned int x);
|
||||
LUAI_FUNC int luaO_rawarith (lua_State *L, int op, const TValue *p1,
|
||||
const TValue *p2, TValue *res);
|
||||
LUAI_FUNC void luaO_arith (lua_State *L, int op, const TValue *p1,
|
||||
const TValue *p2, StkId res);
|
||||
LUAI_FUNC size_t luaO_str2num (const char *s, TValue *o);
|
||||
LUAI_FUNC int luaO_hexavalue (int c);
|
||||
LUAI_FUNC void luaO_tostring (lua_State *L, TValue *obj);
|
||||
LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt,
|
||||
va_list argp);
|
||||
LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...);
|
||||
LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t srclen);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
104
crates/voltex_script/lua/lopcodes.c
Normal file
104
crates/voltex_script/lua/lopcodes.c
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
** $Id: lopcodes.c $
|
||||
** Opcodes for Lua virtual machine
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
|
||||
#define lopcodes_c
|
||||
#define LUA_CORE
|
||||
|
||||
#include "lprefix.h"
|
||||
|
||||
|
||||
#include "lopcodes.h"
|
||||
|
||||
|
||||
/* ORDER OP */
|
||||
|
||||
LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
|
||||
/* MM OT IT T A mode opcode */
|
||||
opmode(0, 0, 0, 0, 1, iABC) /* OP_MOVE */
|
||||
,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADI */
|
||||
,opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADF */
|
||||
,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADK */
|
||||
,opmode(0, 0, 0, 0, 1, iABx) /* OP_LOADKX */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADFALSE */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_LFALSESKIP */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADTRUE */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_LOADNIL */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETUPVAL */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETUPVAL */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABUP */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETTABLE */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETI */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETFIELD */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABUP */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETTABLE */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETI */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_SETFIELD */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_NEWTABLE */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SELF */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDI */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADDK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUBK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_MULK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_MODK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_POWK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIVK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIVK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BANDK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BORK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXORK */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHRI */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHLI */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADD */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUB */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_MUL */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_MOD */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_POW */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_DIV */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_IDIV */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BAND */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BOR */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXOR */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHL */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHR */
|
||||
,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBIN */
|
||||
,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINI*/
|
||||
,opmode(1, 0, 0, 0, 0, iABC) /* OP_MMBINK*/
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_UNM */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_BNOT */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_NOT */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_LEN */
|
||||
,opmode(0, 0, 0, 0, 1, iABC) /* OP_CONCAT */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_CLOSE */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_TBC */
|
||||
,opmode(0, 0, 0, 0, 0, isJ) /* OP_JMP */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQ */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_LT */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_LE */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQK */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_EQI */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_LTI */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_LEI */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_GTI */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_GEI */
|
||||
,opmode(0, 0, 0, 1, 0, iABC) /* OP_TEST */
|
||||
,opmode(0, 0, 0, 1, 1, iABC) /* OP_TESTSET */
|
||||
,opmode(0, 1, 1, 0, 1, iABC) /* OP_CALL */
|
||||
,opmode(0, 1, 1, 0, 1, iABC) /* OP_TAILCALL */
|
||||
,opmode(0, 0, 1, 0, 0, iABC) /* OP_RETURN */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN0 */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_RETURN1 */
|
||||
,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORLOOP */
|
||||
,opmode(0, 0, 0, 0, 1, iABx) /* OP_FORPREP */
|
||||
,opmode(0, 0, 0, 0, 0, iABx) /* OP_TFORPREP */
|
||||
,opmode(0, 0, 0, 0, 0, iABC) /* OP_TFORCALL */
|
||||
,opmode(0, 0, 0, 0, 1, iABx) /* OP_TFORLOOP */
|
||||
,opmode(0, 0, 1, 0, 0, iABC) /* OP_SETLIST */
|
||||
,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */
|
||||
,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */
|
||||
,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */
|
||||
,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user