Merge branch 'lang/jolt-heap' into lang/foundations
This commit is contained in:
commit
a52fbc01a9
5 changed files with 115 additions and 4 deletions
BIN
packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib
(Stored with Git LFS)
BIN
packages/ludic.physics/lib/macos-arm64/libludicjolt.dylib
(Stored with Git LFS)
Binary file not shown.
|
|
@ -45,3 +45,10 @@ extern function jph_body_angular_impulse(w: pointer, id: int, x: float, y: float
|
|||
extern function jph_body_spin(w: pointer, id: int, out: pointer) -> void = "jph_body_spin"
|
||||
extern function jph_body_set_pose(w: pointer, id: int, x: float, y: float, z: float, qx: float, qy: float, qz: float, qw: float, vx: float, vy: float, vz: float) -> void = "jph_body_set_pose"
|
||||
extern function jph_shape_heightfield_bspline(tex: pointer, n: int, ox: float, oz: float, cell: float) -> pointer = "jph_shape_heightfield_bspline"
|
||||
extern function jph_heap_bytes() -> long = "jph_heap_bytes"
|
||||
extern function jph_heap_peak() -> long = "jph_heap_peak"
|
||||
extern function jph_heap_allocs() -> long = "jph_heap_allocs"
|
||||
# what Jolt holds now, the most it has held, and the blocks it has asked for (counted in the shim)
|
||||
export function phys_heap_bytes() -> long { return jph_heap_bytes() }
|
||||
export function phys_heap_peak() -> long { return jph_heap_peak() }
|
||||
export function phys_heap_allocs() -> long { return jph_heap_allocs() }
|
||||
|
|
|
|||
68
packages/ludic.physics/native/shim/jph_alloc.inl
Normal file
68
packages/ludic.physics/native/shim/jph_alloc.inl
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// jph_alloc.inl - Jolt's allocations counted apart from the game's (plan 25): libc's malloc underneath,
|
||||
// each block carrying its size in a 16-byte header, so live bytes, the peak and the count are exact.
|
||||
// The fence reads them (extern_weak) to judge Jolt by its plateau, not per frame.
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
constexpr size_t kHead = 16;
|
||||
std::atomic<int64_t> g_live{0}, g_peak{0}, g_allocs{0};
|
||||
|
||||
void heap_add(int64_t n) {
|
||||
int64_t now = g_live.fetch_add(n, std::memory_order_relaxed) + n;
|
||||
int64_t p = g_peak.load(std::memory_order_relaxed);
|
||||
while (now > p && !g_peak.compare_exchange_weak(p, now, std::memory_order_relaxed)) {}
|
||||
}
|
||||
void *heap_alloc(size_t n) {
|
||||
char *b = static_cast<char *>(std::malloc(n + kHead));
|
||||
if (!b) return nullptr;
|
||||
std::memcpy(b, &n, sizeof n);
|
||||
g_allocs.fetch_add(1, std::memory_order_relaxed);
|
||||
heap_add(int64_t(n));
|
||||
return b + kHead;
|
||||
}
|
||||
void heap_free(void *p) {
|
||||
if (!p) return;
|
||||
char *b = static_cast<char *>(p) - kHead;
|
||||
size_t n;
|
||||
std::memcpy(&n, b, sizeof n);
|
||||
heap_add(-int64_t(n));
|
||||
std::free(b);
|
||||
}
|
||||
void *heap_realloc(void *p, size_t, size_t n) {
|
||||
if (!p) return heap_alloc(n);
|
||||
char *b = static_cast<char *>(p) - kHead;
|
||||
size_t old;
|
||||
std::memcpy(&old, b, sizeof old);
|
||||
char *q = static_cast<char *>(std::realloc(b, n + kHead));
|
||||
if (!q) return nullptr;
|
||||
std::memcpy(q, &n, sizeof n);
|
||||
heap_add(int64_t(n) - int64_t(old));
|
||||
return q + kHead;
|
||||
}
|
||||
// aligned: over-asked, the counted block's pointer kept in the word before the aligned one
|
||||
void *heap_aligned(size_t n, size_t align) {
|
||||
if (align < sizeof(void *)) align = sizeof(void *);
|
||||
char *raw = static_cast<char *>(heap_alloc(n + align + sizeof(void *)));
|
||||
if (!raw) return nullptr;
|
||||
uintptr_t at = (reinterpret_cast<uintptr_t>(raw) + sizeof(void *) + align - 1) & ~(uintptr_t(align) - 1);
|
||||
reinterpret_cast<void **>(at)[-1] = raw;
|
||||
return reinterpret_cast<void *>(at);
|
||||
}
|
||||
void heap_aligned_free(void *p) {
|
||||
if (p) heap_free(static_cast<void **>(p)[-1]);
|
||||
}
|
||||
void heap_register() {
|
||||
JPH::Allocate = heap_alloc;
|
||||
JPH::Reallocate = heap_realloc;
|
||||
JPH::Free = heap_free;
|
||||
JPH::AlignedAllocate = heap_aligned;
|
||||
JPH::AlignedFree = heap_aligned_free;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// bytes Jolt holds now, the most it ever held, and how many blocks it has asked for
|
||||
JPH_SHIM int64_t jph_heap_bytes(void) { return g_live.load(std::memory_order_relaxed); }
|
||||
JPH_SHIM int64_t jph_heap_peak(void) { return g_peak.load(std::memory_order_relaxed); }
|
||||
JPH_SHIM int64_t jph_heap_allocs(void) { return g_allocs.load(std::memory_order_relaxed); }
|
||||
|
|
@ -120,10 +120,12 @@ void *keep(const ShapeSettings::ShapeResult &r) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
// Jolt's allocator, factory and types, once per process; 0 when ready
|
||||
#include "jph_alloc.inl"
|
||||
|
||||
// Jolt's allocator (counted, jph_alloc.inl), factory and types, once per process; 0 when ready
|
||||
JPH_SHIM int jph_init(void) {
|
||||
if (g_ready) return 0;
|
||||
RegisterDefaultAllocator();
|
||||
heap_register();
|
||||
Factory::sInstance = new Factory();
|
||||
RegisterTypes();
|
||||
g_ready = true;
|
||||
|
|
|
|||
34
packages/ludic.physics/tests/jolt_heap_test.ludic
Normal file
34
packages/ludic.physics/tests/jolt_heap_test.ludic
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# jolt_heap_test.ludic - Jolt's own heap, counted in the shim (plan 25): a ground coming and going a
|
||||
# thousand times holds what it held after the first hundred, and a world closed gives back what it took
|
||||
import "ludic.physics"
|
||||
import "ludic.base"
|
||||
program JoltHeapTest {
|
||||
numbers float
|
||||
function cycle(physics_st: mut PhysicsState, h: []float, k: int) -> void {
|
||||
let s = phys_heightfield(physics_st, h, 33, float(k % 7) * 64.0, 0.0, 2.0)
|
||||
let b = phys_ground_add(physics_st, s)
|
||||
phys_step(physics_st)
|
||||
phys_remove(physics_st, b)
|
||||
phys_shape_free(physics_st, s)
|
||||
phys_settle(physics_st)
|
||||
}
|
||||
|
||||
test "a ground in and out a thousand times holds Jolt's heap where it settled, and closing gives it back" (physics_st: mut PhysicsState) {
|
||||
expect(phys_open(physics_st, 64, 1))
|
||||
phys_close(physics_st)
|
||||
let before = phys_heap_bytes() # the factory and the types, made once per process
|
||||
expect(phys_open(physics_st, 64, 1))
|
||||
expect(phys_heap_bytes() > before)
|
||||
let h = floats(33 * 33)
|
||||
for k in 0 .. 100 { cycle(physics_st, h, k) }
|
||||
let held = phys_heap_bytes()
|
||||
let peak = phys_heap_peak()
|
||||
for k in 0 .. 1000 { cycle(physics_st, h, k) }
|
||||
print(`jolt heap: {held} bytes held after 100 grounds, {phys_heap_bytes()} after 1100, peak {phys_heap_peak()}, {phys_heap_allocs()} blocks asked for`)
|
||||
expect(phys_heap_bytes() == held)
|
||||
expect(phys_heap_peak() == peak)
|
||||
phys_close(physics_st)
|
||||
print(`jolt heap: {before} bytes before the world, {phys_heap_bytes()} after it closed`)
|
||||
expect(phys_heap_bytes() == before)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue