Skip to content

Commit

Permalink
feat: Matchmaker Rework (#431)
Browse files Browse the repository at this point in the history
This PR covers a large rework of the matchmaker and its entire flow to
bones & the dev interface. In short the major changes include:

#### Matchmaker:
- Supports multiple games running a single matchmaker, thanks to GameID
now integrated
- Internal state revamped to support now both Matchmaking & newly added
Lobbies
- Bones <-> Matchmaker messaging flow rework to support more than just 1
"endpoint", and importantly enable long-lived connections which is a
requirement for lobbies
- Updated the interface for devs to interact with the matchmaker to
provide a standardized/clean interface that we can scale with new
features


#### Matchmaking Changes:
- Robustness was the main focus, so now implements a simple wait/retry
protocol when the matchmaking room is full, thus avoiding previous
issues with failure during lots of players starting matchmaking at once.
- Implements dead connection cleaning, guaranteeing that when a match
starts all player clients have active connections (are alive) and
starting the match can proceed
- Adds an explicit stop matchmaking search message, making the
matchmaking flow more "proper/clean" and allowing us to do proper local
error checking to prevent bad states without straining the matchmaker
(ie. you can't create a lobby if you're in matchmaking).
- A number of smaller edge case fixes/reworking internals to make things
nicer to work with.


### Lobbies Changes:
- They now exist, freshly implemented
- The core mechanics are in place for list/create/join lobbies
- Supports password-protected lobbies to make it easy for users to play
with their friends

Of note, this PR is already quite large and covers the majority of
breaking changes to the matchmaker needed in the short term, while
getting the flow of matchmaking to feel quite robust with the edge cases
addressed. Lobbies still need further work to be robust, and the
randomness seed/match_data pushing into the ggrs session runner hasn't
been done here yet, however I feel this PR is encompassing enough as-is
and would prefer to move forward with smaller/lighter PRs for the extra
functionality.

---------

Co-authored-by: Max Whitehead <35712032+MaxCWhitehead@users.noreply.github.com>
Co-authored-by: Jeremias <jerccu@gmail.com>
Co-authored-by: Zicklag <zicklag@katharostech.com>
Co-authored-by: Tekhnaeraav <tekhnaeraav@katharostech.com>
  • Loading branch information
5 people authored Oct 2, 2024
1 parent 1ecb3dd commit 2f4e7ac
Show file tree
Hide file tree
Showing 16 changed files with 1,384 additions and 478 deletions.
46 changes: 27 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion framework_crates/bones_framework/src/networking.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Networked multi-player plugin.

Jumpy uses a Peer-to-Peer, rollback networking model built on [GGRS].
Bones uses a Peer-to-Peer, rollback networking model built on [GGRS].

Messages are serialized/deserialized to a binary representation using [`serde`] and the [`postcard`]
crate.
Expand Down
2 changes: 2 additions & 0 deletions framework_crates/bones_framework/src/networking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use crate::input::PlayerControls as PlayerControlsTrait;
pub mod input;
pub mod lan;
pub mod online;
pub mod online_lobby;
pub mod online_matchmaking;
pub mod proto;
pub mod socket;

Expand Down
112 changes: 36 additions & 76 deletions framework_crates/bones_framework/src/networking/online.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,54 @@
Contains the online matchmaker.
# Matchmaking

## Matchmaking
For online matchmaking, we use a centralized matchmaking server to connect peers to each-other and to forward the peers' network traffic. All connections utilize UDP and the QUIC protocol.

For online matchmaking, we use a centralized matchmaking server to connect peers to each-other and
to forward the peers' network traffic. All connections utilize UDP and the QUIC protocol.
The matchmaking server is implemented in the [`bones_matchmaker`] crate. It binds to a single UDP port and listens for client connections. QUIC's multiplexing capabilities allow the server to handle any number of clients on this single port.

In order to establish the peer connections we use a matchmaking server implemented in the
[`bones_matchmaker`] crate. This server binds one UDP port and listens for client connections.
Because QUIC supports mutliplexing connections, we are able to handle any number of clients on a
single UDP port.
Once a match starts, client traffic is proxied through the matchmaking server. While not true peer-to-peer networking, clients logically send messages to each other, with the server acting as an intermediary.

All client traffic is proxied to other peers through the matchmaking server. In this way it is not
true peer-to-peer networking, but logically, once the match starts, clients are sending messages to
each-other, and the server doesn't take part in the match protocol.
Pros of this approach:
- Reduced connections per peer (one connection to the matchmaker only)
- Client IP addresses are hidden from each other
- Easier to bypass firewalls and NATs
- Simplified connection process

Having the matchmaker proxy client messages has the following pros and cons:
Cons:
- Increased server bandwidth usage
- Additional network hop between peers, potentially increasing latency

**Cons:**

- It uses up more of the matchmaking server's bandwidth
- It adds an extra network hop between peers, increasing latency.

**Pros:**

- It reduces the number of connections each peer needs to make. Each peer only holds one
connection to the matchmaking server and nothing else.
- It hides the IP addresses of clients from each-other. This is an important privacy feature.
- It avoids a number of difficulties that you may run into while trying to establish true
peer-to-peer connections, and makes it much easier to bypass firewalls, NATs, etc.

This doesn't prevent us from supporting true peer-to-peer connections in the future, though.
Similarly, another scenario we will support in the future is LAN games that you can join without
needing a matchmaking server.
This design doesn't preclude future support for true peer-to-peer connections or LAN games without a matchmaker.

[`bones_matchmaker`]: https://github.com/fishfolk/bones/tree/main/crates/bones_matchmaker

### Matchmaking Protocol

> **ℹ️ Note:** This is meant as an overview and is not an exact specification of the matchmaking
> protocol.
#### Initial Connection

When a client connects to the matchmaking server, the very first thing it will do is send a
[`RequestMatch`][crate::external::bones_matchmaker_proto::MatchmakerRequest::RequestMatch] message
to the server over a reliable channel.

This message contains the [`MatchInfo`] that tells the server how many players the client wants to
connect to for the match, along with an arbitrary byte sequence for the `match_data`.

In order for players to end up in the same match as each-other, they must specify the _exact_ same
`MatchInfo`, including the `match_data`. We use the `match_data` as a way to specify which game mode
and parameters, etc. that the player wants to connect to, so that all the players that are connected
to each-other are playing the same mode.

The `match_data` also contains the game name and version. Because the matchmaker does not take part
in the match protocol itself, just the matchmaking protocol, **this makes the matchmaking server
game agnostic**. Different games can connect to the same matchmaking server, and they can make sure
they are only connected to players playing the same game, by specifying a unique `match_data`.

> **Note:** To be clear, the game implementation sets the `match_data` for players. Players are
> never exposed directly to the concept of the `match_data`.
## Matchmaking Protocol

#### Waiting For Players
### Initial Connection

After the initial connection and match request, the server will send the client an
[`Accepted`][crate::external::bones_matchmaker_proto::MatchmakerResponse::Accepted] message.
1. The client connects to the matchmaking server.
2. The client sends a [`RequestMatchmaking`][crate::external::bones_matchmaker_proto::MatchmakerRequest::RequestMatchmaking] message over a reliable channel.
3. This message contains [`MatchInfo`] with:
- The desired number of players
- A `game_id` to identify the game
- `match_data` (arbitrary bytes for game mode, parameters, etc.)
- `player_idx_assignment` to specify how player ids should be assigned (ie. randomly assign a side for a pvp match)

If the waiting room for that match already has the desired number of players in it, the server will
then respond immediately with a
[`Success`][crate::external::bones_matchmaker_proto::MatchmakerResponse::Success] message. This
message comes with:
Players must specify identical `MatchInfo` to be matched together. The `match_data` ensures players are connected for the same game mode and version.

- a `random_seed` that can be used by all clients to generate deterministic random numbers, and
- a `player_idx` that tells the client _which_ player in the match it is. This is used throughout
the game to keep track of the players, and is between `0` and `player_count - 1`.
### Waiting for Players

#### In the Match
1. The server responds with an [`Accepted`][crate::external::bones_matchmaker_proto::MatchmakerResponse::Accepted] message.
2. While waiting, the server may send [`MatchmakingUpdate`][crate::external::bones_matchmaker_proto::MatchmakerResponse::MatchmakingUpdate] messages with the current player count.
3. When the desired number of players is reached, the server sends a [`Success`][crate::external::bones_matchmaker_proto::MatchmakerResponse::Success] message containing:
- A `random_seed` for deterministic random number generation
- The client's `player_idx`
- The total `player_count`
- A list of `player_ids` with their network addresses

Immediately after the desired number of clients have joined and the `Success` message has been sent
to all players, the matchmaker goes into proxy mode for all clients in the match.
### In the Match

Once in proxy mode, the server listens for
[`SendProxyMessage`][crate::external::bones_matchmaker_proto::SendProxyMessage]s from clients. Each
message simply specifies a [`TargetClient`][crate::external::bones_matchmaker_proto::TargetClient] (
either a specific client or all of them ), and a binary message data.
Once all players have received the `Success` message, the matchmaker enters proxy mode:

Once it a `SendProxyMessage` it will send it to the target client, which will receive it in the form
of a [`RecvProxyMessage`][crate::external::bones_matchmaker_proto::RecvProxyMessage], containing the
message data, and the index of the client that sent the message.
1. Clients send [`SendProxyMessage`][crate::external::bones_matchmaker_proto::SendProxyMessage]s to the server, specifying a target client (or all clients) and the message data.
2. The server forwards these as [`RecvProxyMessage`][crate::external::bones_matchmaker_proto::RecvProxyMessage]s to the target client(s), including the sender's player index.

The matchmaking server supports forwarding both reliable and unreliable message in this way,
allowing the game to chose any kind of protocol it sees fit to synchronize the match data.
The matchmaker supports both reliable and unreliable message forwarding, allowing the game to implement its preferred synchronization protocol.
Loading

0 comments on commit 2f4e7ac

Please sign in to comment.