-
Notifications
You must be signed in to change notification settings - Fork 457
feat: disconnect reason #2280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: disconnect reason #2280
Changes from all commits
f0790d2
8ef9969
7b7b5cf
3c67ce9
4c4335f
37055ec
25b812f
55f0ec5
98eaeb0
99358bf
0168a14
1bea4ae
8dc1c2b
d683e98
80b278d
a9767dd
35f9279
7ea9d6b
890c8f6
3471527
3ea4a4f
9efaee1
a6d8796
6047dda
1203c8a
496dfce
e4494a8
f113e06
6739f30
8127eeb
7b06f84
35d9315
f622388
3030a05
dc10881
b14cc83
0a52c7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,6 +86,12 @@ public NetworkPrefabHandler PrefabHandler | |
private bool m_ShuttingDown; | ||
private bool m_StopProcessingMessages; | ||
|
||
// <summary> | ||
// When disconnected from the server, the server may send a reason. If a reason was sent, this property will | ||
// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called | ||
// </summary> | ||
public string DisconnectReason { get; internal set; } | ||
jeffreyrainy marked this conversation as resolved.
Show resolved
Hide resolved
jeffreyrainy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private class NetworkManagerHooks : INetworkHooks | ||
{ | ||
private NetworkManager m_NetworkManager; | ||
|
@@ -443,6 +449,11 @@ public class ConnectionApprovalResponse | |
/// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then. | ||
/// </summary> | ||
public bool Pending; | ||
|
||
// <summary> | ||
// Optional reason. If Approved is false, this reason will be sent to the client so they know why they | ||
// were not approved. | ||
public string Reason; | ||
} | ||
|
||
/// <summary> | ||
|
@@ -889,6 +900,7 @@ private void Initialize(bool server) | |
return; | ||
} | ||
|
||
DisconnectReason = string.Empty; | ||
IsApproved = false; | ||
|
||
ComponentFactory.SetDefaults(); | ||
|
@@ -2004,12 +2016,31 @@ internal void HandleIncomingData(ulong clientId, ArraySegment<byte> payload, flo | |
/// </summary> | ||
/// <param name="clientId">The ClientId to disconnect</param> | ||
public void DisconnectClient(ulong clientId) | ||
{ | ||
DisconnectClient(clientId, null); | ||
} | ||
|
||
/// <summary> | ||
/// Disconnects the remote client. | ||
/// </summary> | ||
/// <param name="clientId">The ClientId to disconnect</param> | ||
/// <param name="reason">Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the | ||
/// reason available in the NetworkManager.DisconnectReason property</param> | ||
public void DisconnectClient(ulong clientId, string reason) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a flow in boss room where when the host shuts down gracefully, we'll send a message to all clients telling them "hey! this is a nice shutdown, please give a popup saying the host just left". We need this flow, since else clients will think there was some wifi issues and will do their reconnect coroutine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the solution could still be a custom message (or RPC thinking of it) and still have the "delay one frame to send the RPC so we flush before closing the connection). We would still have this weird "wait one frame" though :( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add an API to shutdown with a disconnect reason. Internally, it would disconnect all clients with this reason, and then shutdown. This would be exactly equivalent to you disconnecting all clients before shutting down. Please let me know if you are requesting this API. That said, from there, two things can happen. Either the UTP flushing of its queue survives the shutdown. Or maybe it doesn't. Maybe shutdown overrides the queuing and you lose recent sends. Maybe it guarantees flushing before shutting down. Someone would need to check. In any case, if it doesn't do what is needed you'd have to file a bug with Transport. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty sure I don't have the power to request anything of you :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So just tested this real quick in boss room when pointing to your branch and it seems to work (and yep, UTP already allowed to flush, that was fixed a few months ago):
And then reading that value in OnClientDisconnectCallback. Testing it in game, when I shutdown the host I'll get the usual "host has left" popup on clients. It's a bit cumbersome to write that for loop but not the end of the world. Not the mountain I'll die on :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, if UTP flushes even on shutdown, you may convince me to add a Shutdown API that also takes a reason. Let me think about it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's particular to your DisconnectClient which does its own sort of flushing. I'd need to check, but if I were to send an RPC right before shutdown, I think it wouldn't work and I'd need to wait a frame before doing the Shutdown. If I remember the conversation from a few months ago correctly, UTP did its flush, but NGO was clearing things before the UTP flush. Your disconnect flushes things before the shutdown clearing (I think, to confirm) |
||
{ | ||
if (!IsServer) | ||
{ | ||
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); | ||
} | ||
|
||
if (!string.IsNullOrEmpty(reason)) | ||
{ | ||
var disconnectReason = new DisconnectReasonMessage(); | ||
disconnectReason.Reason = reason; | ||
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId); | ||
SamuelBellomo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
MessagingSystem.ProcessSendQueues(); | ||
|
||
jeffreyrainy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
OnClientDisconnectFromServer(clientId); | ||
DisconnectRemoteClient(clientId); | ||
} | ||
|
@@ -2243,6 +2274,15 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe | |
} | ||
else | ||
{ | ||
if (!string.IsNullOrEmpty(response.Reason)) | ||
{ | ||
var disconnectReason = new DisconnectReasonMessage(); | ||
disconnectReason.Reason = response.Reason; | ||
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId); | ||
|
||
MessagingSystem.ProcessSendQueues(); | ||
} | ||
|
||
PendingClients.Remove(ownerClientId); | ||
DisconnectRemoteClient(ownerClientId); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
namespace Unity.Netcode | ||
{ | ||
internal struct DisconnectReasonMessage : INetworkMessage | ||
{ | ||
public string Reason; | ||
|
||
public void Serialize(FastBufferWriter writer) | ||
{ | ||
string reasonSent = Reason; | ||
if (reasonSent == null) | ||
{ | ||
reasonSent = string.Empty; | ||
} | ||
|
||
if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent))) | ||
{ | ||
writer.WriteValueSafe(reasonSent); | ||
} | ||
else | ||
{ | ||
writer.WriteValueSafe(string.Empty); | ||
NetworkLog.LogWarning( | ||
"Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string."); | ||
} | ||
} | ||
|
||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context) | ||
{ | ||
reader.ReadValueSafe(out Reason); | ||
return true; | ||
} | ||
|
||
public void Handle(ref NetworkContext context) | ||
{ | ||
((NetworkManager)context.SystemOwner).DisconnectReason = Reason; | ||
} | ||
}; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
using NUnit.Framework; | ||
using Unity.Collections; | ||
|
||
namespace Unity.Netcode.EditorTests | ||
{ | ||
public class DisconnectMessageTests | ||
{ | ||
[Test] | ||
public void EmptyDisconnectReason() | ||
{ | ||
var networkContext = new NetworkContext(); | ||
var writer = new FastBufferWriter(20, Allocator.Temp, 20); | ||
var msg = new DisconnectReasonMessage(); | ||
msg.Reason = string.Empty; | ||
msg.Serialize(writer); | ||
|
||
var fbr = new FastBufferReader(writer, Allocator.Temp); | ||
var recvMsg = new DisconnectReasonMessage(); | ||
recvMsg.Deserialize(fbr, ref networkContext); | ||
|
||
Assert.IsEmpty(recvMsg.Reason); | ||
} | ||
|
||
[Test] | ||
public void DisconnectReason() | ||
{ | ||
var networkContext = new NetworkContext(); | ||
var writer = new FastBufferWriter(20, Allocator.Temp, 20); | ||
var msg = new DisconnectReasonMessage(); | ||
msg.Reason = "Foo"; | ||
msg.Serialize(writer); | ||
|
||
var fbr = new FastBufferReader(writer, Allocator.Temp); | ||
var recvMsg = new DisconnectReasonMessage(); | ||
recvMsg.Deserialize(fbr, ref networkContext); | ||
|
||
Assert.AreEqual("Foo", recvMsg.Reason); | ||
} | ||
|
||
[Test] | ||
public void DisconnectReasonTooLong() | ||
{ | ||
var networkContext = new NetworkContext(); | ||
var writer = new FastBufferWriter(20, Allocator.Temp, 20); | ||
var msg = new DisconnectReasonMessage(); | ||
msg.Reason = "ThisStringIsWayLongerThanTwentyBytes"; | ||
msg.Serialize(writer); | ||
|
||
var fbr = new FastBufferReader(writer, Allocator.Temp); | ||
var recvMsg = new DisconnectReasonMessage(); | ||
recvMsg.Deserialize(fbr, ref networkContext); | ||
|
||
Assert.IsEmpty(recvMsg.Reason); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.