Matchmaking Key Concepts #

To support matching players together into high-quality games quickly and at scale, Pragma has created a matchmaking service that implements a streaming-based algorithm for creating matches. In this topic, we’ll take a look at the key concepts employed within our matchmaker and explain how you can create quality matches quickly for your players.

Matchmaking Overview #

Matchmaking process overview

The above image shows a simplified matchmaking process. At a basic level, the Matchmaking process works as follows:

  1. Parties enter the Matchmaking service from the Party service, and are placed into a queue as a Matchable object.
  2. Matchmaking service works through the matchmaking loop (a-e in the image), using custom logic defined in the Matchmaking Plugin to compare Matchables and determine if two Matchables are appropriate matches for a game instance.
  3. The Matchmaking service can move parties between Matchables to make higher quality matches.
  4. Once an appropriate match is found, the Matchmaking service can send those matches to the Game Instance service. New game instances that have been created can remain within the matchmaking flow or reenter it at any time to accept more players.

Continue reading for more information about matchmaking queues, the matchmaking loop, and different approaches to matchmaking.

Matchmaking Queues #

At the heart of the Pragma matchmaking service are a set of queues. Parties, as well as active game instances seeking more players, are placed in a queue when they enter matchmaking. At this point the parties become Matchables and the game instances become Matchmaking.GameInstances.

Queues segment Matchables/Matchmaking.GameInstances to ensure only parties within the same queue can be matched together. You have full control over how many queues there are, and which specific queue a party or game instance will enter. This allows you to set matchmaking logic that applies only to one specific group.

Parties in different queues will never be compared, and thus will never enter a game instance together.

Each queue is defined by its unique Matchmaking queue key. A matchmaking queue key contains two pieces of information, both of which are set in the Party service: a GameServerVersion that splits queues by the version of the game server, and a custom ExtMatchmakingKey that provides a location for developers to define variables they want to use to split queues. Having a blank ExtMatchmakingKey and GameServerVersion causes all players to enter one global queue.

For example, consider a game that has multiple game modes: 3v3 Ranked, 3v3 Casual, and Co-op Adventure. In this case, we configure the ExtMatchmakingKey proto to consider the game mode.

message ExtMatchmakingKey {
    GameMode game_mode = 1;

enum GameMode {
    Unspecified = 0;
    ThreeVsThreeRanked = 1;
    ThreeVsThreeCasual = 2;
    CoopAdventure = 3;

The Matchmaking Loop #

The Pragma matchmaking service works in loops to compare parties within the same queue and send viable matches to the same game instance. When parties enter matchmaking they become Matchable objects. When active game instances enter matchmaking they become Matchmaking.GameInstance objects. Both Matchable and Matchmaking.GameInstance objects can take parties from other Matchables to form a group of parties to send to a single game instance.

Matching Parties #

When the matchmaking process begins, the Matchmaking service selects the oldest member of the queue as the anchor Matchable. This Matchable is compared with every other Matchable in the queue, starting with the next oldest member (relative to the current anchor), to check viability of combining parties into one Matchable. The Matchable being compared to the anchor Matchable is called the other Matchable. When making comparisons, you have access to each queue member’s data, all stored in-memory, allowing the matchmaker to make thousands of comparisons per second.

During each of these local comparisons, the matchmaking engine invokes the Matchmaking Plugin’s matchParties method, which includes custom logic to determine whether to produce any of the following changes to the Matchables:

  • Produce a new game instance: If there’s a good enough match for players to begin playing a game, send them to the game via a NewGameInstance object.
  • Take parties from the other Matchable queue member: If adding some parties from the other Matchable to the anchor Matchable would improve the quality of the possible ensuing match, move those parties into your anchor Matchable.
  • Take parties from the anchor Matchable queue member: If removing some parties from the anchor Matchable would improve match quality, move the party to the other Matchable. This is usually performed in combination with taking some parties from the other Matchable, making an exchange of parties.
  • Continue matchmaking with new anchor:If, after comparing the anchor Matchable with all other Matchables in the queue, no NewGameInstance has been generated, the anchor position is moved to the next oldest Matchable in the queue. To add custom behavior at this stage, implement endOfLoop as described in Add custom behavior after a matchmaking loop.

Matchmaking loop example

In the following image, Matchable A starts as the anchor. As the anchor works through the queue (comparing with each other Matchable all the way to Matchable n), it can take parties from or give parties to the comparing Matchable.

Match Parties Logic

Let’s say that during this loop, Party 5 was moved from Matchable C to Matchable A.

Match Parties End of Loop

However, no overall suitable match was found and thus no NewGameInstance was created. By default, the anchor position moves to the next oldest Matchable. The following image shows Matchable B, becoming the anchor Matchable.

Match Parties Logic 2

If a new party joins the queue, its Matchable is added to the end of the queue. The new Matchable will be compared to others in the queue the next time the anchor position advances.

Matching Parties with Active Game Instances #

When an active game instance enters matching from the Game Instance service, the Matchmaking service designates the Matchmaking.GameInstance the game. The Matchable in the queue that’s being compared to the game is called the anchor. When making comparisons, you have access to each queue member’s data, all stored in-memory, allowing the matchmaker to make thousands of comparisons per second.

During each of these local comparisons, the matchmaking engine invokes the Matchmaking Plugin’s matchPartiesWithGame method, which includes custom logic to determine whether parties from the anchor Matchable should be added to the active game instance. If adding some parties from the queue would improve the quality of the match in the current game instance, add those parties to the game Matchmaking.GameInstance and return a GameInstanceUpdate object.

If no suitable match is found after comparing the game instance with all Matchables in the queue, matchPartiesWithGame can return null.

You cannot remove parties from a Matchmaking.GameInstance.

Matchmaking Approaches #

The above operations enable you to produce appropriate matches. When matchmaking, one must always consider a trade-off between matchmaking speed and match quality. To ensure that every match produced is of extremely high quality, we recommend an approach of envelope expansion to balance the competing concerns of producing matches as quickly as possible while also making the highest quality matches. Envelope expansion is the process of defining a set of time-in-queue-based envelopes describing what quality of match to create. The longer a party is in matchmaking, the more lenient the envelopes to make a match involving them should become.

Errors #

If the matchParties method throws an exception, the Matchmaking service logs an error and removes both the anchor Matchable and the other Matchable from the queue to allow for debugging. Players are notified via a SessionChangedV1Notification. The anchor position is then reset to the oldest Matchable in the queue.