I don’t usually write up big “here’s what we’re planning on in the future” posts like this. In fact, it’s probably well known by now that I don’t provide ETAs on features or bug fixes because the roadmap for plugins changes frequently.
However, at this point I’ve got a fairly good idea of what the remainder of 2023 and the first half of 2024 is going to look like in terms of EOS Online Subsystem development. It means it will take a longer to get to the next big feature release and a little longer to address support requests while I do important work on the plugin internals, so I wanted to go through what is being done so you know how this benefits you in the future.
This is a very big post though, so for those of you that want the tl;dr, here’s the summary:
bUseLobbiesIfAvailableand address long-standing bugs and feature requests
Before we get into what is being worked on, let’s take a look at where EOS Online Subsystem is today.
As of right now, the Marketplace Edition of EOS Online Subsystem is used by over 1,200 Unreal Engine teams worldwide, with an additional 2,000 students and low income developers leveraging the Free Edition in their projects. That’s a pretty big user base! It means if there’s a bug or an edge case, it usually gets reported to me and goes on the issue tracker (login required).
For some of these bugs, particularly around sessions and lobbies, the underlying code is quite old, dating back to 2020 when Unreal Engine 4.25 was still the latest release. Since then, there have been some big changes both on the Unreal Engine side and on our side:
bUseLobbiesIfAvailableon the sessions interface, also added in Unreal Engine 4.27. We still don’t support this because the implementation of
IOnlineSessionis strongly tied to the session APIs in the EOS SDK, and can’t easily support the lobby APIs.
The pressure from these items have been increasing over time, and with the release of EOS SDK 1.16 regressing the ability to view lobby members in search results (impacting the Matchmaking plugin), it is now at a point where these issues need to be dealt with properly. It means I need to spend a bunch of development time refactoring the internals of the plugin.
I know how important backwards and forwards compatibility is to game developers, so I want to stress: there are no breaking changes here. There might be a few minor migration steps in upcoming plugin releases where you need to update some config/INI files, but nothing that requires you to change your game code. This refactoring effort is purely so that I can address long-standing bugs and feature requests from customers.
Online Services (OSSv2) is the new online API surface introduced in 4.27, and it cleans up a lot of the poor API design from the Online Subsystem (OSSv1) API.
For example, to create a session in OSSv1, you need to register a global event handler, call the
CreateSession method, and then unregister the global event handler when you’re done. This API pattern sucks, and it makes tracking state from “I want to create a session” to “the session has been created” far more clunky than it needs to be:
Compare this with the OSSv2 API for creating a session:
By every standard, this is a better API. There’s no global event to register or unregister, passing state is as simple as using lambda captures, and you can perform multiple operations in parallel. Every operation in OSSv2 is like this; not only sessions, but leaderboards, achievements, and so on and so forth.
So how do we support OSSv2? Well, one way would be to implement
OnlineServicesRedpointEOS with brand new code, and have it completely separate to the existing
OnlineSubsystemRedpointEOS for OSSv1.
This causes a problem however: you’d be able to use one or the other, but they’d fundamentally have different states. Perhaps this is manageable in game code when starting development on a new game, but it is impractical if you are trying to gradually migrate an existing game or if you have multiple plugins each accessing a different API (e.g. one plugin uses OSSv1 and another uses OSSv2).
What we actually need to do is store all the relevant state for EOS in an independent module that our OSSv1 and OSSv2 implementations can both use as a source of truth. That way, regardless of which API you’re accessing, you’ll see the same state: create a session in OSSv1, and you’ll see it the session present when using OSSv2. It means turning the current architecture which looks like this:
graph TD GameCode[Game code] OSSv1[OSSv1 Interfaces] EOSSDK["EOS SDK"] subgraph EOSOnlineSubsystem["EOS Online Subsystem"] subgraph OnlineSubsystemRedpointEOS["OnlineSubsystemRedpointEOS Module"] OnlineSessionInterfaceEOS end end GameCode --> OSSv1 OSSv1 --> EOSOnlineSubsystem OnlineSessionInterfaceEOS --> EOSSDK
graph TD PluginACode[Plugin A code] GameCode[Game code] PluginBCode[Plugin B code] OSSv1[OSSv1 Interfaces] OSSv2[OSSv2 Interfaces] EOSSDK["EOS SDK"] subgraph EOSOnlineSubsystem["EOS Online Subsystem"] RedpointEOSCore["RedpointEOSCore Module"] subgraph OnlineSubsystemRedpointEOS["OnlineSubsystemRedpointEOS Module"] OnlineSessionInterfaceEOS end subgraph OnlineServicesRedpointEOS["OnlineServicesRedpointEOS Module"] SessionsRedpointEOS end end PluginACode --> OSSv1 GameCode --> OSSv1 GameCode --> OSSv2 PluginBCode --> OSSv2 OSSv1 --> OnlineSubsystemRedpointEOS OSSv2 --> OnlineServicesRedpointEOS OnlineSessionInterfaceEOS --> RedpointEOSCore SessionsRedpointEOS --> RedpointEOSCore RedpointEOSCore --> EOSSDK
This means we’ll track things like the current login state and cached data inside a “RedpointEOSCore” module. This module also manages having different EOS SDK platform instances for different play-in-editor windows and ticks the EOS SDK platform as needed. Since we control the API surface of “RedpointEOSCore”, we’re no longer beholden to changes Epic makes in the OSSv1 or OSSv2 interfaces; we can simply update the wrapping code that implements OSSv1 and OSSv2 without having to refactor the plugin code in the future.
This alone is not a trivial amount of work. It involves taking the code inside classes such as
OnlineSessionInterfaceEOS and moving them to an independent module that does not rely on the types specific to OSSv1 or OSSv2. This has to be done for every implementation inside OnlineSubsystemRedpointEOS, including achievements, avatars, entitlements, friends, identity & authentication, leaderboards, lobbies, parties, presence, purchasing & e-commerce, stats, title file, user cloud and voice chat.
However, once this work is done, you’ll be able to use both Online Subsystem (OSSv1) and Online Services (OSSv2) in your games, and incrementally upgrade to the newer OSSv2 over time as you write new code. Third-party plugin developers can write integrations against either OSSv1 or OSSv2, and EOS Online Subsystem will be able to work with them.
The EOS SDK is a low-level C API. This means that when the plugin wants to call an asynchronous API, it needs to do the work of marshalling arguments and managing callbacks. We have a few helper functions in place to make the asynchronous callback management a bit easier, but there’s still quite a bit of marshalling code that needs to be written every time the SDK is called.
Take this condensed example for querying leaderboards:
There’s quite a bit going on here, including setup of the
StatInfo pointer (not shown). Before every call to this API, we have to allocate an array of product user IDs to the heap, and then when the callback comes back via
EOSRunOperation we have to release that heap memory. We also need to wrap the current instance in a weak pointer (with
GetWeakThis) and check it’s validity in the callback with
PinWeakThis in case the online subsystem has been shutdown by the time the callback happens.
Now thankfully this particularly complex API call is only made in one place, but the pattern is the same for every API call that the plugin makes to the EOS SDK, and it’s rather error prone. If I don’t initialize a parameter correctly, or miss the call for releasing heap memory upon callback, this impacts downstream customers. And if you’re a customer who needs to access the SDK directly (perhaps you want to query leaderboards between specific times), you also need to do all this marshalling and callback handling in your game code.
Of note, string marshalling is particularly painful. Each API parameter in the EOS SDK can either be UTF-8 or ANSI, and it’s not well documented which parameter is which encoding. There are some internal helpers in the form of the
EOSString_ classes, but they’re not comprehensive enough to cover every API parameter.
To address these shortcomings, I’ve been adding a
RedpointEOSAPI module, which provides a higher-level one-to-one mapping of the EOS SDK that uses Unreal types and modern C++ for parameters. For example, let’s look at calling
EOS_Lobby_CreateLobby using the new API:
There’s no memory management required at the call site, we can leverage Unreal’s delegate system to handle
this validity, and all parameters for the call are required to be set through the
TRequired helper. Everything is namespaced so we avoid polluting the global scope, and each API call knows if the underlying
EOS_HPlatform instance is still valid before making the SDK call through the new
Defining each of these API calls is made rather straightforward with macros as well:
In a future update of EOS Online Subsystem, you’ll be able to use these modern APIs in your own game code, so you don’t need to do manual memory management or string marshalling. As an added benefit, we’ll also be able to handle some types of EOS SDK API changes at this layer (such as the recent RTC changes in 1.16), so you have a consistent API surface regardless of which EOS SDK version you’re targeting.
As previously mentioned, the implementation inside
OnlineSessionInterfaceEOS is strongly tied to the session APIs in the EOS SDK. The lobby and party interfaces are much the same in that they are tied to the lobby APIs in the EOS SDK.
At a minimum, we need to pull these implementations out of the online subsystem so we can support Online Services (OSSv2) in the future, and this is an opportunity to address architectural issues and bugs that have been around for a while.
At the EOS SDK level, sessions and lobbies have similar interfaces, but they’re still different in a few important ways. For example, sessions support the concept of a host address (known as a connection string in Unreal Engine terminology), but lobbies do not. This means that if we want to support
bUseLobbiesIfAvailable we have to “backfill” this functionality for lobbies at the plugin level, so that regardless of whether you’re using EOS sessions or EOS lobbies, you can still get the connection string for a player-hosted listen server.
A trivial solution might be to set the player’s user ID into an “address” attribute when the sessions interface happens to create a lobby instead, but this quickly becomes impractical for a few reasons:
Instead of going down this route, we’re pursuing a general-purpose “rooms” concept which can encapsulate the APIs both sessions and lobbies. Internally when creating a room, we specify what we want to back it with (sessions or lobbies), as well as the requested features of a room.
Any number of room features can be attached to a room, and room features including things such as “server address room feature” and “voice chat room feature”. Then, the session room provider and lobby room provider can implement these features in a way that is suitable for the underlying EOS SDK API. Sometimes this will be a direct mapping; the session room provider can simply use the
HostAddress field of the session, and other times it will be more involved, such as the lobby room provider storing and retrieving the host address as a custom lobby attribute.
Systems in the rest of the plugin, such as the listen tracking feature which hides sessions if there’s no server running, can then interact with the generalised room API. Those systems don’t need to know the specifics of sessions or lobbies to work.
In addition, unlike the current architecture where
OnlineSessionInterfaceEOS directly calls the synthetic session/party manager when sessions change, room providers don’t need to be aware of native platform integrations. Instead, functionality such as “allow players to join the game via Steam’s Join Game button” is wired up through event sinks, which are again abstracted away from the underlying room implementation. It is the hope that this change will allow EOS Online Subsystem to better support native platform invites, such as adding support for representing parties as Steam parties (instead of as Steam sessions) and improving our support for native platform invites on consoles.
While the rooms API is not something you’re likely to interact with directly as a game developer, it means that the session, lobby and party interfaces exposed to you via OSSv1 and OSSv2 will be more consistent and stable, and we’ll be able to address some of those long-standing bugs and feature requests on the issue tracker.
So, that’s a lot of work to undertake and a lot of refactoring to do. Hopefully this post gives you insight into why this work is important for the future and why it’s going to take a little longer to get to a feature release for EOS Online Subsystem than usual.