Game Dev

Ping 9999 in Unreal Engine’s Steam Session Searches

One of the common issues when writing an online multiplayer game for Steam in Unreal Engine is the 9999 ping value in search results when browsing for sessions. You can see it just running the ShooterGame example on two different machines with the Steam subsystem enabled and a Steam client opened. When you search for sessions, you will see a 9999 ping value for all the results.

The 9999 value is not an Unreal Engine bug, nor it is a problem in your game code or configuration. It just depends on the Steam backend implementation. Here I’m going to explain everything in detail.

Where does the 9999 value come from

The 9999 value for the PingInMs property of the class FOnlineSessionSearchResult is just the MAX_QUERY_PING value, which is the default value set for this property in the class constructor. You are reading this value in your search results just because Unreal Engine is not setting the value at all when parsing the session search results. To understand why, we need to look first at the Steamworks APIs for lobbies and game servers; than we will see how the Steam Online Subsystem is mapped on those APIs under the hood.

STEAM Matchmaking, Lobbies and Game Servers

The Steamworks API offers a set of interfaces for online games:

  • Matchmaking & Lobbies – is the interface for creating lobbies and search for suitable games
  • Game Server – is the interface for creating Game servers (more details below).

There isn’t any detailed documentation on the Steamworks API. The only documentation available can be found on the Steamworks website (you must be logged in to read the full documentation and download the SDK). The best documentation source is in the API interfaces source code itself: the Steam SDK and the Steamworks example distributed with the SDK.

In the Steamworks Space War example (an asteroid-like multiplayer game), you can create and search for lobbies and game servers.

Steam Lobbies

In the Steam context, lobbies are server objects which have an owner and a list of user members. Users can join and leave lobbies, the owner can change and the lobby persists until the last user leaves it. Users in a lobby can communicate via multicast chat messages.

Lobbies can be searched via the Steam Matchmaking interface using the SteamMatchmaking()->RequestLobbyList() method.

When you parse the results from this search, you can retrieve lobby data such as Lobby owner and Lobby name. Unfortunately, no lobby ping information is available through the Steamwork API.

Lobby

A Lobby in Space War

Steam Game Servers

If you create a Game Server in the Space War example, you will get directly in game, allowing for other users to find you and join the game. A game server is also created when users go from the lobby to the game.

Space war gameplay (Game Server)

If you run the Space War example on two machines with an open Steam client, you can make some experiments with Game servers:

  • If the two machines are on the same LAN and you create a game server on the first machine, you can find the server from the second machine using the Find LAN Servers menu command (SteamMatchmakingServers()->RequestLANServerList is called). In the search results you can see the value of the server ping.
Server search results on LAN (note the ping)

Server search results on LAN (note the ping)

  • If you try to find the same game server using the Find Internet Servers menu command (SteamMatchmakingServers()->RequestInternetServerList is called), you won’t find any result. This because the Server search works in two steps: first a server list is retrieved from the Steam server backend, then each Game server is queried directly through its QueryPort (set by the SteamGameServer_Init function). If your game server is not binded to a public IP address, the query fails and you won’t find it.

Unreal Engine Steam Online Subsystem

The online subsystem for Steam in Unreal Engine maps the abstract Online Subsystem interfaces into the Lobby or the Game Server interfaces depending on the bIsPresence session flag specified in the FOnlineSessionSettings object passed to the CreateSession method of the IOnlineSession interface.

The Lobby-based implementation of the Steam Online Subsystem is defined in the OnlineSessionAsyncLobbySteam.h / OnlineSessionAsyncLobbySteam.cpp Unreal Engine source files.

The GameServer-based implementation of the Steam Online Subsystem is defined int the OnlineSessionAsyncServerSteam.h / OnlineSessionAsyncServerSteam.cpp Unreal Engine source files.

If you create a session with presence, which is the kind of session that the Shooter Game example is creating, then you will be able to find other sessions even if they have been created from machines with private IP addresses on LANs. But if you change the bIsPresence flag to false when creating the session, you won’t find anything unless you are running the server on a public address.

So, unless you are writing a dedicated server for your game, you will need to stick to a session with presence.

Let’s have a look at the source code

Let’s see how Unreal Engine populates the session search results in both modes: Lobby and Server:

  • Lobby – In Lobby mode (bIsPresence = true) the session result data is populated in the OnlineSessionAsyncLobbySteam.cpp source code file, inside the ParseSearchResult method. Here only the Session property of the FOnlineSessionSearchResult object is written using the FillSessionFromLobbyData method. The PingInMs property is not written and remains at its 9999 default value.

  • Game Server – in Server mode (bIsPresence = false) the search result data (FOnlineSessionSearchResult) is populated in the OnlineSessionAsyncServerSteam.cpp source code file, inside the ParseSearchResult method. Here you can see that the PingInMs property of the FOnlineSessionSearchResult object is written based on the gameserveritem_t data obtained by the Steamworks SDK.

Adding ping values to presence session searches

We have seen that the missing values for pings in session search results are not an Unreal Engine issue but a feature derived from the Steamworks API.

There are two things you can do to deal with this problem:

  1. Don’t care about pings. Steamworks matchmaking documentation says that “The results are returned ordered by geographical distance”. Although this definition is based on a geographical distance, we can suppose that this distance can be tied to the roundtrip time (which is the ping) values. I don’t know exactly what’s going on under the hood of the Steam Matchmaking backend, but it is reasonable to think that a matchmaking algorithm must take into account low roundtrip times when ordering match results. So it could be fine to forget about pings in your results and thrust the matchmaking ordering.
  2. Implement ping queries inside the Steam Online Subsystem. This second solution is for people with a good understanding of the Unreal Engine internal module structure and extension capabilities. You can modify the Steam Online Subsystem module, or create another module based on the original one if you don’t want to modify the engine. The new code added should allow the game to query each lobby owner in the search results with a ping packet (containing a timestamp) and receive back the same packet in response. On the send (client) side you need to create an UDP server socket to receive the ping acknowledge packet, then send a ping packet to the Steam address of the lobby owner. On the lobby owner (server) side you need to receive and recognize the ping packet and send it back to the sender. Unreal Engine sockets are implemented on the Steam Subsystem using the Steam P2P interface, which is connectionless. The first packet will take very long (a second) to reach the destination, but after the first packet, the communication will be very fast. You have to send a burst of packets to the lobby owner and take the minimum ping after receiving the ping acknowledges packets.

Some code examples

In your GameSession class, you may have an handler to call after a session search completes. In this handler you can parse each session result and send some ping packet to each session’s owner, see below.

These ping packets will be received by the StatelessConnectHandlerComponent of the USteamNetDriver which is inited in the UNetDriver::InitConnectionlessHandler of the UNetDriver base class.

When the packet arrives, it will be processed by the UIpNetDriver::TickDispatch. Since a connection is not established yet, this packet will be passed to the
ConnectionlessHandler->IncomingConnectionless
wich will eventually call the StatelessConnectHandlerComponent::IncomingConnectionless. This method will handle the initial connection handshake during the creation of a new connection. Here we have to manage our special Ping packets by sending them back to the client.

[update] A full working ping implementation

If you want to try a full working implementation, check out Elliotek code here.

8 thoughts on “Ping 9999 in Unreal Engine’s Steam Session Searches

    1. Giuseppe Post author

      That paragraph about modifying the engine is only a memo for me in the case I’ll decide to implement ping evaluation in the future. At the moment I don’t have any other code, sorry.

      Reply

Leave a Reply