Currently I’m having a class called TransportConnectionManager which is in DontDestroyOnLoad (to make it available in both MainMenuScene and GamePlayScene) and it manages both the matchmaking via local LAN IP and sending and receiving messages.
Only the host can press “Main Menu” button. Then the host will send the “MainMenu” message to everyone. Please note that I also wait for BeginSend result and ReliableSequencedPipelineStage result to be 0 before the host itself call GoToMainMenuOnDevice();
//TransportConnectionManager.cs
private Queue<HostCommand> _hostCommands = null;
private NativeList<NetworkConnection> m_Connections;
private NetworkPipeline m_Pipeline = m_Driver.CreatePipeline(typeof(ReliableSequencedPipelineStage));
// Inside Host's Update function
bool goToMainMenuAfterSend = false;
bool waitUntilNextUpdate = false;
while (_hostCommands.Count > 0 && waitUntilNextUpdate == false)
{
HostCommand hostCmd = _hostCommands.Peek();
if ((CommandType)hostCmd.CmdType == CommandType.MainMenu)
goToMainMenuAfterSend = true;
// Note that the messages are batched, so the first 4 bytes of a message always contains the length of the message. After the entire message is received it is put together and handled.
byte[] cmdBytes = hostCmd.PackToBytes();
byte[] lengthAndCmdBytes = new byte[4 + cmdBytes.Length];
Array.Copy(BitConverter.GetBytes(cmdBytes.Length), 0, lengthAndCmdBytes, 0, 4);
Array.Copy(cmdBytes, 0, lengthAndCmdBytes, 4, cmdBytes.Length);
NativeArray<byte> bytes = new NativeArray<byte>(lengthAndCmdBytes, Allocator.Temp);
// Get a reference to the internal state or shared context of the reliability
var reliableStageId = NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage));
m_Driver.GetPipelineBuffers(m_Pipeline, reliableStageId, hostCmd.DataDevice.GetNetworkConnection(), out var tmpReceiveBuffer, out var tmpSendBuffer, out NativeArray<byte> serverReliableBuffer);
unsafe {
var serverReliableCtx = (ReliableUtility.SharedContext*) serverReliableBuffer.GetUnsafePtr();
int sendResult = m_Driver.BeginSend(m_Pipeline, hostCmd.DataDevice.GetNetworkConnection(), out DataStreamWriter writer);
if (sendResult == 0)
{
writer.WriteBytes(bytes);
m_Driver.EndSend(writer);
if (serverReliableCtx->errorCode != 0)
{
waitUntilNextUpdate = true;
goToMainMenuAfterSend = false;
Debug.LogWarning("Failed to send with reliability : " + serverReliableCtx->errorCode);
// Failed to send with reliability, error code will be ReliableUtility.ErrorCodes.OutgoingQueueIsFull if no buffer space is left to store the packet
} else {
_lastMsgTimeToClient[hostCmd.DataDevice.GetNetworkConnection()] = Time.time;
}
}
else
{
waitUntilNextUpdate = true;
goToMainMenuAfterSend = false;
}
}
if (waitUntilNextUpdate == false)
{
_hostCommands.Dequeue();
}
}
if (goToMainMenuAfterSend == true)
{
GoToMainMenuOnDevice();
}
When the client receive the “MainMenu” command, it will call GoToMainMenuOnDevice(); function (Both the host and the client call this same function)
public void GoToMainMenuOnDevice()
{
// Miscs cleanup
................................
// I WANT TO CLOSE THE CONNECTIONS HERE BUT IT WON'T WORK
SceneManager.LoadScene("MainMenuScene");
}
Since the script is in DontDestroyOnLoad, if I don’t close the connection, it will still exist in main menu. All the remaining messages (if any) will still be sent. However, I cannot close the connection at the above commented spot :
- If I call m_Connections.Dispose(); → it will throw error about connection already closed
- If I call Destroy(this.gameObject); → Only the host can go back to MainMenuScene and the clients stuck at GamePlayScene
- If I leave everything as it is → I cannot create a new connection. The hack I’m doing is to try-catch delete the connection just before creating a new one.
So, what is a proper way to send everyone back to MainMenuScene and close all the connections?