mod common;
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use common::rollback::{Inputs, RollbackScene};
use oxijolt::*;
struct CountingAllocator;
static ARMED: AtomicBool = AtomicBool::new(false);
static ALLOCATIONS: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
count();
unsafe { System.alloc(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
count();
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
count();
unsafe { System.realloc(ptr, layout, new_size) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
fn count() {
if ARMED.load(Ordering::SeqCst) {
ALLOCATIONS.fetch_add(1, Ordering::SeqCst);
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
const SAVES: usize = 1000;
fn allocations_of(saves: impl FnOnce() -> Result<(), StateError>) -> usize {
ALLOCATIONS.store(0, Ordering::SeqCst);
ARMED.store(true, Ordering::SeqCst);
let saved = saves();
ARMED.store(false, Ordering::SeqCst);
saved.unwrap();
ALLOCATIONS.load(Ordering::SeqCst)
}
fn world_with_characters(count: usize) -> PhysicsWorld {
let mut world = common::world(Vec3::new(0.0, -9.81, 0.0), 1);
let floor = Shape::new_box(Vec3::new(10.0, 0.5, 10.0)).unwrap();
let at = RVec3::new(0.0, -0.5, 0.0);
world
.create_body(&floor, &BodySettings::new_static().position(at))
.unwrap();
let settings = CharacterSettings::humanoid(1.8, 0.3).unwrap();
for i in 0..count {
let feet = RVec3::new(2.0 * i as Real, 0.0, 0.0);
let character = world
.create_character(&settings, feet, Quat::IDENTITY)
.unwrap();
world
.refresh_character_contacts(character, &QueryFilter::new())
.unwrap();
}
world
}
#[test]
fn saving_into_a_used_buffer_allocates_nothing() {
let mut scene = RollbackScene::new(1);
for _ in 0..30 {
scene.tick(Inputs::PLAYED);
}
let selected = [scene.sleeper, scene.platform, scene.floor];
let selections = [
BodySelection::All,
BodySelection::Movable,
BodySelection::Only(&selected),
];
let mut state = WorldState::new();
for &selection in &selections {
scene.world.save_state_into(selection, &mut state).unwrap();
}
let allocations = allocations_of(|| {
for save in 0..SAVES {
let selection = selections[save % selections.len()];
scene.world.save_state_into(selection, &mut state)?;
}
Ok(())
});
assert_eq!(allocations, 0, "{SAVES} saves into a used buffer allocated");
scene.world.restore_state(&state).unwrap();
let mut state = WorldState::new();
scene
.world
.save_state_into(BodySelection::All, &mut state)
.unwrap();
let allocations = allocations_of(|| {
scene
.world
.save_state_into(BodySelection::Movable, &mut state)?;
scene
.world
.save_state_into(BodySelection::Only(&selected), &mut state)
});
assert_eq!(allocations, 0, "selected saves after a full save allocated");
let (mut two, one) = (world_with_characters(2), world_with_characters(1));
let mut state = WorldState::new();
two.save_state_into(BodySelection::All, &mut state).unwrap();
let allocations = allocations_of(|| {
one.save_state_into(BodySelection::All, &mut state)?;
two.save_state_into(BodySelection::All, &mut state)
});
assert_eq!(
allocations, 0,
"saves of two, one and two characters allocated"
);
two.restore_state(&state).unwrap();
}