1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//! A leak gate for the debug renderer that `PhysicsWorld::debug_lines` creates and destroys on
//! every call: 2000 measured calls into one reused buffer against a 4 MiB threshold. Each
//! renderer builds Jolt's shared debug geometry (spheres, boxes, capsules, cylinders), so one
//! leaked renderer per call would grow the process far past the threshold. The scene holds a
//! heightfield and a mesh, whose own debug geometry Jolt caches in the shape on the first draw.
//!
//! It measures the private bytes of the process (Windows `K32GetProcessMemoryInfo`), because
//! the renderer is allocated by C++, which a Rust global allocator does not see. The file holds
//! exactly one test, so its binary runs alone and no parallel test disturbs the counter.
#![cfg(all(windows, feature = "debug-renderer"))]
mod common;
use common::memory::private_bytes;
use common::*;
use oxijolt::*;
/// Enough to build the heightfield's and the mesh's cached debug geometry and settle the
/// allocator.
const WARM_UP_CALLS: usize = 200;
const MEASURED_CALLS: usize = 2000;
const MAX_GROWTH: usize = 4 * 1024 * 1024;
#[test]
fn debug_lines_do_not_leak() {
let mut scene = wireframe_scene();
// A static mesh near the origin: Jolt builds its debug geometry once, on the first draw.
let (vertices, triangles) = common::meshes::grid(8, 1.0, |x, z| 0.1 * (x + z).sin());
let (mesh, _) = Shape::new_mesh(&vertices, &triangles).unwrap();
scene
.world
.create_body(
&mesh,
&BodySettings::new_static().position(RVec3::new(-8.0, 0.5, -8.0)),
)
.unwrap();
let settings = DebugLineSettings::new(RVec3::ZERO, 20.0);
let filter = QueryFilter::new();
let mut lines = DebugLines::new();
let mut run_calls = |calls: usize| {
let mut total = 0;
for _ in 0..calls {
scene
.world
.debug_lines_into(&settings, &filter, &mut lines)
.unwrap();
total += lines.lines().len();
}
total
};
assert!(run_calls(WARM_UP_CALLS) > 0);
let before = private_bytes();
assert!(run_calls(MEASURED_CALLS) > 0);
let after = private_bytes();
let growth = after.saturating_sub(before);
eprintln!("private bytes before {before}, after {after}, growth {growth}");
assert!(
growth < MAX_GROWTH,
"private bytes grew by {growth} (before {before}, after {after}): the debug renderer leaks"
);
}