99
1010#include " StdInc.h"
1111#include < optional>
12+ #include < cstddef>
13+ #include < cstdint>
1214#include < game/CStreaming.h>
1315#include < game/CModelInfo.h>
1416#include < game/CSettings.h>
17+ #include < game/CWorld.h>
1518#include " CModelCacheManager.h"
1619
1720namespace
@@ -25,6 +28,92 @@ namespace
2528 bool bIsModelCachedHere;
2629 bool bIsModelLoadedByGame;
2730 };
31+
32+ constexpr uint8_t STREAMING_FLAG_GAME_REQUIRED = 0x2 ;
33+ constexpr uint8_t STREAMING_FLAG_MISSION_REQUIRED = 0x4 ;
34+
35+ constexpr std::uintptr_t VAR_CStreaming_msPedsLoaded = 0x8E4C00 ;
36+ constexpr std::uintptr_t VAR_CStreaming_msNumPedsLoaded = 0x8E4BB0 ;
37+ constexpr std::uintptr_t VAR_CStreaming_msVehiclesLoaded = 0x8E4C24 ;
38+ constexpr std::uintptr_t FUNC_CGame_CanSeeOutsideFromCurrArea = 0x53C4A0 ;
39+
40+ constexpr std::size_t PED_STREAMING_SLOT_COUNT = 8u ;
41+ constexpr int32_t PED_STREAMING_SLOT_MAX_VALUE = 0xFFFF ;
42+ constexpr uint32_t PED_STREAMING_FLOOR = 4u ;
43+ constexpr uint32_t VEHICLE_STREAMING_FLOOR_EXTERIOR = 7u ;
44+ constexpr uint32_t VEHICLE_STREAMING_FLOOR_INTERIOR = 4u ;
45+ constexpr std::size_t VEHICLE_STREAMING_SLOT_COUNT = 23u ;
46+ constexpr int16_t VEHICLE_STREAMING_SLOT_UNUSED = -1 ;
47+
48+ uint32_t GetNativeNumPedsLoaded ()
49+ {
50+ return *reinterpret_cast <uint32_t *>(VAR_CStreaming_msNumPedsLoaded);
51+ }
52+
53+ bool IsModelTrackedByNativePedSlots (uint16_t modelId)
54+ {
55+ const auto * slots = reinterpret_cast <const int32_t *>(VAR_CStreaming_msPedsLoaded);
56+ if (!slots)
57+ return false ;
58+
59+ for (std::size_t idx = 0 ; idx < PED_STREAMING_SLOT_COUNT; ++idx)
60+ {
61+ const int32_t pedSlot = slots[idx];
62+ if (pedSlot < 0 || pedSlot > PED_STREAMING_SLOT_MAX_VALUE)
63+ continue ;
64+
65+ if (static_cast <uint16_t >(pedSlot) == modelId)
66+ return true ;
67+ }
68+
69+ return false ;
70+ }
71+
72+ uint32_t GetNativeVehicleStreamCount ()
73+ {
74+ const auto * slots = reinterpret_cast <const int16_t *>(VAR_CStreaming_msVehiclesLoaded);
75+ if (!slots)
76+ return 0 ;
77+
78+ uint32_t count = 0 ;
79+ for (std::size_t idx = 0 ; idx < VEHICLE_STREAMING_SLOT_COUNT; ++idx)
80+ {
81+ if (slots[idx] == VEHICLE_STREAMING_SLOT_UNUSED)
82+ break ;
83+
84+ ++count;
85+ }
86+
87+ return count;
88+ }
89+
90+ bool IsModelTrackedByNativeVehicleGroup (uint16_t modelId)
91+ {
92+ const auto * slots = reinterpret_cast <const int16_t *>(VAR_CStreaming_msVehiclesLoaded);
93+ if (!slots)
94+ return false ;
95+
96+ for (std::size_t idx = 0 ; idx < VEHICLE_STREAMING_SLOT_COUNT; ++idx)
97+ {
98+ const int16_t slotValue = slots[idx];
99+ if (slotValue == VEHICLE_STREAMING_SLOT_UNUSED)
100+ break ;
101+
102+ if (slotValue < 0 )
103+ continue ;
104+
105+ if (static_cast <uint16_t >(slotValue) == modelId)
106+ return true ;
107+ }
108+
109+ return false ;
110+ }
111+
112+ bool NativeCanSeeOutsideFromCurrArea ()
113+ {
114+ using Fn = bool (__cdecl*)();
115+ return reinterpret_cast <Fn>(FUNC_CGame_CanSeeOutsideFromCurrArea)();
116+ }
28117} // namespace
29118
30119// /////////////////////////////////////////////////////////////
@@ -59,6 +148,7 @@ class CModelCacheManagerImpl : public CModelCacheManager
59148 int GetModelRefCount (ushort usModelId);
60149 void AddModelRefCount (ushort usModelId);
61150 void SubModelRefCount (ushort usModelId);
151+ bool TryReleaseCachedModel (ushort usModelId, SModelCacheInfo& info, const char * contextTag, uint& uiNumModelsCachedHereOnly);
62152
63153protected:
64154 CGame* m_pGame{};
@@ -399,31 +489,29 @@ void CModelCacheManagerImpl::UpdateModelCaching(const std::map<ushort, float>& n
399489 }
400490
401491 // If at or above cache limit, try to uncache unneeded first
492+ bool bReleasedModel = false ;
493+ auto AttemptReleaseFromList = [&](const std::map<uint, ushort>& candidateList, const char * contextTag) -> bool {
494+ for (auto it = candidateList.rbegin (); it != candidateList.rend (); ++it)
495+ {
496+ const ushort modelId = it->second ;
497+ auto cacheIt = currentCacheInfoMap.find (modelId);
498+ if (cacheIt == currentCacheInfoMap.end ())
499+ continue ;
500+
501+ SModelCacheInfo& candidateInfo = cacheIt->second ;
502+ assert (candidateInfo.bIsModelCachedHere );
503+
504+ if (TryReleaseCachedModel (modelId, candidateInfo, contextTag, uiNumModelsCachedHereOnly))
505+ return true ;
506+ }
507+ return false ;
508+ };
509+
402510 if (uiNumModelsCachedHereOnly >= uiMaxCachedAllowed && !maybeUncacheUnneededList.empty ())
403- {
404- const ushort usModelId = maybeUncacheUnneededList.rbegin ()->second ;
405- SModelCacheInfo* pInfo = MapFind (currentCacheInfoMap, usModelId);
406- assert (pInfo);
407- assert (pInfo->bIsModelCachedHere );
408- SubModelRefCount (usModelId);
409- pInfo->bIsModelCachedHere = false ;
410- MapRemove (currentCacheInfoMap, usModelId);
411- OutputDebugLine (SString (" [Cache] End caching model %d (UncacheUnneeded)" , usModelId));
412- }
413- else if (uiNumModelsCachedHereOnly > uiMaxCachedAllowed && !maybeUncacheNeededList.empty ())
414- {
415- // Only uncache from the needed list if above limit
511+ bReleasedModel = AttemptReleaseFromList (maybeUncacheUnneededList, " UncacheUnneeded" );
416512
417- // Uncache furthest away model
418- const ushort usModelId = maybeUncacheNeededList.rbegin ()->second ;
419- SModelCacheInfo* pInfo = MapFind (currentCacheInfoMap, usModelId);
420- assert (pInfo);
421- assert (pInfo->bIsModelCachedHere );
422- SubModelRefCount (usModelId);
423- pInfo->bIsModelCachedHere = false ;
424- MapRemove (currentCacheInfoMap, usModelId);
425- OutputDebugLine (SString (" [Cache] End caching model %d (UncacheNeeded)" , usModelId));
426- }
513+ if (!bReleasedModel && uiNumModelsCachedHereOnly > uiMaxCachedAllowed && !maybeUncacheNeededList.empty ())
514+ bReleasedModel = AttemptReleaseFromList (maybeUncacheNeededList, " UncacheNeeded" );
427515
428516 // Cache if room
429517 if (!maybeCacheList.empty () && uiNumModelsCachedHereOnly < uiMaxCachedAllowed)
@@ -510,6 +598,7 @@ bool CModelCacheManagerImpl::UnloadModel(ushort usModelId)
510598// /////////////////////////////////////////////////////////////
511599void CModelCacheManagerImpl::OnRestreamModel (ushort usModelId)
512600{
601+ // Keep forced restream untouched: callers expect full removal when restreaming.
513602 std::map<ushort, SModelCacheInfo>* mapList[] = {&m_PedModelCacheInfoMap, &m_VehicleModelCacheInfoMap};
514603
515604 for (uint i = 0 ; i < NUMELMS (mapList); i++)
@@ -529,3 +618,79 @@ void CModelCacheManagerImpl::OnRestreamModel(ushort usModelId)
529618 }
530619 }
531620}
621+
622+ // /////////////////////////////////////////////////////////////
623+ //
624+ // CModelCacheManagerImpl::TryReleaseCachedModel
625+ //
626+ // /////////////////////////////////////////////////////////////
627+ bool CModelCacheManagerImpl::TryReleaseCachedModel (ushort usModelId, SModelCacheInfo& info, const char * contextTag, uint& uiNumModelsCachedHereOnly)
628+ {
629+ assert (info.bIsModelCachedHere );
630+
631+ CStreaming* pStreaming = m_pGame->GetStreaming ();
632+ if (!pStreaming)
633+ {
634+ return false ;
635+ }
636+
637+ CStreamingInfo* pStreamingInfo = pStreaming->GetStreamingInfo (usModelId);
638+ if (!pStreamingInfo)
639+ {
640+ return false ;
641+ }
642+
643+ if (pStreamingInfo->flg & STREAMING_FLAG_MISSION_REQUIRED)
644+ {
645+ return false ;
646+ }
647+
648+ if (pStreamingInfo->flg & STREAMING_FLAG_GAME_REQUIRED)
649+ {
650+ return false ;
651+ }
652+
653+ const int iRefCount = GetModelRefCount (usModelId);
654+ if (iRefCount > 1 )
655+ {
656+ return false ;
657+ }
658+
659+ CModelInfo* pModelInfo = m_pGame->GetModelInfo (usModelId, true );
660+ const eModelInfoType modelType = pModelInfo ? pModelInfo->GetModelType () : eModelInfoType::UNKNOWN;
661+ const bool bIsPedModel = modelType == eModelInfoType::PED;
662+ const bool bIsVehicleModel = modelType == eModelInfoType::VEHICLE;
663+
664+ const bool bTrackedByNativePedSlots = bIsPedModel && IsModelTrackedByNativePedSlots (usModelId);
665+ const bool bTrackedByNativeVehicleGroup = bIsVehicleModel && IsModelTrackedByNativeVehicleGroup (usModelId);
666+
667+ if (bTrackedByNativePedSlots)
668+ {
669+ if (GetNativeNumPedsLoaded () <= PED_STREAMING_FLOOR)
670+ {
671+ return false ;
672+ }
673+ }
674+
675+ if (bTrackedByNativeVehicleGroup)
676+ {
677+ const uint32_t vehicleCount = GetNativeVehicleStreamCount ();
678+ const bool bIsInterior = m_pGame->GetWorld () && m_pGame->GetWorld ()->GetCurrentArea () != 0 ;
679+ const bool bTreatAsInterior = bIsInterior || !NativeCanSeeOutsideFromCurrArea ();
680+ const uint32_t minVehicleBudget = bTreatAsInterior ? VEHICLE_STREAMING_FLOOR_INTERIOR : VEHICLE_STREAMING_FLOOR_EXTERIOR;
681+ if (vehicleCount <= minVehicleBudget)
682+ {
683+ return false ;
684+ }
685+ }
686+
687+ SubModelRefCount (usModelId);
688+ info.bIsModelCachedHere = false ;
689+ info.lastNeeded = m_TickCountNow;
690+ info.bIsModelLoadedByGame = false ;
691+
692+ if (uiNumModelsCachedHereOnly > 0 )
693+ uiNumModelsCachedHereOnly--;
694+
695+ return true ;
696+ }
0 commit comments