// We need to delay slightly to give the disconnect message sent during Dispose time to reach the host, so that we don't destroy the connection without it being flushed first.
// Special case: We want to be able to filter on our color data, so we need to supply an arbitrary index to retrieve later. Uses N# for numerics, instead of S# for strings.
/// This observes the local player and updates remote players over Relay when there are local changes, demonstrating basic data transfer over the Unity Transport (UTP).
/// Created after the connection to Relay has been confirmed.
/// If you are using the Unity Networking Package, you can use their Relay instead of building your own packets.
/// </summary>
publicclassRelayUtpClient:MonoBehaviour,IDisposable// This is a MonoBehaviour merely to have access to Update.
{
protectedLocalPlayerm_localUser;
protectedLocalLobbym_localLobby;
protectedNetworkDriverm_networkDriver;
protectedList<NetworkConnection>m_connections;// For clients, this has just one member, but for hosts it will have more.
/// This observes the local player and updates remote players over Relay when there are local changes, demonstrating basic data transfer over the Unity Transport (UTP).
/// Created after the connection to Relay has been confirmed.
/// If you are using the Unity Networking Package, you can use their Relay instead of building your own packets.
/// </summary>
publicclassRelayUtpClient:MonoBehaviour,IDisposable// This is a MonoBehaviour merely to have access to Update.
{
protectedLocalPlayerm_localUser;
protectedLocalLobbym_localLobby;
protectedNetworkDriverm_networkDriver;
protectedList<NetworkConnection>m_connections;// For clients, this has just one member, but for hosts it will have more.
// Don't clean up the NetworkDriver here, or else our disconnect message won't get through to the host. The host will handle cleaning up the connection.
// Don't clean up the NetworkDriver here, or else our disconnect message won't get through to the host. The host will handle cleaning up the connection.
}
publicvoidOnDestroy()
{
Dispose();
}
publicvoidDispose()
{
if(!m_hasDisposed)
{
Uninitialize();
m_hasDisposed=true;
}
}
privatevoidOnLocalChange(LocalPlayerlocalUser)
{
if(m_connections.Count==0)// This could be the case for the host alone in the lobby.
return;
foreach(NetworkConnectionconninm_connections)
DoUserUpdate(m_networkDriver,conn,m_localUser);
}
publicvoidOnDestroy()
{
Dispose();
}
privatevoidUpdate()
{
OnUpdate();
}
privatevoidOnLocalChange(LocalPlayerlocalUser)
{
if(m_connections.Count==0)// This could be the case for the host alone in the lobby.
return;
foreach(NetworkConnectionconninm_connections)
DoUserUpdate(m_networkDriver,conn,m_localUser);
}
/// <summary>
/// Clients need to send any data over UTP periodically, or else Relay will remove them from the allocation.
/// </summary>
privatevoidUpdateSlow(floatdt)
{
if(!m_IsRelayConnected)// If disconnected from Relay for some reason, we *want* this client to timeout.
WriteByte(m_networkDriver,connection,"0",MsgType.Ping,0);// The ID doesn't matter here, so send a minimal number of bytes.
}
privatevoidUpdate()
{
OnUpdate();
}
protectedvirtualvoidOnUpdate()
{
if(!m_hasSentInitialMessage)
ReceiveNetworkEvents(m_networkDriver);// Just on the first execution, make sure to handle any events that accumulated while completing the connection.
m_networkDriver.ScheduleUpdate().Complete();// This pumps all messages, which pings the Relay allocation and keeps it alive. It should be called no more often than ReceiveNetworkEvents.
ReceiveNetworkEvents(m_networkDriver);// This reads the message queue which was just updated.
if(!m_hasSentInitialMessage)
SendInitialMessage(m_networkDriver,m_connections[0]);// On a client, the 0th (and only) connection is to the host.
}
/// <summary>
/// Clients need to send any data over UTP periodically, or else Relay will remove them from the allocation.
/// </summary>
privatevoidUpdateSlow(floatdt)
{
if(!m_IsRelayConnected)// If disconnected from Relay for some reason, we *want* this client to timeout.
while((cmd=driver.PopEvent(outconn,outstrm))!=NetworkEvent.Type.Empty)// NetworkConnection also has PopEvent, but NetworkDriver.PopEvent automatically includes new connections.
{
ProcessNetworkEvent(conn,strm,cmd);
}
}
protectedvirtualvoidOnUpdate()
{
if(!m_hasSentInitialMessage)
ReceiveNetworkEvents(m_networkDriver);// Just on the first execution, make sure to handle any events that accumulated while completing the connection.
m_networkDriver.ScheduleUpdate().Complete();// This pumps all messages, which pings the Relay allocation and keeps it alive. It should be called no more often than ReceiveNetworkEvents.
ReceiveNetworkEvents(m_networkDriver);// This reads the message queue which was just updated.
if(!m_hasSentInitialMessage)
SendInitialMessage(m_networkDriver,m_connections[0]);// On a client, the 0th (and only) connection is to the host.
}
// See the Write* methods for the expected event format.
while((cmd=driver.PopEvent(outconn,outstrm))!=NetworkEvent.Type.Empty)// NetworkConnection also has PopEvent, but NetworkDriver.PopEvent automatically includes new connections.
{
ProcessNetworkEvent(conn,strm,cmd);
}
}
MsgTypemsgType=(MsgType)msgContents[0];
intidLength=msgContents[1];
if(msgContents.Count<idLength+2)
{UnityEngine.Debug.LogWarning($"Relay client processed message of length {idLength}, but contents were of length {msgContents.Count}.");
return;
}
// See the Write* methods for the expected event format.
// Don't react to our own messages. Also, don't need to hold onto messages if the ID is absent; clients should be initialized and in the lobby before they send events.
// (Note that this enforces lobby membership before processing any events besides an approval request, so a client is unable to fully use Relay unless they're in the lobby.)
// Don't react to our own messages. Also, don't need to hold onto messages if the ID is absent; clients should be initialized and in the lobby before they send events.
// (Note that this enforces lobby membership before processing any events besides an approval request, so a client is unable to fully use Relay unless they're in the lobby.)
unsafe// Similarly to ReadMessageContents, our data must be converted to a pointer before being sent.
{
fixed(byte*bytesPtr=bytes)
{
dataStream.WriteBytes(bytesPtr,message.Count);
driver.EndSend(dataStream);
}
}
}
}
/// <summary>
/// Disconnect from Relay, usually while leaving the lobby. (You can also call this elsewhere to see how Lobby will detect a Relay disconnect automatically.)
// If the client calls Disconnect, the host might not become aware right away (depending on when the PubSub messages get pumped), so send a message over UTP instead.
/// Disconnect from Relay, usually while leaving the lobby. (You can also call this elsewhere to see how Lobby will detect a Relay disconnect automatically.)
// If the client calls Disconnect, the host might not become aware right away (depending on when the PubSub messages get pumped), so send a message over UTP instead.
/// Determine the server endpoint for connecting to the Relay server, for either an Allocation or a JoinAllocation.
/// If DTLS encryption is available, and there's a secure server endpoint available, use that as a secure connection. Otherwise, just connect to the Relay IP unsecured.
/// Determine the server endpoint for connecting to the Relay server, for either an Allocation or a JoinAllocation.
/// If DTLS encryption is available, and there's a secure server endpoint available, use that as a secure connection. Otherwise, just connect to the Relay IP unsecured.
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// Shared behavior for binding to the Relay allocation, which is required for use.
/// Note that a host will send bytes from the Allocation it creates, whereas a client will send bytes from the JoinAllocation it receives using a relay code.
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.
/// </summary>
publicclassRelayUtpSetupHost:RelayUtpSetup
{
[Flags]
privateenumJoinState
{
None=0,
Bound=1,
Joined=2
}
/// <summary>
/// Host logic: Request a new Allocation, and then both bind to it and request a join code. Once those are both complete, supply data back to the lobby.