Skip to content

Commit 35129eb

Browse files
committed
Improve streamer performance.
Keeps the cache manager aligned with GTA’s streaming system, while optimizing the purges (Plays a role in perceived streaming smoothness and micro-stutter/frame timing)
1 parent 74326a3 commit 35129eb

File tree

1 file changed

+188
-23
lines changed

1 file changed

+188
-23
lines changed

Client/core/CModelCacheManager.cpp

Lines changed: 188 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
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

1720
namespace
@@ -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

63153
protected:
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
///////////////////////////////////////////////////////////////
511599
void 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

Comments
 (0)