From b368912c7809985ec423fe80075bddba1cc86b89 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 13:32:18 +0300 Subject: [PATCH] nav, physics: Recast's and Detour's memory counted (nav_heap_bytes / _peak / _allocs, a counted allocator registered at load, tile bytes staged from it); package tests that crossing the map and back holds the native bytes The user's walk showed the footprint rising outside the Ludic heap. Jolt's bytes were already counted; the navmesh's were not, so nothing could see them. Now: - ludic.nav nav_heap_test: fifty crossings of a four-tile map by the resident index hold the tiles in and the native bytes to the first crossing's; a thousand crowd walkers in and out grow nothing; a reset gives back every byte the mesh took. - ludic.physics cross_heap_test: a 1 km map of 64 m chunks kept to a ring round a player crossing corner to corner and back, each chunk a heightfield, twelve owned posts and six owned scaled hulls as the game makes them: six more crossings hold the bodies, the shapes and Jolt's bytes and peak exactly. (A post made as an offset of a cylinder, with only the offset owned, leaked the cylinder every time: the game's solid_pillar owns its cylinder directly.) lib/macos-arm64 rebuilt; lib/windows-x64 needs native/build.sh run on the PC for the new exports. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.nav/README.md | 6 ++ .../lib/macos-arm64/libludicnav.dylib | 4 +- packages/ludic.nav/native.ludic | 8 ++ packages/ludic.nav/native/shim/nav_alloc.inl | 42 +++++++++ .../ludic.nav/native/shim/nav_resident.inl | 4 +- packages/ludic.nav/native/shim/nav_shim.cpp | 4 + packages/ludic.nav/native/shim/nav_tiles.inl | 7 +- packages/ludic.nav/tests/nav_heap_test.ludic | 73 ++++++++++++++++ .../ludic.physics/tests/cross_heap_test.ludic | 85 +++++++++++++++++++ 9 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 packages/ludic.nav/native/shim/nav_alloc.inl create mode 100644 packages/ludic.nav/tests/nav_heap_test.ludic create mode 100644 packages/ludic.physics/tests/cross_heap_test.ludic diff --git a/packages/ludic.nav/README.md b/packages/ludic.nav/README.md index 7c2a76a8..2b442a9c 100644 --- a/packages/ludic.nav/README.md +++ b/packages/ludic.nav/README.md @@ -72,6 +72,12 @@ across the seams, a tile under water with no polygons, and the set saved and loa and the shim (`native/shim/nav_shim.cpp`) into `lib//` - the same script on the Mac and on the PC (Git Bash, the LLVM installer's clang). `native/LICENSE-recastnavigation` ships with it. +Every byte Recast and Detour allocate - the tiles in, the queries, the crowds - goes through a counted +allocator registered when the library loads (`native/shim/nav_alloc.inl`, as ludic.physics counts +Jolt's): `nav_heap_bytes()`, `nav_heap_peak()` and `nav_heap_allocs()`. A tile's bytes are staged from +it too, since Detour frees them with the tile. `tests/nav_heap_test` holds fifty crossings of the map +and a thousand crowd walkers to the bytes of the first, and a reset to the bytes before the mesh. + ## Crowds (phase 18) `nav_crowd_start(kind, max, max_radius)` puts a DetourCrowd on a kind's mesh, with the mesh's tastes as diff --git a/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib b/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib index d2f01a3d..266f7f39 100755 --- a/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib +++ b/packages/ludic.nav/lib/macos-arm64/libludicnav.dylib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ef7c47b0bf408ed20ac01c4db36d814dbd87b7bb00e9eee44814f416deb21bc -size 199024 +oid sha256:2bb39b8c7f63a50bf8f6589e5120a4879c5895ce571e65e346d939fec8be28aa +size 216176 diff --git a/packages/ludic.nav/native.ludic b/packages/ludic.nav/native.ludic index 8f11a053..66bb1dbd 100644 --- a/packages/ludic.nav/native.ludic +++ b/packages/ludic.nav/native.ludic @@ -37,3 +37,11 @@ extern function nvc_index_size(h: pointer, i: int) -> int = "nav_index_size" extern function nvc_index_in(h: pointer, i: int, data: pointer, size: int) -> int = "nav_index_in" extern function nvc_index_out(h: pointer, i: int) -> int = "nav_index_out" extern function nvc_load_staged(buf: pointer, size: int) -> pointer = "nav_load_staged" +extern function nvc_heap_bytes() -> long = "nav_heap_bytes" +extern function nvc_heap_peak() -> long = "nav_heap_peak" +extern function nvc_heap_allocs() -> long = "nav_heap_allocs" +# the bytes Recast and Detour hold now (every tile in, the queries, the crowds), the most they ever +# held, and how many blocks they asked for: counted in the shim, apart from the game's heap +export function nav_heap_bytes() -> long { return nvc_heap_bytes() } +export function nav_heap_peak() -> long { return nvc_heap_peak() } +export function nav_heap_allocs() -> long { return nvc_heap_allocs() } diff --git a/packages/ludic.nav/native/shim/nav_alloc.inl b/packages/ludic.nav/native/shim/nav_alloc.inl new file mode 100644 index 00000000..d1bb87d5 --- /dev/null +++ b/packages/ludic.nav/native/shim/nav_alloc.inl @@ -0,0 +1,42 @@ +// nav_alloc.inl - Recast's and Detour's allocations counted apart from the game's (plan 25), as +// ludic.physics counts Jolt's: libc's malloc underneath, each block carrying its size in a 16-byte +// header, so the tiles, queries and crowds are measured exactly. Registered when the library loads. +#include + +namespace { +constexpr size_t kNavHead = 16; +std::atomic g_nav_live{0}, g_nav_peak{0}, g_nav_allocs{0}; + +void *nav_heap_alloc(size_t n) { + char *b = static_cast(std::malloc(n + kNavHead)); + if (!b) return nullptr; + std::memcpy(b, &n, sizeof n); + g_nav_allocs.fetch_add(1, std::memory_order_relaxed); + long long now = g_nav_live.fetch_add((long long)n, std::memory_order_relaxed) + (long long)n; + long long p = g_nav_peak.load(std::memory_order_relaxed); + while (now > p && !g_nav_peak.compare_exchange_weak(p, now, std::memory_order_relaxed)) {} + return b + kNavHead; +} +void nav_heap_free(void *p) { + if (!p) return; + char *b = static_cast(p) - kNavHead; + size_t n; + std::memcpy(&n, b, sizeof n); + g_nav_live.fetch_sub((long long)n, std::memory_order_relaxed); + std::free(b); +} +void *nav_dt_alloc(size_t n, dtAllocHint) { return nav_heap_alloc(n); } +void *nav_rc_alloc(size_t n, rcAllocHint) { return nav_heap_alloc(n); } + +struct NavHeapRegister { + NavHeapRegister() { + dtAllocSetCustom(nav_dt_alloc, nav_heap_free); + rcAllocSetCustom(nav_rc_alloc, nav_heap_free); + } +} g_nav_heap_register; +} // namespace + +// bytes Recast and Detour hold now, the most they ever held, and how many blocks they asked for +NAV_SHIM long long nav_heap_bytes(void) { return g_nav_live.load(std::memory_order_relaxed); } +NAV_SHIM long long nav_heap_peak(void) { return g_nav_peak.load(std::memory_order_relaxed); } +NAV_SHIM long long nav_heap_allocs(void) { return g_nav_allocs.load(std::memory_order_relaxed); } diff --git a/packages/ludic.nav/native/shim/nav_resident.inl b/packages/ludic.nav/native/shim/nav_resident.inl index d382748d..4a68a9c5 100644 --- a/packages/ludic.nav/native/shim/nav_resident.inl +++ b/packages/ludic.nav/native/shim/nav_resident.inl @@ -57,8 +57,8 @@ NAV_SHIM int nav_index_size(void *h, int i) { return static_cast