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 delivers modest network-layer performance improvements by refactoring loop logic across four files and removing two unused debug counters (numPackets, numCommands) in ConnectionManager::doRelay().

  • Early break on empty slot: Consumer loops in ConnectionManager::doRelay(), NAT::connectionUpdate(), and LANAPI::update() now break as soon as they hit a zero-length buffer entry, relying on the invariant that m_inBuffer is always a contiguous prefix of filled slots.
  • Persistent bufferIndex: Transport::doRecv() and Transport::doSend() (latency path) now carry bufferIndex across inner-loop iterations so each successive message doesn't rescan already-occupied slots from index 0; the assert previously commented-out is re-enabled as DEBUG_ASSERTCRASH(bufferIndex < MAX_MESSAGES, ...).
  • ARRAY_SIZE() over MAX_MESSAGES: All array-bound literals are replaced with the safer compile-time macro; all three arrays (m_inBuffer, m_outBuffer, m_delayedInBuffer) are declared as [MAX_MESSAGES], so the substitution is numerically equivalent.

Confidence Score: 5/5

Changes are safe to merge. The early-break optimization is correct because each consumer calls m_transport->update() (which runs doRecv()) immediately before its own loop, guaranteeing a freshly-filled contiguous buffer by the time the break logic runs.

The three core invariants the PR relies on all hold: doRecv() fills m_inBuffer from index 0 each call, consumers iterate and clear sequentially from index 0, and all three arrays share the same MAX_MESSAGES bound so ARRAY_SIZE() substitution is numerically identical. Removing the dead static counters is a clean, risk-free change. The re-enabled DEBUG_ASSERTCRASH fires correctly for the first packet that will actually be dropped.

Transport.cpp deserves the closest review — the bufferIndex carrying state across the outer while loop is a new and non-obvious pattern; a comment explaining the invariant it relies on would help future readers.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameNetwork/Transport.cpp Most complex change: introduces shared bufferIndex across while-loop iterations in doRecv() and inner loop in doSend() latency path. Logic is correct given all three arrays are MAX_MESSAGES in size, and continue after the latency block gives proper mutual exclusion. The re-enabled DEBUG_ASSERTCRASH fires correctly for the actual first lost packet.
Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp Removes dead numPackets/numCommands static counters; converts while-loops to for-loops; adds early break. packet is set to nullptr immediately after deleteInstance inside the first loop, so the deleteInstance(packet) after the second loop is a safe null call.
Core/GameEngine/Source/GameNetwork/NAT.cpp Adds early break on empty slot. Safe because NAT calls m_transport->update() immediately before the loop, guaranteeing a freshly-filled contiguous buffer. Unrecognized packets (neither PROBE nor KEEPALIVE) are never cleared — a pre-existing issue that does not interact badly with the break since uncleared slots keep length > 0.
Core/GameEngine/Source/GameNetwork/LANAPI.cpp Minor change: variable declaration moved into for-init, ARRAY_SIZE() substituted, else { break; } added on empty slot. The !LANbuttonPushed guard in the loop condition is correctly retained. Every branch (including default) ends by setting m_inBuffer[i].length = 0, so LANAPI always leaves a clean buffer for the next frame's doRecv().

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GameLoop
    participant Transport
    participant Consumer

    GameLoop->>Transport: update()
    activate Transport
    Transport->>Transport: doRecv - fills m_inBuffer[0..k] contiguously
    note right of Transport: bufferIndex resets to 0 each call,<br/>skips full slots, fills only empty ones
    Transport->>Transport: doSend - delivers delayed msgs to m_inBuffer[k+1..m]
    deactivate Transport

    GameLoop->>Consumer: doRelay / connectionUpdate / LANAPI::update
    activate Consumer
    Consumer->>Consumer: "iterate i=0 while length > 0"
    Consumer->>Consumer: "clear m_inBuffer[i].length = 0"
    Consumer->>Consumer: else break on first empty slot (NEW)
    deactivate Consumer

    note over Transport,Consumer: Invariant: m_inBuffer is always a contiguous filled prefix.<br/>Break is safe only because doRecv fills from index 0<br/>and consumers clear sequentially.
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 GameLoop
    participant Transport
    participant Consumer

    GameLoop->>Transport: update()
    activate Transport
    Transport->>Transport: doRecv - fills m_inBuffer[0..k] contiguously
    note right of Transport: bufferIndex resets to 0 each call,<br/>skips full slots, fills only empty ones
    Transport->>Transport: doSend - delivers delayed msgs to m_inBuffer[k+1..m]
    deactivate Transport

    GameLoop->>Consumer: doRelay / connectionUpdate / LANAPI::update
    activate Consumer
    Consumer->>Consumer: "iterate i=0 while length > 0"
    Consumer->>Consumer: "clear m_inBuffer[i].length = 0"
    Consumer->>Consumer: else break on first empty slot (NEW)
    deactivate Consumer

    note over Transport,Consumer: Invariant: m_inBuffer is always a contiguous filled prefix.<br/>Break is safe only because doRecv fills from index 0<br/>and consumers clear sequentially.
Loading

Reviews (3): Last reviewed commit: "Used explicit 'length > 0' check instead..." | 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