Skip to content

Commit 6ae640e

Browse files
PrimelPrimeFileEX
andauthored
Fix O(n^2) performance bottleneck in CClientStreamer (#4695)
#### Summary Replace O(n) linear scans in CClientStreamer with O(1) hash-based lookups to eliminate quadratic performance degradation during bulk element creation. Co-authored-by: FileEX <kongali@interia.pl>
1 parent aa3692a commit 6ae640e

File tree

4 files changed

+62
-74
lines changed

4 files changed

+62
-74
lines changed

Client/mods/deathmatch/logic/CClientStreamSector.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,24 +137,32 @@ void CClientStreamSector::CompareSurroundings(CClientStreamSector* pSector, list
137137
}
138138
}
139139

140-
void CClientStreamSector::AddElements(list<CClientStreamElement*>* pList)
140+
void CClientStreamSector::AddElements(list<CClientStreamElement*>* pList, std::unordered_set<CClientStreamElement*>* pSet)
141141
{
142142
list<CClientStreamElement*>::iterator iter = m_Elements.begin();
143143
for (; iter != m_Elements.end(); iter++)
144144
{
145-
// Don't add if already in the list
146-
if (ListContains(*pList, *iter))
145+
// Don't add if already in the list (O(1) if set provided)
146+
if (pSet)
147+
{
148+
if (pSet->count(*iter))
149+
continue;
150+
pSet->insert(*iter);
151+
}
152+
else if (ListContains(*pList, *iter))
147153
continue;
148154

149155
pList->push_back(*iter);
150156
}
151157
}
152158

153-
void CClientStreamSector::RemoveElements(list<CClientStreamElement*>* pList)
159+
void CClientStreamSector::RemoveElements(list<CClientStreamElement*>* pList, std::unordered_set<CClientStreamElement*>* pSet)
154160
{
155161
list<CClientStreamElement*>::iterator iter = m_Elements.begin();
156162
for (; iter != m_Elements.end(); iter++)
157163
{
158164
pList->remove(*iter);
165+
if (pSet)
166+
pSet->erase(*iter);
159167
}
160168
}

Client/mods/deathmatch/logic/CClientStreamSector.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#pragma once
1212

1313
#include <list>
14+
#include <unordered_set>
1415
#include "CClientCommon.h"
1516

1617
class CClientStreamer;
@@ -38,8 +39,8 @@ class CClientStreamSector
3839
std::list<CClientStreamElement*>::iterator Begin() { return m_Elements.begin(); }
3940
std::list<CClientStreamElement*>::iterator End() { return m_Elements.end(); }
4041

41-
void AddElements(std::list<CClientStreamElement*>* pList);
42-
void RemoveElements(std::list<CClientStreamElement*>* pList);
42+
void AddElements(std::list<CClientStreamElement*>* pList, std::unordered_set<CClientStreamElement*>* pSet = nullptr);
43+
void RemoveElements(std::list<CClientStreamElement*>* pList, std::unordered_set<CClientStreamElement*>* pSet = nullptr);
4344
unsigned int CountElements() { return m_Elements.size(); }
4445

4546
CClientStreamSectorRow* GetRow() { return m_pRow; }

Client/mods/deathmatch/logic/CClientStreamer.cpp

Lines changed: 31 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,9 @@ CClientStreamer::~CClientStreamer()
4848
m_WorldRows.clear();
4949

5050
// Clear our extra rows
51-
iter = m_ExtraRows.begin();
52-
for (; iter != m_ExtraRows.end(); iter++)
51+
for (auto& [key, pRow] : m_ExtraRows)
5352
{
54-
delete *iter;
53+
delete pRow;
5554
}
5655
m_ExtraRows.clear();
5756
}
@@ -244,24 +243,21 @@ CClientStreamSectorRow* CClientStreamer::FindOrCreateRow(CVector& vecPosition, C
244243
}
245244
}
246245

247-
// Search through our extra rows
248-
iter = m_ExtraRows.begin();
249-
for (; iter != m_ExtraRows.end(); iter++)
250-
{
251-
pRow = *iter;
252-
if (pRow->DoesContain(vecPosition))
253-
{
254-
return pRow;
255-
}
256-
}
257-
// We need a new row, align it with the others
246+
// Search through our extra rows using map lookup
258247
float fBottom = float((int)(vecPosition.fY / m_fRowSize)) * m_fRowSize;
259248
if (vecPosition.fY < 0.0f)
260249
fBottom -= m_fRowSize;
250+
int iRowIndex = (int)(fBottom / m_fRowSize);
251+
252+
auto it = m_ExtraRows.find(iRowIndex);
253+
if (it != m_ExtraRows.end())
254+
return it->second;
255+
256+
// We need a new row, align it with the others
261257
pRow = new CClientStreamSectorRow(fBottom, fBottom + m_fRowSize, m_fSectorSize, m_fRowSize);
262258
ConnectRow(pRow);
263259
pRow->SetExtra(true);
264-
m_ExtraRows.push_back(pRow);
260+
m_ExtraRows[iRowIndex] = pRow;
265261
return pRow;
266262
}
267263

@@ -279,16 +275,16 @@ CClientStreamSectorRow* CClientStreamer::FindRow(float fY)
279275
}
280276
}
281277

282-
// Search through our extra rows
283-
iter = m_ExtraRows.begin();
284-
for (; iter != m_ExtraRows.end(); iter++)
285-
{
286-
pRow = *iter;
287-
if (pRow->DoesContain(fY))
288-
{
289-
return pRow;
290-
}
291-
}
278+
// Search through our extra rows using map lookup
279+
float fBottom = float((int)(fY / m_fRowSize)) * m_fRowSize;
280+
if (fY < 0.0f)
281+
fBottom -= m_fRowSize;
282+
int iRowIndex = (int)(fBottom / m_fRowSize);
283+
284+
auto it = m_ExtraRows.find(iRowIndex);
285+
if (it != m_ExtraRows.end())
286+
return it->second;
287+
292288
return NULL;
293289
}
294290

@@ -333,6 +329,7 @@ void CClientStreamer::RemoveElement(CClientStreamElement* pElement)
333329
{
334330
OnElementEnterSector(pElement, NULL);
335331
m_ActiveElements.remove(pElement);
332+
m_ActiveElementSet.erase(pElement);
336333
m_ToStreamOut.remove(pElement);
337334
}
338335

@@ -355,27 +352,14 @@ void CClientStreamer::AddToSortedList(list<CClientStreamElement*>* pList, CClien
355352
float fDistance = pElement->GetDistanceToBoundingBoxSquared(m_vecPosition);
356353
pElement->SetExpDistance(fDistance);
357354

358-
// Don't add if already in the list
359-
if (ListContains(*pList, pElement))
355+
// Don't add if already in the list (O(1) check)
356+
if (m_ActiveElementSet.count(pElement))
360357
return;
361358

362-
// Search through our list. Add it behind the first item further away than this
363-
CClientStreamElement* pTemp = NULL;
364-
list<CClientStreamElement*>::iterator iter = pList->begin();
365-
for (; iter != pList->end(); iter++)
366-
{
367-
pTemp = *iter;
368-
369-
// Is it further than the one we add?
370-
if (pTemp->GetDistanceToBoundingBoxSquared(m_vecPosition) > fDistance)
371-
{
372-
// Add it before here
373-
pList->insert(iter, pElement);
374-
return;
375-
}
376-
}
359+
// Track in the set
360+
m_ActiveElementSet.insert(pElement);
377361

378-
// We have no elements in the list, add it at the beginning
362+
// Append unsorted - DoPulse sorts the list every frame via m_ActiveElements.sort()
379363
pList->push_back(pElement);
380364
}
381365

@@ -386,15 +370,7 @@ bool CClientStreamer::CompareExpDistance(CClientStreamElement* p1, CClientStream
386370

387371
bool CClientStreamer::IsActiveElement(CClientStreamElement* pElement)
388372
{
389-
list<CClientStreamElement*>::iterator iter = m_ActiveElements.begin();
390-
for (; iter != m_ActiveElements.end(); iter++)
391-
{
392-
if (*iter == pElement)
393-
{
394-
return true;
395-
}
396-
}
397-
return false;
373+
return m_ActiveElementSet.count(pElement) > 0;
398374
}
399375

400376
void CClientStreamer::Restream(bool bMovedFar)
@@ -650,7 +626,7 @@ void CClientStreamer::OnEnterSector(CClientStreamSector* pSector)
650626
m_ToStreamOut.push_back(pElement);
651627
}
652628
}
653-
pTempSector->RemoveElements(&m_ActiveElements);
629+
pTempSector->RemoveElements(&m_ActiveElements, &m_ActiveElementSet);
654630
pTempSector->SetActivated(false);
655631
}
656632
}
@@ -666,7 +642,7 @@ void CClientStreamer::OnEnterSector(CClientStreamSector* pSector)
666642
pTempSector = *iter;
667643
if (!pTempSector->IsActivated())
668644
{
669-
pTempSector->AddElements(&m_ActiveElements);
645+
pTempSector->AddElements(&m_ActiveElements, &m_ActiveElementSet);
670646
pTempSector->SetActivated(true);
671647
}
672648
}
@@ -708,7 +684,7 @@ void CClientStreamer::OnElementEnterSector(CClientStreamElement* pElement, CClie
708684
// Should we activate this sector?
709685
if (pSector->IsExtra() && (m_pSector->IsMySurroundingSector(pSector) || m_pSector == pSector))
710686
{
711-
pSector->AddElements(&m_ActiveElements);
687+
pSector->AddElements(&m_ActiveElements, &m_ActiveElementSet);
712688
pSector->SetActivated(true);
713689
}
714690
// If we're in a deactivated sector and streamed in, stream us out

Client/mods/deathmatch/logic/CClientStreamer.h

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include "CClientCommon.h"
1414
#include <list>
15+
#include <unordered_map>
16+
#include <unordered_set>
1517
class CClientStreamSector;
1618
class CClientStreamSectorRow;
1719
class CClientStreamElement;
@@ -61,19 +63,20 @@ class CClientStreamer
6163
void OnElementForceStreamOut(CClientStreamElement* pElement);
6264
void OnElementDimension(CClientStreamElement* pElement);
6365

64-
const float m_fSectorSize;
65-
const float m_fRowSize;
66-
float m_fMaxDistanceExp;
67-
float m_fMaxDistanceThreshold;
68-
StreamerLimitReachedFunction* m_pLimitReachedFunc;
69-
std::list<CClientStreamSectorRow*> m_WorldRows;
70-
std::list<CClientStreamSectorRow*> m_ExtraRows;
71-
CClientStreamSectorRow* m_pRow;
72-
CClientStreamSector* m_pSector;
73-
CVector m_vecPosition;
74-
unsigned short m_usDimension;
75-
std::list<CClientStreamElement*> m_ActiveElements;
76-
std::list<CClientStreamElement*> m_ToStreamOut;
66+
const float m_fSectorSize;
67+
const float m_fRowSize;
68+
float m_fMaxDistanceExp;
69+
float m_fMaxDistanceThreshold;
70+
StreamerLimitReachedFunction* m_pLimitReachedFunc;
71+
std::list<CClientStreamSectorRow*> m_WorldRows;
72+
std::unordered_map<int, CClientStreamSectorRow*> m_ExtraRows;
73+
CClientStreamSectorRow* m_pRow;
74+
CClientStreamSector* m_pSector;
75+
CVector m_vecPosition;
76+
unsigned short m_usDimension;
77+
std::list<CClientStreamElement*> m_ActiveElements;
78+
std::unordered_set<CClientStreamElement*> m_ActiveElementSet;
79+
std::list<CClientStreamElement*> m_ToStreamOut;
7780

7881
static void* pAddingElement;
7982
};

0 commit comments

Comments
 (0)