Skip to content

Commit 6d69b53

Browse files
committed
Refactor parts of texture system against white textures #3
1 parent 382c529 commit 6d69b53

1 file changed

Lines changed: 72 additions & 17 deletions

File tree

Client/game_sa/CRenderWareSA.TextureReplacing.cpp

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ namespace
268268

269269
RwTexDictionary* g_pCachedVehicleTxd = nullptr;
270270
RwListEntry* g_pCachedVehicleTxdListHead = nullptr; // textures.root.next at cache time; detects texture rebuild at same TXD address
271+
RwListEntry* g_pCachedVehicleTxdListTail = nullptr; // textures.root.prev at cache time; detects changes at list tail
271272
TxdTextureMap g_CachedVehicleTxdMap;
272273
unsigned short g_usVehicleTxdSlotId = 0xFFFF;
273274

@@ -370,7 +371,7 @@ namespace
370371
return nullptr;
371372
}
372373

373-
void BuildTxdTextureMapFast(RwTexDictionary* pTxd, TxdTextureMap& outMap);
374+
bool BuildTxdTextureMapFast(RwTexDictionary* pTxd, TxdTextureMap& outMap);
374375
static void AddVehicleTxdFallback(TxdTextureMap& outMap);
375376
static bool ShouldUseVehicleTxdFallback(unsigned short usModelId);
376377
static bool IsReadableTexture(RwTexture* pTexture);
@@ -417,17 +418,21 @@ namespace
417418
constexpr std::size_t MAX_PENDING_ISOLATED_PER_TICK = 64;
418419

419420
// Per-TXD texture map cache to avoid repeated linked list iteration.
420-
// Keyed by TXD slot ID; validated by TXD pointer and texture list head
421-
// (the list head catches TXD rebuilds at the same pool address).
421+
// Keyed by TXD slot ID; validated by TXD pointer, list head, and list tail
422+
// (head/tail catch TXD rebuilds and texture adds/removes at either end).
422423
struct SCachedTxdTextureMap
423424
{
424425
RwTexDictionary* pTxd = nullptr; // TXD pointer at cache time (detects reload/slot reuse)
425-
RwListEntry* pListHead = nullptr; // textures.root.next at cache time (detects texture rebuild at same TXD address)
426+
RwListEntry* pListHead = nullptr; // textures.root.next at cache time (detects head change)
427+
RwListEntry* pListTail = nullptr; // textures.root.prev at cache time (detects tail change)
426428
TxdTextureMap textureMap; // Cached texture name > texture map
427429

428430
bool IsValid(RwTexDictionary* pCurrentTxd) const noexcept
429431
{
430-
if (pTxd == nullptr || pTxd != pCurrentTxd || pListHead != pCurrentTxd->textures.root.next)
432+
if (pTxd == nullptr || pTxd != pCurrentTxd)
433+
return false;
434+
435+
if (pListHead != pCurrentTxd->textures.root.next || pListTail != pCurrentTxd->textures.root.prev)
431436
return false;
432437

433438
if (pListHead && pListHead != &pCurrentTxd->textures.root)
@@ -458,7 +463,7 @@ namespace
458463
g_TxdTextureMapCache.swap(temp);
459464
}
460465

461-
// Build or retrieve cached texture map and merge into output map
466+
// Build or retrieve cached texture map and merge into output map.
462467
// Use when building combined maps (parent + child + vehicle fallback)
463468
static void MergeCachedTxdTextureMap(unsigned short usTxdId, RwTexDictionary* pTxd, TxdTextureMap& outMap)
464469
{
@@ -474,16 +479,25 @@ namespace
474479
return;
475480
}
476481

482+
// Rebuild: reset cached metadata before populating
477483
entry.pTxd = pTxd;
478484
entry.pListHead = pTxd->textures.root.next;
485+
entry.pListTail = pTxd->textures.root.prev;
479486
// Use swap idiom to ensure complete memory release
480487
// (avoids reconnect leaks; safer than .clear() on potentially corrupted maps)
481488
TxdTextureMap temp;
482489
entry.textureMap.swap(temp);
483-
BuildTxdTextureMapFast(pTxd, entry.textureMap);
490+
bool bComplete = BuildTxdTextureMapFast(pTxd, entry.textureMap);
484491

485492
for (const auto& kv : entry.textureMap)
486493
outMap[kv.first] = kv.second;
494+
495+
// Don't keep a partial map in the cache.. next call will rebuild from scratch
496+
if (!bComplete)
497+
{
498+
g_TxdTextureMapCache.erase(usTxdId);
499+
AddReportLog(9401, SString("BuildTxdTextureMapFast: partial walk for TXD %u, cache entry discarded", usTxdId));
500+
}
487501
}
488502

489503
// Remove pReplacementTextures from all usedByReplacements vectors in the map.
@@ -1235,7 +1249,10 @@ namespace
12351249

12361250
const bool bProcessedAll = (pNode == pRoot);
12371251
if (!bProcessedAll)
1252+
{
1253+
AddReportLog(9401, SString("OrphanTxdTexturesBounded: bailed after %u entries, TXD list too large or corrupt", (unsigned int)uiCount));
12381254
return false;
1255+
}
12391256

12401257
for (RwTexture* pTexture : vecTexturesToOrphan)
12411258
{
@@ -2468,17 +2485,19 @@ namespace
24682485
return true;
24692486
}
24702487

2471-
// Fast name->texture map builder
2472-
void BuildTxdTextureMapFast(RwTexDictionary* pTxd, TxdTextureMap& outMap)
2488+
// Fast name->texture map builder. Returns true if the full list was walked,
2489+
// false if the walk bailed early (corrupt list, limit hit, null node).
2490+
// Callers that cache the result must not cache incomplete maps.
2491+
bool BuildTxdTextureMapFast(RwTexDictionary* pTxd, TxdTextureMap& outMap)
24732492
{
24742493
if (!pTxd)
2475-
return;
2494+
return false;
24762495

24772496
RwListEntry* const pRoot = &pTxd->textures.root;
24782497
RwListEntry* pNode = pRoot->next;
24792498

24802499
if (pNode == nullptr || pNode == pRoot)
2481-
return;
2500+
return true;
24822501

24832502
if (outMap.empty())
24842503
outMap.reserve(32);
@@ -2489,19 +2508,21 @@ namespace
24892508
while (pNode != pRoot)
24902509
{
24912510
if (++count > kMaxTextures)
2492-
return;
2511+
return false;
24932512

24942513
RwTexture* pTex = reinterpret_cast<RwTexture*>(reinterpret_cast<char*>(pNode) - offsetof(RwTexture, TXDList));
24952514
if (!pTex)
2496-
return;
2515+
return false;
24972516

24982517
if (strnlen(pTex->name, RW_TEXTURE_NAME_LENGTH) < RW_TEXTURE_NAME_LENGTH)
24992518
outMap[pTex->name] = pTex;
25002519

25012520
pNode = pNode->next;
25022521
if (!pNode)
2503-
return;
2522+
return false;
25042523
}
2524+
2525+
return true;
25052526
}
25062527

25072528
RwTexDictionary* GetVehicleTxd()
@@ -2519,15 +2540,16 @@ namespace
25192540
if (!pVehicleTxd)
25202541
return;
25212542

2522-
// Check both TXD pointer and its texture list head.
2543+
// Check TXD pointer, texture list head and tail.
25232544
// Streaming can destroy and reload a TXD at the same address (pool/allocator reuse),
25242545
// which leaves the cache with dangling texture name pointers.
25252546
RwListEntry* pListHead = pVehicleTxd->textures.root.next;
2547+
RwListEntry* pListTail = pVehicleTxd->textures.root.prev;
25262548
RwTexture* pFirstTex = nullptr;
25272549
if (pListHead && pListHead != &pVehicleTxd->textures.root)
25282550
pFirstTex = (RwTexture*)((char*)pListHead - offsetof(RwTexture, TXDList));
25292551

2530-
bool bInvalidate = (pVehicleTxd != g_pCachedVehicleTxd || pListHead != g_pCachedVehicleTxdListHead);
2552+
bool bInvalidate = (pVehicleTxd != g_pCachedVehicleTxd || pListHead != g_pCachedVehicleTxdListHead || pListTail != g_pCachedVehicleTxdListTail);
25312553

25322554
if (!bInvalidate && pFirstTex && !g_CachedVehicleTxdMap.empty())
25332555
{
@@ -2543,8 +2565,14 @@ namespace
25432565
g_CachedVehicleTxdMap.clear();
25442566
g_pCachedVehicleTxd = pVehicleTxd;
25452567
g_pCachedVehicleTxdListHead = pListHead;
2568+
g_pCachedVehicleTxdListTail = pListTail;
25462569
g_usVehicleTxdSlotId = 0xFFFF;
2547-
BuildTxdTextureMapFast(pVehicleTxd, g_CachedVehicleTxdMap);
2570+
if (!BuildTxdTextureMapFast(pVehicleTxd, g_CachedVehicleTxdMap))
2571+
{
2572+
// Incomplete build - use what we have but don't keep caching it
2573+
g_pCachedVehicleTxd = nullptr;
2574+
AddReportLog(9401, "BuildTxdTextureMapFast: partial walk for vehicle TXD, cache disabled");
2575+
}
25482576
}
25492577

25502578
for (const auto& entry : g_CachedVehicleTxdMap)
@@ -5058,6 +5086,7 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(unsigned short usModelId
50585086
g_bInTxdReapply = true;
50595087

50605088
RwTexDictionary* txdAtStart = pCurrentTxd;
5089+
bool bTxdChangedMidReapply = false;
50615090

50625091
for (auto& reapplyEntry : replacementsToReapply)
50635092
{
@@ -5069,13 +5098,19 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(unsigned short usModelId
50695098
if (!modelIds.empty())
50705099
{
50715100
if (CTxdStore_GetTxd(usTxdId) != txdAtStart)
5101+
{
5102+
bTxdChangedMidReapply = true;
50725103
break;
5104+
}
50735105

50745106
size_t uiAppliedIndex = modelIds.size();
50755107
for (size_t i = 0; i < modelIds.size(); ++i)
50765108
{
50775109
if (CTxdStore_GetTxd(usTxdId) != txdAtStart)
5110+
{
5111+
bTxdChangedMidReapply = true;
50785112
break;
5113+
}
50795114

50805115
unsigned short usTestModelId = modelIds[i];
50815116
const uint32_t uiStartSerial = g_uiIsolationDeniedSerial;
@@ -5091,10 +5126,16 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(unsigned short usModelId
50915126
break;
50925127
}
50935128

5129+
if (bTxdChangedMidReapply)
5130+
break;
5131+
50945132
if (bAppliedToFirstModel)
50955133
{
50965134
if (CTxdStore_GetTxd(usTxdId) != txdAtStart)
5135+
{
5136+
bTxdChangedMidReapply = true;
50975137
break;
5138+
}
50985139

50995140
for (size_t i = 0; i < modelIds.size(); ++i)
51005141
{
@@ -5111,6 +5152,19 @@ CModelTexturesInfo* CRenderWareSA::GetModelTexturesInfo(unsigned short usModelId
51115152
}
51125153
}
51135154

5155+
// Queue un-applied replacements when a TXD change forced erly exit
5156+
if (bTxdChangedMidReapply)
5157+
{
5158+
for (auto& remaining : replacementsToReapply)
5159+
{
5160+
if (!remaining.first)
5161+
continue;
5162+
for (unsigned short usModelId : remaining.second)
5163+
QueuePendingReplacement(usModelId, remaining.first, 0, 0);
5164+
}
5165+
AddReportLog(9401, SString("GetModelTexturesInfo: TXD %u changed mid-reapply, queued remaining replacements", usTxdId));
5166+
}
5167+
51145168
g_bInTxdReapply = bPrevInReapply;
51155169
}
51165170
info.bReapplyingTextures = false;
@@ -7971,6 +8025,7 @@ void CRenderWareSA::StaticResetModelTextureReplacing()
79718025

79728026
g_pCachedVehicleTxd = nullptr;
79738027
g_pCachedVehicleTxdListHead = nullptr;
8028+
g_pCachedVehicleTxdListTail = nullptr;
79748029
g_CachedVehicleTxdMap = TxdTextureMap{};
79758030
g_usVehicleTxdSlotId = 0xFFFF;
79768031
ClearTxdTextureMapCache();

0 commit comments

Comments
 (0)