CUGL 2.5
Cornell University Game Library
|
#include <CUNetcodeConnection.h>
Public Types | |
enum class | State : int { INACTIVE = -1 , CONNECTING = 0 , NEGOTIATING = 1 , CONNECTED = 2 , INSESSION = 3 , MIGRATING = 4 , DISCONNECTED = 5 , DENIED = 6 , MISMATCHED = 7 , INVALID = 8 , FAILED = 9 , DISPOSED = 10 } |
typedef std::function< void(const std::string uuid)> | ConnectionCallback |
typedef std::function< bool(bool confirmed)> | PromotionCallback |
typedef std::function< void(State state)> | StateCallback |
typedef std::function< void(const std::string source, const std::vector< std::byte > &message)> | Dispatcher |
Public Member Functions | |
NetcodeConnection () | |
~NetcodeConnection () | |
std::string | getUUID () |
bool | isHost () const |
const std::string | getHost () |
bool | isOpen () const |
State | getState () const |
size_t | getCapacity () |
void | setCapacity (size_t capacity) |
const std::string | getRoom () const |
const std::unordered_set< std::string > | getPlayers () |
const std::unordered_map< std::string, std::shared_ptr< NetcodePeer > > | getPeers () |
bool | isPlayerActive (const std::string player) |
size_t | getNumPlayers () |
size_t | getTotalPlayers () |
void | open () |
void | close () |
bool | sendTo (const std::string dst, const std::vector< std::byte > &data) |
bool | sendToHost (const std::vector< std::byte > &data) |
bool | broadcast (const std::vector< std::byte > &data) |
void | receive (const Dispatcher &dispatcher) |
void | startSession () |
void | endSession () |
void | onReceipt (Dispatcher callback) |
void | onConnect (ConnectionCallback callback) |
void | onDisconnect (ConnectionCallback callback) |
void | onStateChange (StateCallback callback) |
void | onPromotion (PromotionCallback callback) |
void | setDebug (bool flag) |
bool | getDebug () const |
Static Public Member Functions | |
static std::shared_ptr< NetcodeConnection > | alloc (const NetcodeConfig &config) |
static std::shared_ptr< NetcodeConnection > | alloc (const NetcodeConfig &config, const std::string room) |
Friends | |
class | NetcodeManager |
class | NetcodeChannel |
class | NetcodePeer |
This class to supports a connection to other players with a peer-to-peer interface.
The premise of this class is to make networking as simple as possible. Simply call broadcast
with a byte vector, and then all others will receive it when they call receive
. You can use the classes NetcodeSerializer
and NetcodeDeserializer
to handle more complex types.
This class maintains a networked game using a peer-to-peer connections. One player is designated as "host", but this is purely an organizational concept. The host monitors the other players, allowing them join. But once the game starts, all communication is peer-to-peer and the question of authority are determined by the application layer.
You can use this as a true client-server by replacing all calls to broadcast
with calls to sendTo
. That way clients can send to the host and the host can broadcast its responses.
Using this class requires an external lobby websocket server to enable Web RTC data channels. This server does not handle actual game data. In only connects that players, an occasionally monitors for disconnects requiring host migration. This reduces server costs significantly. We will provide you a Docker package for a lobby server later in the semester.
This class supports optional host migration should the host be lost. Upon loss of the host, each surviving client will receive an invocation of the callback function onPromotion
. if the callback exists and returns true, that client will become a candidate to be the new host. If more than one client is a candidate, the lobby will chose the first available. If any client disconnects during the migration process, host migration will fail. See onPromotion
for more details.
It is completely unsafe for network connections to be used on the stack. For that reason, this class hides the initialization methods (and the constructors create uninitialized connections). You are forced to go through the static allocator alloc
to create instances of this class.
This type represents a callback for the NetcodeConnection
class.
This type refers to two different possible callbacks: one when a new peer connection connects and another when it disconnects. This notification goes to all connections, whether they are host or client (so there is no guarantee of a direct connection to the peer). The uuid sent to the callback indentifies the peer that connected/disconnected.
Callback functions differ from listeners (found in the input classes) in that only one callback of any type is allowed in a NetcodeConnection
class. Callback functions are guaranteed to be called at the start of an animation frame, before the method Application#update(float)
.
The function type is equivalent to
std::function<void(const std::string uuid)>
uuid | The unique identifier of the disconnecting peer |
The dispatcher is called by the receive
function to consume data from the message buffer. Not only does it relay the message data, but it also communicates the "source". For broadcast messages, this will be the value "broadcast". For private messages, it will be the UUID of the sending client. Note that only the host can receive private messages.
The function type is equivalent to
const std::function<void(const std::string source, const std::vector<std::byte>& message)>
source | The message source |
message | The message data |
This type represents a callback for the NetcodeConnection
class.
This callback is invoked when the websocket makes an offer to this connection to become host, as part of host migration. If the callback returns true, then this connection will become a candidate for the new host. However, selection is not guaranteed, as the server polls all clients simultaneously.
If the connection is actually selected as the new host, this callback will be invoked a second time with the parameter set to true. If the callback returns false on the confirmation (because of a change of heart), migration fails and all clients are disconnected.
Callback functions differ from listeners (found in the input classes) in that only one callback of any type is allowed in a NetcodeConnection
class. Callback functions are guaranteed to be called at the start of an animation frame, before the method Application#update(float)
.
The function type is equivalent to
std::function<bool(bool)>
confirmed | Whether this connection is confirmed as the new host |
This type represents a callback for the NetcodeConnection
class.
This callback is invoked when the connection state has changed. The parameter marks the new connection state of the migration. This is particularly helpful for monitoring host migrations.
Callback functions differ from listeners (found in the input classes) in that only one callback of any type is allowed in a NetcodeConnection
class. Callback functions are guaranteed to be called at the start of an animation frame, before the method Application#update(float)
.
The function type is equivalent to
std::function<void(State state)>
state | The new connection state |
|
strong |
An enum representing the current connection state.
This state is the relationship of this connection to the lobby websocket server. The peer connections and data channels have their own separate states.
Enumerator | |
---|---|
INACTIVE | The connection is initialized, but |
CONNECTING | The connection is in the initial connection phase. This represent the initial handshake with the game lobby server. This state ends when the connection is officially marked as open. |
NEGOTIATING | The connection is negotiating its role with the server (host or client) This state ends when the connection receives a role acknowledgement from the server. |
CONNECTED | The connection is complete and currently allowing players to join the room. This state ends when connection receives an acknowledgement that the host called the method |
INSESSION | The connection is actively playing the game. This states ends when the player closes the connection or destroys the socket. |
MIGRATING | The connection is migrating to a new host. This state is caused when the host connection does not end "cleanly" (e.g. with a call to |
DISCONNECTED | The connection is disconnected. This state occurs when the connection to the websocket is lost. It is typically the result of a call to |
DENIED | The connection was denied the option to join a room. This error indicates that the room is full, or the game has started. |
MISMATCHED | The connection did not match the host API version |
INVALID | The client connection specified a non-existent room |
FAILED | The connection failed with an unknown error. |
DISPOSED | This object has been disposed and is no longer available for use. |
cugl::net::NetcodeConnection::NetcodeConnection | ( | ) |
Creates a degenerate websocket connection.
This object has not been initialized with a NetcodeConfig
and cannot be used.
You should NEVER USE THIS CONSTRUCTOR. All connections should be created by the static constructor alloc
instead.
cugl::net::NetcodeConnection::~NetcodeConnection | ( | ) |
Deletes this websocket connection, disposing all resources
|
inlinestatic |
Returns a newly allocated network connection as host.
This method initializes this websocket connection with all of the correct settings. However, it does not connect to the game lobby. You must call the method open
to initiate connection. This design decision is intended to give the user a chance to set the callback functions before connection is established.
This method will always return nullptr if the NetworkLayer
failed to initialize.
config | The connection configuration |
|
inlinestatic |
Returns a newly allocated network connection as a client.
This method initializes this websocket connection with all of the correct settings. However, it does not connect to the game lobby. You must call the method open
to initiate connection. This design decision is intended to give the user a chance to set the callback functions before connection is established.
The room should match one specified by the host. If you are using the traditional CUGL lobby server, this will be a hexadecimal string.
This method will always return nullptr if the NetworkLayer
failed to initialize.
config | The connection configuration |
room | The host's assigned room id |
bool cugl::net::NetcodeConnection::broadcast | ( | const std::vector< std::byte > & | data | ) |
Sends a byte array to all other players.
Within a few frames, other players should receive this via a call to receive
or the callback function onReceipt
. As this is a broadcast message, this player will receive it as well (with the indication of this connection as the sender).
As with sendTo
, communication from a particular source is guaranteed to be ordered. So if connection A broadcasts two messages, all other connections will receive those messages in the same order. However, there is no relationship between the messages coming from different sources.
You may choose to either send a byte array directly, or you can use the NetcodeSerializer
and NetcodeDeserializer
classes to encode more complex data.
This requires a connection be established. Otherwise it will return false. It will also return false if the host is currently migrating.
data | The byte array to send. |
void cugl::net::NetcodeConnection::close | ( | ) |
Closes this connection normally.
If this method is called on a client, it simply leaves the game; the game can continue without this. If the method is called on the host, shutdown commands are issued to all of the clients. Host migration will never take place when this method is called. Migration requires that the host disconnect without first closing.
Because this requires coordination with this connection, this method does not close the connection immediately. Verify that the state is State#DISCONNECTED
before destroying this object.
void cugl::net::NetcodeConnection::endSession | ( | ) |
Marks the game as completed.
This will issue shutdown commands to call clients, disconnecting them from the game.
Note: This can only be called by the host. This method is ignored for clients.
size_t cugl::net::NetcodeConnection::getCapacity | ( | ) |
Returns the message buffer capacity.
It is possible for this connection to receive several messages over the network before it has a chance to all receive
. This buffer stores those messages to be read later. The capacity indicates the number of messages that can be stored.
Note that this is NOT the same as the capacity of a single message. That value was set as part of the initial NetcodeConfig
.
This method is not const because it requires a lock.
|
inline |
Returns the debugging status of this connection.
If debugging is active, connections will be quite verbose
const std::string cugl::net::NetcodeConnection::getHost | ( | ) |
Returns the UUID for the (current) game host
This method is not const because it requires a lock.
size_t cugl::net::NetcodeConnection::getNumPlayers | ( | ) |
Returns the number of players currently connected to this game
This does not include any players that have been disconnected.
This method is not const because it requires a lock.
const std::unordered_map< std::string, std::shared_ptr< NetcodePeer > > cugl::net::NetcodeConnection::getPeers | ( | ) |
Returns the list of peer connections for this websocket connection
If the connection is a client, there will only be one peer (the host). Otherwise, this list contains all the peer connections to the clients. Most users should never need this method, as all communication should be initiated through the websocket. It is provided for debugging purposes only.
This method is not const because it requires a lock.
const std::unordered_set< std::string > cugl::net::NetcodeConnection::getPlayers | ( | ) |
Returns the list of active players
This vector stores the UUIDs of all the players who are currently playing the game. This list will continually update as players join and leave the game.
This method is not const because it requires a lock.
|
inline |
Returns the room ID or empty string.
If this player is a client, this will return the room ID this object was constructed with. Otherwise, as host, this will return the empty string until getState
is CONNECTED.
If this connection is used with the standard CUGL lobby server, then the string will represent a hexadecimal number.
|
inline |
Returns the current state of this connection.
Monitoring state is one of the most important components of working with a NetcodeConnection
. Many state changes (moving from State#CONNECTED
to State#INSESSION
, or State#INSESSION
to State#MIGRATING
) can happen due to circumstances beyond the control of this connection. It is particularly important to pay attention to the State#MIGRATING
state, as no messages can be sent during that time.
State can either be monitored via polling with this method, or with a callback set to onStateChange
.
size_t cugl::net::NetcodeConnection::getTotalPlayers | ( | ) |
Returns the number of players present when the game was started
This includes any players that may have disconnected. It returns 0 if the game has not yet started (e.g. the state is not State#INSESSION
).
This method is not const because it requires a lock.
std::string cugl::net::NetcodeConnection::getUUID | ( | ) |
Returns a globally unique UUID representing this connection.
While room IDs are assigned by the lobby server, connections must assign their own IDs. The only way to guarantee that this IDs are unique is to use Universally Unique Identifiers (UUID) as defined here:
https://en.wikipedia.org/wiki/Universally_unique_identifier
This number is assigned open allocation of this connection. Different connections, even on the same device, have different UUIDs.
This method is not const because it requires a lock.
|
inline |
Returns true if this connection is (currently) the game host
|
inline |
Returns true if this connection is open
Technically a connection is not open if the state is CONNECTING.
bool cugl::net::NetcodeConnection::isPlayerActive | ( | const std::string | player | ) |
Returns true if the given player UUID is currently connected to the game.
This method is not const because it requires a lock.
player | The player to test for connection |
void cugl::net::NetcodeConnection::onConnect | ( | ConnectionCallback | callback | ) |
Sets a callback function to invoke on player connections
The websocket will keep a player aware of any connections that may happen. This callback will update getPlayers
after any such connection. Hence connections can be detected through polling or this callback interface. If this information is important to you, the callback interface is preferred.
All callback functions are guaranteed to be called on the main thread. They are called at the start of an animation frame, before the method Application#update(float)
.
callback | The connection callback |
void cugl::net::NetcodeConnection::onDisconnect | ( | ConnectionCallback | callback | ) |
Sets a callback function to invoke on player disconnections
The websocket will keep a player aware of any disconnections that may happen. This callback will update getPlayers
after any such disconnection. Hence disconnections can be detected through polling or this callback interface. If this information is important to you, the callback interface is preferred.
All callback functions are guaranteed to be called on the main thread. They are called at the start of an animation frame, before the method Application#update(float)
.
callback | The disconnection callback |
void cugl::net::NetcodeConnection::onPromotion | ( | PromotionCallback | callback | ) |
Sets a callback function to invoke on host migration.
Host migration occurs if the host quits (even before the play session starts) without first calling close
. During migration, the game lobby will attempt to chose a host. If present, this callback will be invoked (with the argument set to false) for all surviving clients. If no callback function is registered, it is assumed this connection declines to become host. Otherwise, if the callback returns true, this client will be considered as a candidate for the new host.
The game lobby will select a new host if at least one client returns true to the promotion request (otherwise migration fails and all clients are disconnected). If this connection is selected as the new host, the callback will be invoked a second time with the argument set to true. If the callback returns false on that second invocation, it is assumed that the migration failed and all clients are disconnected.
Host migration is only designed for isolated disconnects. If any client disconnects during the host migration process, the migration will fail and all clients will be disconnected.
All callback functions are guaranteed to be called on the main thread. They are called at the start of an animation frame, before the method Application#update(float)
.
callback | The promotion callback |
void cugl::net::NetcodeConnection::onReceipt | ( | Dispatcher | callback | ) |
Sets a callback function to invoke on message receipt
This callback is alternative to the method receive
. Instead of buffering messages and calling that method each frame, this callback function will be invoked as soon as the message is received.
All callback functions are guaranteed to be called on the main thread. They are called at the start of an animation frame, before the method Application#update(float)
.
callback | The dispatcher callback |
void cugl::net::NetcodeConnection::onStateChange | ( | StateCallback | callback | ) |
Sets a callback function to invoke on state changes
Monitoring state is one of the most important components of working with a NetcodeConnection
. Many state changes (moving from State#CONNECTED
to State#INSESSION
, or State#INSESSION
to State#MIGRATING
) can happen due to circumstances beyond the control of this connection. It is particularly important to pay attention to the State#MIGRATING
state, as no messages can be sent during that time.
State can either be monitored via a callback with this method, or with a polling the method getState
.
callback | The state change callback |
void cugl::net::NetcodeConnection::open | ( | ) |
Opens the connection to the game lobby sever
This process is not instantaneous. Upon calling this method, you should wait for getState
or the callback onStateChange
to return State#CONNECTED
. Once it does, getRoom
will be your assigned room ID.
This method can only be called once. Future calls to this method are ignored. If you need to reopen a closed or failed connection, should should make a new NetcodeConnection
object. This method was only separated from the static allocator so that the user could have the opportunity to register callback functions.
void cugl::net::NetcodeConnection::receive | ( | const Dispatcher & | dispatcher | ) |
Receives incoming network messages.
When executed, the function dispatch
willl be called on every received byte array since the last call to receive
. It is up to you to interpret this data on your own or with NetcodeDeserializer
A network frame can, but need not be, the same as a render frame. Your dispatch function should be prepared to be called multiple times a render frame, or even not at all.
If a dispatcher callback has been registered with onReceipt
, this method will never do anything. In that case, messages are not buffered and are processed as soon as they are received.
dispatcher | The function to process received data |
bool cugl::net::NetcodeConnection::sendTo | ( | const std::string | dst, |
const std::vector< std::byte > & | data | ||
) |
Sends a byte array to the specified connection.
As the underlying connection of this netcode is peer-to-peer, this method can be used to send a communication to any other player in the game, regardless of host status (e.g. two non-host players can communicate this way). If the destination is this connection, the message will be immediately appended to the receipt buffer.
Communication from a source is guaranteed to be ordered. So if connection A sends two messages to connection B, connection B will receive those messages in the same order. However, there is no relationship between the messages coming from different sources.
You may choose to either send a byte array directly, or you can use the NetcodeSerializer
and NetcodeDeserializer
classes to encode more complex data.
This requires a connection be established. Otherwise it will return false. It will also return false if the host is currently migrating.
dst | The UUID of the peer to receive the message |
data | The byte array to send. |
bool cugl::net::NetcodeConnection::sendToHost | ( | const std::vector< std::byte > & | data | ) |
Sends a byte array to the host player.
This method is similar to sendTo
, except that it always sends to the host player. If this connection is the host, the message will be immediately appended to the receipt buffer.
Communication from a source is guaranteed to be ordered. So if connection A sends two messages to connection B, connection B will receive those messages in the same order. However, there is no relationship between the messages coming from different sources.
You may choose to either send a byte array directly, or you can use the NetcodeSerializer
and NetcodeDeserializer
classes to encode more complex data.
This requires a connection be established. Otherwise it will return false. It will also return false if the host is currently migrating.
data | The byte array to send. |
void cugl::net::NetcodeConnection::setCapacity | ( | size_t | capacity | ) |
Sets the message buffer capacity.
It is possible for this connection to recieve several messages over the network before it has a chance to all receive
. This buffer stores those messages to be read later. The capacity indicates the number of messages that can be stored.
Note that this is NOT the same as the capacity of a single message. That value was set as part of the initial NetcodeConfig
.
capacity | The new message buffer capacity. |
void cugl::net::NetcodeConnection::setDebug | ( | bool | flag | ) |
Toggles the debugging status of this connection.
If debugging is active, connections will be quite verbose
flag | Whether to activate debugging |
void cugl::net::NetcodeConnection::startSession | ( | ) |
Marks the game as started and bans incoming connections.
Note: This can only be called by the host. This method is ignored for clients.
|
friend |
Allow access to the other netcode classes