Description
Description
Calling StartClient()
or StartHost()
from within an OnServerStopped()
/OnClientStopped()
callback leads to inconsistent state.
Reproduce Steps
NetworkManager.Singleton.OnServerStopped += MyOnServerStoppedHandler
NetworkManager.Singleton.StartHost()
- Wait for a few seconds
NetworkManager.Singleton.Shutdown(true)
- Inside
MyOnServerStoppedHandler
, callNetworkManager.Singleton.StartClient()
Actual Outcome
A few tidbits of client state are cleared out at the tail end of NetworkManager::InternalShutdown()
that were just set up inside of StartClient()
/StartHost()
. For example, StartClient()
calls ConnectionManager.LocalClient.SetRole(false, true, this);
as part of that callback, but then a moment later InternalShutdown()
calls ConnectionManager.LocalClient.SetRole(false, false);
.
Expected Outcome
Any of these three options would be fine:
- (preferred) All shutdown work is complete and settled before
OnServerStopped()
/OnClientStopped()
are called, permittingStartClient()
/StartHost()
to be called from within those callbacks. - A new
OnNetworkManagerShutdownComplete
callback happens when shutdown is well and truly complete. StartClient()
/StartHost()
detect that shutdown isn't quite finished yet, and bail by returningfalse
(preferably with some sort of error log explaining why).
Environment
- OS: N/A
- Unity Version: N/A
- Netcode Version: 1.6.0
Additional Context
I bumped into this as a problem while trying to accommodate a flow of "I used to be hosting, but then I got an invite from a friend and want to switch to being a client that's connected to their host". To work around the problem, I had MyOnServerStopped()
call Invoke(nameof(OnServerFullyStopped), 0.01f)
, and then called StartClient()
from within OnServerFullyStopped()
.
This workaround caused an issue for me in a different way, though, because SteamNetworkingSocketsTransport
does a similar workaround during their shutdown. Critically, they wait for 0.1f seconds before finishing the transport's cleanup. This resulted in a race condition where my game would be halfway through connecting to its new host when the transport would wrap up shutting itself down.
For the time being, I can work around that problem by simply waiting longer for everything to settle before calling StartClient()
, but clearly that's suboptimal all around.
All of that said, if I'm simply missing something in the docs on how my code can know that NetworkManager
is really, truly safe to call StartClient()
/StartHost()
, or if I'm going about it wrong and should instead destroy/recreate my NetworkManager
for this particular flow, please let me know!