Skip to content

perf(network): Improve performance by refactoring loop logic and removing unused static variables#2659

Open
Caball009 wants to merge 4 commits into
TheSuperHackers:mainfrom
Caball009:perf_network
Open

perf(network): Improve performance by refactoring loop logic and removing unused static variables#2659
Caball009 wants to merge 4 commits into
TheSuperHackers:mainfrom
Caball009:perf_network

Conversation

@Caball009

@Caball009 Caball009 commented Apr 28, 2026

Copy link
Copy Markdown

This PR makes modest performance improvements:

  1. Improves loop logic in a couple of places (early breaks and keep track of the current index to avoid restarting inner loops).
  2. Removes unused static variables.

See commits for cleaner diffs.

TODO:

  • Verify everything works ok.

@Caball009 Caball009 added Minor Severity: Minor < Major < Critical < Blocker Performance Is a performance concern Network Anything related to network, servers labels Apr 28, 2026
@Caball009 Caball009 changed the title perf(network): Improve performancy by reducing packet allocations and improving loop logic perf(network): Improve performance by reducing packet allocations and improving loop logic Apr 28, 2026
@Caball009 Caball009 force-pushed the perf_network branch 2 times, most recently from 7ebd8f1 to a2267a1 Compare April 28, 2026 20:41
@stephanmeesters

Copy link
Copy Markdown

I think it would be nice if we can change the signature of NetPacket from class NetPacket : public MemoryPoolObject to struct NetPacket. The memory pooling right now doesn't really get used, especially with your change? By the looks of it this would mostly need changes around NetPacket::ConstructBigCommandPacketList

@Caball009

Copy link
Copy Markdown
Author

I think it would be nice if we can change the signature of NetPacket from class NetPacket : public MemoryPoolObject to struct NetPacket. The memory pooling right now doesn't really get used, especially with your change? By the looks of it this would mostly need changes around NetPacket::ConstructBigCommandPacketList

Perhaps I can just put the relevant logic from Connection::sendNetCommandMsg in a callback and pass that to NetPacket::ConstructBigCommandPacketList.

@Caball009

Copy link
Copy Markdown
Author

I think it would be nice if we can change the signature of NetPacket from class NetPacket : public MemoryPoolObject to struct NetPacket. The memory pooling right now doesn't really get used, especially with your change? By the looks of it this would mostly need changes around NetPacket::ConstructBigCommandPacketList

I think it's best to do this in a separate pull request because it involves another set of changes.

@Caball009 Caball009 force-pushed the perf_network branch 2 times, most recently from 3fe1916 to d4f75e9 Compare June 2, 2026 20:59
@Caball009 Caball009 marked this pull request as ready for review July 9, 2026 18:59
@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes modest performance improvements across four network files by adding early break statements when an empty buffer slot is encountered, replacing MAX_MESSAGES literals with ARRAY_SIZE(...), converting while iterator loops to range-for style, and removing two unused static debug counters from ConnectionManager::doRelay().

  • Early-break optimization — safe because both fill paths maintain a no-gap invariant in m_inBuffer, so the first empty slot reliably marks the end of valid packets.
  • bufferIndex carry-over in doRecv() — avoids restarting the inner slot-search loop from index 0 for every packet within a single call; the counter is shared between m_delayedInBuffer and m_inBuffer, which is safe only while m_useLatency does not toggle mid-call.
  • Re-enabled DEBUG_ASSERTCRASH — the assert that was previously commented out is now active, which may crash debug sessions under burst traffic.

Confidence Score: 5/5

Safe to merge. All changes are non-latency-path production code with no correctness impact; concerns are limited to debug-build behaviour.

The early-break optimization is correct: the no-gap invariant in m_inBuffer is maintained by both the fill and clear paths. The only notable changes are confined to #if defined(RTS_DEBUG) or debug-counter removal, meaning release behaviour is unaffected. The re-enabled DEBUG_ASSERTCRASH and the shared bufferIndex are potential debug-build annoyances rather than production defects.

Transport.cpp deserves a second look for the re-enabled DEBUG_ASSERTCRASH and the shared bufferIndex across the latency and non-latency receive paths.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameNetwork/Transport.cpp Introduces bufferIndex to avoid restarting inner loops, removes static counters, and re-enables a DEBUG_ASSERTCRASH that was previously commented out. The bufferIndex is shared between m_delayedInBuffer (latency path) and m_inBuffer (normal path), which could misplace packets if m_useLatency toggles mid-call.
Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp Removes two unused static debug counters, converts while loops to range-for, and adds an early break on empty m_inBuffer slots. The early break is safe because the buffer invariant is maintained by the write/clear ordering.
Core/GameEngine/Source/GameNetwork/LANAPI.cpp Replaces MAX_MESSAGES with ARRAY_SIZE and adds an early break on empty slots. The existing LANbuttonPushed guard is preserved in the loop condition.
Core/GameEngine/Source/GameNetwork/NAT.cpp Replaces MAX_MESSAGES with ARRAY_SIZE and adds an early break on empty slots. Minimal, low-risk change consistent with the rest of the PR.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UDP as UDP Socket
    participant TR as Transport::doRecv()
    participant IB as m_inBuffer[]
    participant DIB as m_delayedInBuffer[]
    participant CM as ConnectionManager::doRelay()
    participant LAN as LANAPI::update()
    participant NAT as NAT::connectionUpdate()

    UDP->>TR: Read(packet)
    TR->>TR: bufferIndex starts at 0 (local)
    alt "m_useLatency == true (DEBUG only)"
        TR->>DIB: fill at bufferIndex, ++bufferIndex
        Note over TR,DIB: bufferIndex shared with m_inBuffer path
    else "m_useLatency == false"
        TR->>IB: fill at bufferIndex, ++bufferIndex
    end
    Note over TR,IB: Early break added: readers stop at first empty slot
    CM->>IB: "iterate i=0..N, break on length==0"
    IB->>CM: process packet, clear slot
    LAN->>IB: "iterate i=0..N, break on length==0"
    IB->>LAN: process packet, clear slot
    NAT->>IB: "iterate i=0..N, break on length==0"
    IB->>NAT: process packet, clear slot
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UDP as UDP Socket
    participant TR as Transport::doRecv()
    participant IB as m_inBuffer[]
    participant DIB as m_delayedInBuffer[]
    participant CM as ConnectionManager::doRelay()
    participant LAN as LANAPI::update()
    participant NAT as NAT::connectionUpdate()

    UDP->>TR: Read(packet)
    TR->>TR: bufferIndex starts at 0 (local)
    alt "m_useLatency == true (DEBUG only)"
        TR->>DIB: fill at bufferIndex, ++bufferIndex
        Note over TR,DIB: bufferIndex shared with m_inBuffer path
    else "m_useLatency == false"
        TR->>IB: fill at bufferIndex, ++bufferIndex
    end
    Note over TR,IB: Early break added: readers stop at first empty slot
    CM->>IB: "iterate i=0..N, break on length==0"
    IB->>CM: process packet, clear slot
    LAN->>IB: "iterate i=0..N, break on length==0"
    IB->>LAN: process packet, clear slot
    NAT->>IB: "iterate i=0..N, break on length==0"
    IB->>NAT: process packet, clear slot
Loading

Reviews (4): Last reviewed commit: "Used '>' and '<=' instead of '==' and '!..." | Re-trigger Greptile

Comment thread Core/GameEngine/Source/GameNetwork/LANAPI.cpp Outdated
@Caball009 Caball009 changed the title perf(network): Improve performance by reducing packet allocations and improving loop logic perf(network): Reduce packet allocations and improve loop logic Jul 9, 2026

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull does a number of things.

Comment thread Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp Outdated
Comment thread Core/GameEngine/Include/GameNetwork/NetPacket.h Outdated
Comment thread Core/GameEngine/Source/GameNetwork/NetPacket.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/NetPacket.cpp Outdated
m_transport->m_inBuffer[i].length = 0;
}
} else {
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively could do early breaks in the loops. But its ok either way.

}
else
{
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there cannot be non-zero buffer after a zero buffer, yes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. Transport::doRecv should handle that correctly.

for (Int i = 0; i < MAX_MESSAGES; ++i) {
if (m_transport->m_inBuffer[i].length != 0) {
for (size_t i = 0; i < ARRAY_SIZE(m_transport->m_inBuffer); ++i) {
if (m_transport->m_inBuffer[i].length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status quo: Compares with > 0, != 0

Expectation:

> 0, <= 0

or

== 0, != 0

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've split this off to a separate commit. I do think this is the right comparator, though, as long as length is still signed.

The original code already does it this way here:

if (m_transport->m_inBuffer[i].length > 0)

if (m_transport->m_inBuffer[i].length > 0) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change the == 0 cases to <= 0 if you like.

@Caball009

Copy link
Copy Markdown
Author

This pull does a number of things.

I can put the loop logic in another PR and put #2866 in here to replace all memory pooled uses of NetPacket (either with a single dynamic allocation or stack allocation if possible).

@xezon

xezon commented Jul 12, 2026

Copy link
Copy Markdown

Whatever makes most sense.

@Caball009 Caball009 marked this pull request as draft July 13, 2026 13:13
@Caball009 Caball009 changed the title perf(network): Reduce packet allocations and improve loop logic perf(network): Improve performance by refactoring loop logic and removing unused static variables Jul 13, 2026
@Caball009 Caball009 marked this pull request as ready for review July 13, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Performance Is a performance concern

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants