Skip to content

Commit 6045e43

Browse files
committed
Pool CSteamNetworkingMessage
1 parent 529a4e4 commit 6045e43

File tree

9 files changed

+256
-10
lines changed

9 files changed

+256
-10
lines changed

.github/workflows/cd-pc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
- name: Checkout
3636
uses: actions/checkout@v4
3737
with:
38-
submodules: ${{matrix.backend.name == 'GNS'}}
38+
submodules: true
3939

4040
- name: Cache Steamworks SDK
4141
if: matrix.backend.name == 'Steamworks'

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "GameNetworkingSockets"]
22
path = GameNetworkingSockets
33
url = https://github.com/ValveSoftware/GameNetworkingSockets.git
4+
[submodule "NetBuff"]
5+
path = NetBuff
6+
url = https://github.com/copyrat90/NetBuff.git

CMakeLists.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
cmake_minimum_required(VERSION 3.25)
2-
project(nalchi VERSION 0.1.2)
1+
cmake_minimum_required(VERSION 3.26)
2+
project(nalchi VERSION 0.1.3)
33

44
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
55

@@ -26,6 +26,10 @@ endif()
2626
option(NALCHI_BUILD_TESTS "Build nalchi tests" FALSE)
2727
option(NALCHI_ASAN "Enable AddressSanitizer for nalchi" FALSE)
2828

29+
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
30+
option(NALCHI_POOL_MSGS "Pool `SteamNetworkingMessage_t` in uni/multicast" TRUE)
31+
endif()
32+
2933
# nalchi target
3034
add_library(nalchi)
3135
target_include_directories(nalchi
@@ -39,6 +43,13 @@ INTERFACE
3943
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
4044
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}>"
4145
)
46+
47+
if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND NALCHI_POOL_MSGS)
48+
add_subdirectory(NetBuff EXCLUDE_FROM_ALL SYSTEM)
49+
target_link_libraries(nalchi PRIVATE $<BUILD_LOCAL_INTERFACE:NetBuff>)
50+
target_compile_definitions(nalchi PUBLIC NALCHI_POOL_MSGS)
51+
endif()
52+
4253
if(BUILD_SHARED_LIBS)
4354
target_compile_definitions(nalchi PRIVATE NALCHI_BUILD_EXPORT)
4455
else()

NetBuff

Submodule NetBuff added at 1205d17

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
## Features
1010

1111
* Efficient [multicast](https://nalchi-net.github.io/nalchi/classnalchi_1_1socket__extensions.html) support with reference counted [`nalchi::shared_payload`](https://nalchi-net.github.io/nalchi/structnalchi_1_1shared__payload.html).
12+
* Internal `SteamNetworkingMessage_t` is pooled on 64-bit machines.
1213
* Bit-level serialization support with [`nalchi::bit_stream_writer`](https://nalchi-net.github.io/nalchi/classnalchi_1_1bit__stream__writer.html) & [`nalchi::bit_stream_reader`](https://nalchi-net.github.io/nalchi/classnalchi_1_1bit__stream__reader.html)
1314

1415
See <https://nalchi-net.github.io/nalchi/> for the full API reference.
@@ -28,7 +29,7 @@ nalchi supports either building with the stand-alone version of GameNetworkingSo
2829
If you don't need the Steamworks SDK integration because you're building your game for platforms other than Steam,<br>
2930
you can build the [stand-alone GameNetworkingSockets](https://github.com/ValveSoftware/GameNetworkingSockets) which is licensed under the [BSD 3-Clause "New" or "Revised" License](https://github.com/ValveSoftware/GameNetworkingSockets/blob/master/LICENSE).
3031

31-
1. Recursively clone this repo to get [GameNetworkingSockets/](GameNetworkingSockets/) submodule.
32+
1. Recursively clone this repo to get [GameNetworkingSockets/](GameNetworkingSockets/) and [NetBuff/](NetBuff/) submodules.
3233
1. Prepare the dependencies of the GameNetworkingSockets.
3334
* Refer to the [`BUILDING.md`](https://github.com/ValveSoftware/GameNetworkingSockets/blob/master/BUILDING.md) on GameNetworkingSockets.
3435
* If you're using *Developer Powershell for VS 2022* on Windows, do note that it defaults to x86 environment, which obviously doesn't work when building for the AMD64.<br>
@@ -61,6 +62,7 @@ you can build the [stand-alone GameNetworkingSockets](https://github.com/ValveSo
6162
6263
If you need to integrate with Steamworks SDK to make use of other Steamworks API functionality, you can build with Steamworks SDK.
6364
65+
1. Recursively clone this repo to get [NetBuff/](NetBuff/) submodule.
6466
1. Download the Steamworks SDK from the [Steamworks partner site](https://partner.steamgames.com/).
6567
1. Unzip it, and copy the `public/` and `redistributable_bin/` into the [sdk/](sdk/) directory.
6668
1. Run `cmake --preset nalchi-steamworks` to configure.

include/nalchi/socket_extensions.hpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#include "nalchi/typed_input_range.hpp"
77

88
#include <steam/isteamnetworkingsockets.h>
9-
#include <steam/isteamnetworkingutils.h>
109
#include <steam/steamnetworkingtypes.h>
1110

1211
#include <cstdint>
@@ -81,8 +80,9 @@ class socket_extensions
8180
std::remove_const_t<decltype(connections_count)> i = 0;
8281
for (const auto conn : connections)
8382
{
84-
// TODO: Pool message instead of allocating.
85-
messages[i] = SteamNetworkingUtils()->AllocateMessage(0);
83+
// Pool `CSteamNetworkingMessage` instead of
84+
// allocating it via `SteamNetworkingUtils()->AllocateMessage(0)`.
85+
messages[i] = allocate_message();
8686

8787
// Setup the message to send to `conn`.
8888
payload.add_to_message(messages[i], logical_bytes_length);
@@ -123,6 +123,18 @@ class socket_extensions
123123
int logical_bytes_length, int send_flags,
124124
std::int64_t* out_message_number_or_result, std::uint16_t lane = 0,
125125
std::int64_t user_data = 0);
126+
127+
private:
128+
/// @brief Pool `CSteamNetworkingMessage` instead of allocating it
129+
/// via `SteamNetworkingUtils()->AllocateMessage(0)` in 64-bit environments.
130+
///
131+
/// `SteamNetworkingUtils()->AllocateMessage(0)` will
132+
/// `new` & `delete` the `CSteamNetworkingMessage`, which is 264 bytes. \n
133+
/// That's bad, so I provide my own pooled allocation function.
134+
NALCHI_API static auto allocate_message() -> SteamNetworkingMessage_t*;
135+
136+
/// @brief Release `SteamNetworkingMessage_t` to the pool.
137+
static void release_message(SteamNetworkingMessage_t*);
126138
};
127139

128140
} // namespace nalchi

src/CSteamNetworkingMessage.hpp

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/// @file
2+
/// @brief Copy-pasted `CSteamNetworkingMessage` declaration from GNS.
3+
4+
#pragma once
5+
6+
#include <steam/steamnetworkingtypes.h>
7+
8+
#include <limits>
9+
10+
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MEM_OVERRIDE
11+
#define STEAMNETWORKINGSOCKETS_DECLARE_CLASS_OPERATOR_NEW \
12+
static void* operator new(size_t s) noexcept \
13+
{ \
14+
return malloc(s); \
15+
} \
16+
static void* operator new[](size_t) = delete; \
17+
static void operator delete(void* p) noexcept \
18+
{ \
19+
free(p); \
20+
} \
21+
static void operator delete[](void*) = delete;
22+
#else
23+
#define STEAMNETWORKINGSOCKETS_DECLARE_CLASS_OPERATOR_NEW
24+
#endif
25+
26+
typedef unsigned char byte;
27+
28+
namespace SteamNetworkingSocketsLib
29+
{
30+
31+
struct SteamNetworkingMessageQueue;
32+
33+
/// We implement priority groups using Weighted Fair Queueing.
34+
/// https://en.wikipedia.org/wiki/Weighted_fair_queueing
35+
/// The idea is to assign a virtual "timestamp" when the message
36+
/// would finish sending, and each time we have an opportunity to
37+
/// send, we select the group with the earliest finish time.
38+
/// Virtual time is essentially an arbitrary counter that increases
39+
/// at a fixed rate per outbound byte sent.
40+
typedef int64 VirtualSendTime;
41+
static constexpr VirtualSendTime k_virtSendTime_Infinite = std::numeric_limits<VirtualSendTime>::max();
42+
43+
/// Actual implementation of SteamNetworkingMessage_t, which is the API
44+
/// visible type. Has extra fields needed to put the message into intrusive
45+
/// linked lists.
46+
class CSteamNetworkingMessage : public SteamNetworkingMessage_t
47+
{
48+
public:
49+
STEAMNETWORKINGSOCKETS_DECLARE_CLASS_OPERATOR_NEW
50+
static CSteamNetworkingMessage* New(uint32 cbSize);
51+
static void DefaultFreeData(SteamNetworkingMessage_t* pMsg);
52+
53+
/// OK to delay sending this message until this time. Set to zero to explicitly force
54+
/// Nagle timer to expire and send now (but this should behave the same as if the
55+
/// timer < usecNow). If the timer is cleared, then all messages with lower message numbers
56+
/// are also cleared.
57+
// NOTE: Intentionally reusing the m_usecTimeReceived field, which is not used on outbound messages
58+
inline SteamNetworkingMicroseconds SNPSend_UsecNagle() const
59+
{
60+
return m_usecTimeReceived;
61+
}
62+
inline void SNPSend_SetUsecNagle(SteamNetworkingMicroseconds x)
63+
{
64+
m_usecTimeReceived = x;
65+
}
66+
67+
/// "Virtual finish time". This is the "virtual time" when we
68+
/// wound have finished sending the current message, if any,
69+
/// if all priority groups were busy and we got our proportionate
70+
/// share.
71+
inline VirtualSendTime SNPSend_VirtualFinishTime() const
72+
{
73+
return m_nConnUserData;
74+
}
75+
inline void SNPSend_SetVirtualFinishTime(VirtualSendTime x)
76+
{
77+
m_nConnUserData = x;
78+
}
79+
80+
/// Offset in reliable stream of the header byte. 0 if we're not reliable.
81+
inline int SNPSend_ReliableStreamSize() const;
82+
83+
inline bool SNPSend_IsReliable() const;
84+
85+
inline int64 SNPSend_ReliableStreamPos() const;
86+
inline void SNPSend_SetReliableStreamPos(int64 x);
87+
88+
// Working data for reliable messages.
89+
struct ReliableSendInfo_t
90+
{
91+
int64 m_nStreamPos;
92+
93+
// Number of reliable segments that refer to this message.
94+
// Also while we are in the queue waiting to be sent the queue holds a reference
95+
int m_nSentReliableSegRefCount;
96+
int m_cbHdr;
97+
byte m_hdr[16];
98+
};
99+
const ReliableSendInfo_t& ReliableSendInfo() const;
100+
ReliableSendInfo_t& ReliableSendInfo();
101+
102+
/// Remove it from queues
103+
void Unlink();
104+
105+
struct Links
106+
{
107+
SteamNetworkingMessageQueue* m_pQueue;
108+
CSteamNetworkingMessage* m_pPrev;
109+
CSteamNetworkingMessage* m_pNext;
110+
111+
inline void Clear()
112+
{
113+
m_pQueue = nullptr;
114+
m_pPrev = nullptr;
115+
m_pNext = nullptr;
116+
}
117+
};
118+
119+
/// Intrusive links for the "primary" list we are in
120+
Links m_links;
121+
122+
/// Intrusive links for any secondary list we may be in. (Same listen socket or
123+
/// P2P channel, depending on message type)
124+
Links m_linksSecondaryQueue;
125+
126+
void LinkBefore(CSteamNetworkingMessage* pSuccessor, Links CSteamNetworkingMessage::* pMbrLinks,
127+
SteamNetworkingMessageQueue* pQueue);
128+
void LinkToQueueTail(Links CSteamNetworkingMessage::* pMbrLinks, SteamNetworkingMessageQueue* pQueue);
129+
void UnlinkFromQueue(Links CSteamNetworkingMessage::* pMbrLinks);
130+
131+
//private:
132+
// Use New and Release()!!
133+
inline CSteamNetworkingMessage()
134+
{
135+
}
136+
inline ~CSteamNetworkingMessage()
137+
{
138+
}
139+
static void ReleaseFunc(SteamNetworkingMessage_t* pIMsg);
140+
};
141+
142+
} // namespace SteamNetworkingSocketsLib

src/socket_extensions.cpp

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
#include "nalchi/socket_extensions.hpp"
22

3+
#if defined(NALCHI_POOL_MSGS)
4+
#define NB_OBJ_POOL_CHECK false
5+
#include "CSteamNetworkingMessage.hpp"
6+
#include <NetBuff/LockfreeObjectPool.hpp>
7+
#include <cassert>
8+
#else
9+
#include <steam/isteamnetworkingutils.h>
10+
#endif
11+
312
namespace nalchi
413
{
514

15+
#if defined(NALCHI_POOL_MSGS)
16+
static nb::LockfreeObjectPool<SteamNetworkingSocketsLib::CSteamNetworkingMessage, true> g_msg_pool;
17+
#endif
18+
619
NALCHI_API void socket_extensions::unicast(ISteamNetworkingSockets* sockets, HSteamNetConnection connection,
720
nalchi::shared_payload payload, int logical_bytes_length, int send_flags,
821
std::int64_t* out_message_number_or_result, std::uint16_t lane,
922
std::int64_t user_data)
1023
{
11-
// TODO: Pool message instead of allocating.
12-
SteamNetworkingMessage_t* msg = SteamNetworkingUtils()->AllocateMessage(0);
24+
// Pool `CSteamNetworkingMessage` instead of
25+
// allocating it via `SteamNetworkingUtils()->AllocateMessage(0)`.
26+
SteamNetworkingMessage_t* msg = allocate_message();
1327

1428
// Setup the message to send to `conn`.
1529
payload.add_to_message(msg, logical_bytes_length);
@@ -33,4 +47,65 @@ NALCHI_API void socket_extensions::multicast(ISteamNetworkingSockets* sockets, u
3347
std::span<std::int64_t>(out_message_number_or_result, connections_count), lane, user_data);
3448
}
3549

50+
NALCHI_API auto socket_extensions::allocate_message() -> SteamNetworkingMessage_t*
51+
{
52+
#if defined(NALCHI_POOL_MSGS)
53+
SteamNetworkingSocketsLib::CSteamNetworkingMessage& msg = g_msg_pool.construct();
54+
55+
msg.m_cbSize = 0;
56+
msg.m_pData = nullptr;
57+
msg.m_pfnFreeData = nullptr;
58+
59+
// Clear identity
60+
// msg.m_conn = k_HSteamNetConnection_Invalid; // will be set on uni/multicast
61+
msg.m_identityPeer.m_eType = k_ESteamNetworkingIdentityType_Invalid;
62+
msg.m_identityPeer.m_cbSize = 0;
63+
64+
// Set the release function
65+
msg.m_pfnRelease = release_message;
66+
67+
// Clear these fields
68+
msg.m_nConnUserData = 0;
69+
msg.m_usecTimeReceived = 0;
70+
msg.m_nMessageNumber = 0;
71+
msg.m_nChannel = -1;
72+
// msg.m_nFlags = 0; // will be set on uni/multicast
73+
// msg.m_idxLane = 0; // will be set on uni/multicast
74+
75+
msg.m_links.Clear();
76+
msg.m_linksSecondaryQueue.Clear();
77+
78+
return &msg;
79+
#else
80+
return SteamNetworkingUtils()->AllocateMessage(0);
81+
#endif
82+
}
83+
84+
void socket_extensions::release_message(SteamNetworkingMessage_t* msg)
85+
{
86+
#if defined(NALCHI_POOL_MSGS)
87+
SteamNetworkingSocketsLib::CSteamNetworkingMessage* c_msg =
88+
static_cast<SteamNetworkingSocketsLib::CSteamNetworkingMessage*>(msg);
89+
90+
// Free up the buffer, if we have one
91+
if (c_msg->m_pData && c_msg->m_pfnFreeData)
92+
(*c_msg->m_pfnFreeData)(c_msg);
93+
c_msg->m_pData = nullptr; // Just for grins
94+
95+
// We must not currently be in any queue. In fact, our parent
96+
// might have been destroyed.
97+
assert(!c_msg->m_links.m_pQueue);
98+
assert(!c_msg->m_links.m_pPrev);
99+
assert(!c_msg->m_links.m_pNext);
100+
assert(!c_msg->m_linksSecondaryQueue.m_pQueue);
101+
assert(!c_msg->m_linksSecondaryQueue.m_pPrev);
102+
assert(!c_msg->m_linksSecondaryQueue.m_pNext);
103+
104+
// Self destruct
105+
g_msg_pool.destroy(*static_cast<SteamNetworkingSocketsLib::CSteamNetworkingMessage*>(msg));
106+
#else
107+
msg->Release();
108+
#endif
109+
}
110+
36111
} // namespace nalchi

vcpkg.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
33
"name": "nalchi",
4-
"version-string": "0.1.2",
4+
"version-string": "0.1.3",
55
"description": "Utilities for efficient message sending over Valve's GameNetworkingSockets",
66
"homepage": "https://github.com/nalchi-net/nalchi",
77
"dependencies": [

0 commit comments

Comments
 (0)