app: unload stream-probe cells before map transitions tear them down

This commit is contained in:
mchughalex 2026-07-22 10:00:37 +01:00
parent 87b27dc2a9
commit 70a7c55183
3 changed files with 56 additions and 6 deletions

View file

@ -416,6 +416,26 @@ extern "C" REX_FUNC(sub_8247B3A0) {
(list_mode != 0 && list_count != 0) || cell_size > 4096) {
return;
}
// Loading screens and the frontend stream one world set out and another
// in, then destroy the departing collection objects. Any probe record
// kept in the gather output here stays loaded past that teardown and its
// content leaks into the arriving map (the differ cannot unload a record
// whose collection object is already gone - the ProcessEntries sanitizer
// below drops such descriptors without an unload). Leave the output
// untouched so probe cells unload through the same differ passes as the
// departing vanilla set while their collections are still live, and drop
// the caches: their neighbourhood is the departing map's. The in-game
// pause menu keeps the world resident and reports false; it merges the
// cache below instead.
if (skate3::native_scene::LoadingOrFrontendActive()) {
if (debug && (g_probe_cache[0].count > 0 || g_probe_cache[1].count > 0)) {
REXLOG_INFO(
"draw_distance: stream probe caches dropped (loading/frontend)");
}
g_probe_cache[0] = {};
g_probe_cache[1] = {};
return;
}
ProbeRecord records[192];
size_t record_count = 0;
@ -499,8 +519,12 @@ extern "C" REX_FUNC(sub_8247B3A0) {
const bool set_resident = desc_total > 0 && desc_loaded >= desc_total;
const bool in_gameplay =
rex::kernel::guest_presence::GameplayContextValue() == 1;
if (cache_valid || (cache_usable && cache.count > 0 &&
(!set_resident || !in_gameplay))) {
// An empty vanilla gather is the game clearing this focus (teardown or a
// transient); never inject on top of it - the differ is about to unload
// everything the focus held.
if (vanilla_count > 0 &&
(cache_valid || (cache_usable && cache.count > 0 &&
(!set_resident || !in_gameplay)))) {
// The neighbourhood cannot have changed (cell membership is quantized
// to the grid): merge the cached probe results instead of re-running
// the gather fan. Cached distances are slightly stale; they are only
@ -525,10 +549,10 @@ extern "C" REX_FUNC(sub_8247B3A0) {
++record_count;
}
}
} else if (!set_resident || !in_gameplay) {
// Initial load, a fresh focus, or outside gameplay (loading screen /
// frontend): let the vanilla set finish streaming first; the fan
// starts on a later cycle.
} else if (vanilla_count == 0 || !set_resident || !in_gameplay) {
// Initial load, a fresh focus, an emptied focus, or outside gameplay:
// let the vanilla set finish streaming first; the fan starts on a
// later cycle.
} else {
// Full probe fan: every grid cell within the probe radius, stepped in
// cell multiples. The 7x7 cap bounds the fan at 48 gather calls; the

View file

@ -6092,6 +6092,23 @@ bool FreecamGuestPose(float out_pos[3]) {
return true;
}
bool LoadingOrFrontendActive() {
if (rex::kernel::guest_presence::GameplayContextValue() != 0) {
return false;
}
// Same publish-staleness window as YieldForMenus: the pause menu keeps
// the world resubmitting perspective scenes behind it, loading screens
// and the frontend stop publishing.
const int64_t last_ns = g_last_publish_ns.load(std::memory_order_relaxed);
if (last_ns < 0) {
return true;
}
const int64_t now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
return now_ns - last_ns >= 300'000'000;
}
// Guest render thread only. Returns true when vp_out/cam_out hold a
// re-timed pose for `now`; false = keep the raw guest pose (no history yet,
// or a cut/teleport snapped).

View file

@ -500,6 +500,15 @@ void GetCapturedSunDir(float out[3]);
// SetViewMatrix override in skate3_native_scene.cpp.
bool FreecamGuestPose(float out_pos[3]);
// True while the guest sits on a loading screen or in the frontend: the
// presence context is out of gameplay AND the world has stopped publishing
// perspective scenes. The in-game pause menu keeps publishing behind the
// menu and reports false. Mirrors the pause-vs-loading distinction in
// YieldForMenus (same 300 ms publish-staleness window). With scene capture
// disabled entirely, publishes never arrive and every out-of-gameplay
// context reports true (the conservative side).
bool LoadingOrFrontendActive();
// The game's per-entity spawn/streaming fade as carried by the item's
// validated character-lighting capture (peds c21.x, defaultcharacter c13.x,
// cacstamp c22.x, vehicle body c20.x, hair strand scale). 1.0 for items