#![cfg(windows)]
mod common;
use std::ptr::null;
use common::memory::private_bytes;
use common::raw_system;
use common::rollback::{Inputs, RollbackScene};
use oxijolt::*;
use oxijolt_sys::*;
const WARM_UP_ROUNDS: usize = 5000;
const BLOCK_ROUNDS: usize = 1000;
const BLOCKS: usize = 7;
const MAX_ROUND_GROWTH: usize = 100;
const MAX_TOTAL_GROWTH: usize = 4 * 1024 * 1024;
struct Rounds {
scene: RollbackScene,
full: WorldState,
movable: WorldState,
some: WorldState,
}
impl Rounds {
fn new() -> Self {
let mut scene = RollbackScene::new(1);
for _ in 0..10 {
scene.tick(Inputs::PLAYED);
}
Self {
scene,
full: WorldState::new(),
movable: WorldState::new(),
some: WorldState::new(),
}
}
fn round(&mut self) {
let world = &mut self.scene.world;
let some = [self.scene.bodies[1], self.scene.sleeper];
world
.save_state_into(BodySelection::All, &mut self.full)
.unwrap();
world
.save_state_into(BodySelection::Movable, &mut self.movable)
.unwrap();
world
.save_state_into(BodySelection::Only(&some), &mut self.some)
.unwrap();
world
.restore_state_of(&self.full, BodySelection::Only(&some))
.unwrap();
world
.restore_state_of(&self.movable, BodySelection::Movable)
.unwrap();
world
.restore_state_of(&self.some, BodySelection::All)
.unwrap();
}
}
fn block_growth(mut round: impl FnMut()) -> Vec<usize> {
(0..BLOCKS)
.map(|_| {
let before = private_bytes();
for _ in 0..BLOCK_ROUNDS {
round();
}
private_bytes().saturating_sub(before)
})
.collect()
}
fn median(blocks: &[usize]) -> usize {
let mut sorted = blocks.to_vec();
sorted.sort_unstable();
sorted[sorted.len() / 2]
}
#[test]
fn state_buffers_and_filtered_restores_do_not_leak() {
let mut rounds = Rounds::new();
for _ in 0..WARM_UP_ROUNDS {
rounds.round();
}
let blocks = block_growth(|| rounds.round());
eprintln!("state buffers: growth per block of {BLOCK_ROUNDS} rounds: {blocks:?}");
let (median_growth, total): (usize, usize) = (median(&blocks), blocks.iter().sum());
assert!(
median_growth < MAX_ROUND_GROWTH * BLOCK_ROUNDS,
"median block growth {median_growth} bytes: a state buffer leaks ({blocks:?})"
);
assert!(
total < MAX_TOTAL_GROWTH,
"total growth {total} bytes: a state buffer leaks ({blocks:?})"
);
let system = unsafe { raw_system() };
assert!(!system.is_null());
let blocks = block_growth(|| {
rounds.round();
unsafe {
let recorder = JPH_StateRecorder_Create();
JPH_PhysicsSystem_SaveState(system, recorder, JPH_StateRecorderState_Bodies, null(), 0);
}
});
eprintln!("control: growth per block of {BLOCK_ROUNDS} rounds: {blocks:?}");
unsafe { JPH_PhysicsSystem_Destroy(system) };
assert!(
median(&blocks) >= MAX_ROUND_GROWTH * BLOCK_ROUNDS,
"the control forgot a recorder per round but the median block grew only by {} ({blocks:?})",
median(&blocks)
);
}