Skip to content

Commit 2318c21

Browse files
committed
fix: address code review of the CLI review fixes (MTT-16534)
- NetworkManager keeps the role it last started as (internal LastStartedRole), so reconnect refuses a stopped host or server however it was started, and the CLI's own role dictionary, and its leak, is gone. - The simulator forgets a blackout only once every live driver has been let through, so timeout false still lifts one left on after a script recompile in play mode. - After a recompile in play mode, the simulator subscribes to new drivers again when it's on. - The console warns again when the simulator settings change, instead of keeping the first values. - config set address/port explains a hybrid session when the transport has already been swapped to UnifiedNetcodeTransport. - A distributed authority manager isn't reported as hybrid, since that topology rejects hybrid prefabs. - Start's doc comment is back on Start.
1 parent ac42e0d commit 2318c21

7 files changed

Lines changed: 76 additions & 23 deletions

File tree

‎com.unity.netcode.gameobjects/Documentation~/unity-cli-commands.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The total bytes sent and received are exact. Per message type, the bytes receive
5757

5858
### netcode.gameobjects.simulator
5959

60-
The simulator applies packet delay, jitter and loss to UnityTransport through its simulator pipeline stage, which is available in the Editor and development builds. Settings last for the Editor session and also apply to sessions started later. Once the simulator, `timeout` and `lag_spike` are all off, the transport gets its own simulator settings back. While the simulator is on, the Console shows a warning in each play session it affects.
60+
The simulator applies packet delay, jitter and loss to UnityTransport through its simulator pipeline stage, which is available in the Editor and development builds. Settings last for the Editor session and also apply to sessions started later. Once the simulator, `timeout` and `lag_spike` are all off, the transport gets its own simulator settings back. While the simulator is on, the Console shows a warning in each play session it affects, and again when its settings change.
6161

6262
| **Command** | **Description** |
6363
| :--- | :--- |

‎com.unity.netcode.gameobjects/Editor/CliQueries/NgoCliQueries.Config.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ internal static ConfigSetResult SetConfig(string field, string value, string man
9292
{
9393
if (manager.NetworkConfig.NetworkTransport is not UnityTransport utp)
9494
{
95+
// A hybrid session that has run replaced the UnityTransport with UnifiedNetcodeTransport.
96+
if (IsHybridSession(manager))
97+
{
98+
throw new ArgumentException(HybridSessionNote(manager, $"{name}, a UnityTransport setting, can't be set on it"));
99+
}
95100
throw new ArgumentException($"{name} is a UnityTransport setting, and {manager.name} uses {manager.NetworkConfig.NetworkTransport?.GetType().Name ?? "no transport"}.");
96101
}
97102
RequireDirectConnection(utp, name);

‎com.unity.netcode.gameobjects/Editor/CliQueries/NgoCliQueries.Session.cs‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ internal enum SessionMode
2323
/// address and port first. Returns once the start call returns; use <see cref="WaitReadyAsync"/> to wait
2424
/// for the connection.
2525
/// </summary>
26-
// The role each NetworkManager last started as through the CLI: once it has stopped, it no longer says.
27-
private static readonly Dictionary<NetworkManager, SessionMode> s_StartedAs = new Dictionary<NetworkManager, SessionMode>();
28-
2926
internal static SessionResult Start(SessionMode mode, string managerFilter, string address, int port)
3027
{
3128
RequireDefined(mode, "mode");
@@ -81,7 +78,6 @@ internal static SessionResult Start(SessionMode mode, string managerFilter, stri
8178
throw new InvalidOperationException($"{manager.name} failed to start as {mode.ToString().ToLowerInvariant()}{errors.Describe()}");
8279
}
8380
}
84-
s_StartedAs[manager] = mode;
8581

8682
result.Status = DescribeManager(manager);
8783
if (mode == SessionMode.Client)
@@ -152,8 +148,9 @@ internal static async Task<WaitReadyResult> ReconnectAsync(string managerFilter,
152148
throw new InvalidOperationException($"{manager.name} is the {GetRole(manager)}. reconnect is for clients; restart a server with netcode.gameobjects.session stop, then start.");
153149
}
154150
// A stopped host or server no longer reports IsServer, and would otherwise start again as a client.
155-
if (!manager.IsListening && s_StartedAs.TryGetValue(manager, out var startedAs) && startedAs != SessionMode.Client)
151+
if (!manager.IsListening && manager.LastStartedRole.IsServer)
156152
{
153+
var startedAs = manager.LastStartedRole.IsClient ? SessionMode.Host : SessionMode.Server;
157154
throw new InvalidOperationException($"{manager.name} was stopped as the {startedAs.ToString().ToLowerInvariant()}. reconnect is for clients; start it again with netcode.gameobjects.session start --mode {startedAs}.");
158155
}
159156

@@ -172,7 +169,6 @@ internal static async Task<WaitReadyResult> ReconnectAsync(string managerFilter,
172169
throw new InvalidOperationException($"{manager.name} shut down but failed to start again{errors.Describe()}");
173170
}
174171
}
175-
s_StartedAs[manager] = SessionMode.Client;
176172

177173
var remaining = timeoutSeconds - (float)(EditorApplication.timeSinceStartup - started);
178174
var ready = await WaitReadyAsync(manager, Math.Max(remaining, 0.1f), 0);

‎com.unity.netcode.gameobjects/Editor/CliQueries/NgoCliQueries.Simulator.cs‎

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace Unity.Netcode.GameObjects.Editor.Cli
2121
/// No driver is touched, and no transport event subscribed to, until the CLI turns the simulator, a timeout or
2222
/// a lag spike on. Each driver's own simulator parameters (from the transport or a Network Simulator component)
2323
/// are saved before the first override and put back once all three are off. While the simulator is on, the
24-
/// console warns once per play session, when it first changes a driver.
24+
/// console warns when it first changes a driver in a play session, and again when its settings change.
2525
/// </remarks>
2626
[InitializeOnLoad]
2727
internal static class NgoCliSimulator
@@ -88,12 +88,17 @@ private struct Baseline
8888

8989
// Keyed by the id UnityTransport passes to TransportInitialized (UnityTransport.TransportId).
9090
private static readonly Dictionary<int, Baseline> s_Baselines = new Dictionary<int, Baseline>();
91-
private static bool s_WarnedThisPlaySession;
91+
private static string s_LastWarning;
9292

9393
static NgoCliSimulator()
9494
{
9595
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
9696
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
97+
// A script recompile in play mode resets UnityTransport's events without running SubscribeToDrivers again.
98+
if (Enabled)
99+
{
100+
SubscribeWhilePlaying();
101+
}
97102
}
98103

99104
// UnityTransport clears its driver events when play mode starts, so subscribe once the runtime is up, and
@@ -133,7 +138,7 @@ private static void OnPlayModeStateChanged(PlayModeStateChange change)
133138
{
134139
TimeoutActive = false;
135140
s_LagSpikeEnd = 0;
136-
s_WarnedThisPlaySession = false;
141+
s_LastWarning = null;
137142
// The drivers are disposed with play mode.
138143
BlackoutApplied = false;
139144
EditorApplication.update -= EndLagSpikeWhenDue;
@@ -170,14 +175,20 @@ internal static List<UnityTransport> LiveTransports()
170175
internal static List<string> ApplyToLiveTransports(List<UnityTransport> live = null, bool clearBlackout = false)
171176
{
172177
var applied = new List<string>();
178+
// A driver left alone without clearBlackout may still drop every packet, e.g. after a script recompile.
179+
var blackoutLifted = true;
173180
foreach (var utp in live ?? LiveTransports())
174181
{
175182
if (Apply(utp.TransportId, utp.GetNetworkDriver(), clearBlackout))
176183
{
177184
applied.Add(NgoCliQueries.GetHierarchyPath(utp.transform));
178185
}
186+
else if (!clearBlackout)
187+
{
188+
blackoutLifted = false;
189+
}
179190
}
180-
if (!Blackout)
191+
if (!Blackout && blackoutLifted)
181192
{
182193
// Every live driver is back to its own loss, or the simulator's.
183194
BlackoutApplied = false;
@@ -217,7 +228,7 @@ private static bool Apply(int transportId, NetworkDriver driver, bool clearBlack
217228
stage.PacketJitterMs = JitterMs;
218229
stage.PacketDropPercentage = DropPercent;
219230
stage.PacketDropInterval = 0;
220-
WarnOncePerPlaySession();
231+
WarnSimulatorOn();
221232
}
222233
driver.ModifySimulatorStageParameters(stage);
223234

@@ -239,16 +250,17 @@ private static bool Apply(int transportId, NetworkDriver driver, bool clearBlack
239250

240251
/// <summary>
241252
/// The simulator outlasts the play session that turned it on, so say so in the console, where someone who
242-
/// didn't run the command will look for the cause of the lag.
253+
/// didn't run the command will look for the cause of the lag. Warns again when the settings change.
243254
/// </summary>
244-
private static void WarnOncePerPlaySession()
255+
private static void WarnSimulatorOn()
245256
{
246-
if (s_WarnedThisPlaySession)
257+
var warning = $"[Netcode] The Unity CLI network simulator is on for this editor session: {DelayMs} ms delay, {JitterMs} ms jitter and {DropPercent}% packet loss on every UnityTransport. Turn it off with netcode.gameobjects.simulator set --enable false.";
258+
if (warning == s_LastWarning)
247259
{
248260
return;
249261
}
250-
s_WarnedThisPlaySession = true;
251-
Debug.LogWarning($"[Netcode] The Unity CLI network simulator is on for this editor session: {DelayMs} ms delay, {JitterMs} ms jitter and {DropPercent}% packet loss on every UnityTransport. Turn it off with netcode.gameobjects.simulator set --enable false.");
262+
s_LastWarning = warning;
263+
Debug.LogWarning(warning);
252264
}
253265

254266
/// <summary>

‎com.unity.netcode.gameobjects/Editor/CliQueries/NgoCliQueries.cs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,14 @@ internal static bool IsHybridSession(NetworkManager manager)
147147
{
148148
return false;
149149
}
150-
// A running session has registered its prefabs; before it starts, look at the configured ones.
151-
return manager.IsListening
152-
? manager.NetworkConfig.Prefabs != null && manager.NetworkConfig.Prefabs.HasGhostPrefabs
153-
: ConfiguredHybridPrefabs(manager.NetworkConfig).Count > 0;
150+
// A running session has registered its prefabs; before it starts, look at the configured ones. Distributed
151+
// authority rejects hybrid prefabs, so such a session never runs on a Netcode for Entities world.
152+
if (manager.IsListening)
153+
{
154+
return manager.NetworkConfig.Prefabs != null && manager.NetworkConfig.Prefabs.HasGhostPrefabs;
155+
}
156+
return manager.NetworkConfig.NetworkTopology != NetworkTopologyTypes.DistributedAuthority
157+
&& ConfiguredHybridPrefabs(manager.NetworkConfig).Count > 0;
154158
#else
155159
return false;
156160
#endif

‎com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,12 @@ public ulong LocalClientId
661661
/// </summary>
662662
public bool IsHost => ConnectionManager.LocalClient.IsHost;
663663

664+
/// <summary>
665+
/// The role this NetworkManager last started as. Unlike <see cref="IsServer"/> and <see cref="IsClient"/>, it
666+
/// stays set after a shutdown, so editor tooling can tell a stopped host or server from a stopped client.
667+
/// </summary>
668+
internal (bool IsServer, bool IsClient) LastStartedRole { get; private set; }
669+
664670
/// <summary>
665671
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
666672
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
@@ -1476,6 +1482,7 @@ public bool StartServer()
14761482
{
14771483
return false;
14781484
}
1485+
LastStartedRole = (true, false);
14791486
ConnectionManager.LocalClient.ClientId = ServerClientId;
14801487

14811488
try
@@ -1558,6 +1565,7 @@ public bool StartClient()
15581565
{
15591566
return false;
15601567
}
1568+
LastStartedRole = (false, true);
15611569

15621570
try
15631571
{
@@ -1632,6 +1640,7 @@ public bool StartHost()
16321640
{
16331641
return false;
16341642
}
1643+
LastStartedRole = (true, true);
16351644

16361645
try
16371646
{

‎com.unity.netcode.gameobjects/Tests/Editor/Cli/NgoCliQueriesTests.cs‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.Net;
55
using System.Net.Sockets;
6+
using System.Reflection;
67
using System.Text.RegularExpressions;
78
using System.Threading.Tasks;
89
using NUnit.Framework;
@@ -568,6 +569,15 @@ public void HybridPrefabs_TransportCommandsSayTheyDontApply()
568569
var simulatorNotes = NgoCliQueries.SetSimulator(true, null, 50, -1, -1).Notes;
569570
StringAssert.Contains("hybrid session", simulatorNotes[simulatorNotes.Count - 1]);
570571
StringAssert.Contains("hybrid session", Only(NgoCliQueries.GetSimulator().Notes));
572+
573+
manager.NetworkConfig.NetworkTopology = NetworkTopologyTypes.DistributedAuthority;
574+
Assert.IsFalse(NgoCliQueries.IsHybridSession(manager), "Distributed authority rejects hybrid prefabs.");
575+
manager.NetworkConfig.NetworkTopology = NetworkTopologyTypes.ClientServer;
576+
577+
// Once a hybrid session has run, the NetworkManager's transport is UnifiedNetcodeTransport.
578+
manager.NetworkConfig.NetworkTransport = manager.gameObject.AddComponent<Unity.Netcode.Unified.UnifiedNetcodeTransport>();
579+
var swapped = Assert.Throws<ArgumentException>(() => NgoCliQueries.SetConfig("address", "10.0.0.2", null, false));
580+
StringAssert.Contains("hybrid session", swapped.Message);
571581
}
572582
#endif
573583

@@ -701,6 +711,8 @@ public IEnumerator RunningHost_SupportsTheRuntimeQueries()
701711
CollectionAssert.IsNotEmpty(simulator.AppliedLiveTo, "The running transport gets the settings now.");
702712
driver.CurrentSettings.TryGet<SimulatorUtility.Parameters>(out var applied);
703713
Assert.AreEqual(50, applied.PacketDelayMs);
714+
LogAssert.Expect(LogType.Warning, new Regex("network simulator is on for this editor session: 80 ms delay"));
715+
NgoCliQueries.SetSimulator(true, null, 80, -1, -1);
704716

705717
var timeout = NgoCliQueries.SetTimeout(true);
706718
Assert.IsTrue(timeout.Current.TimeoutActive);
@@ -715,6 +727,15 @@ public IEnumerator RunningHost_SupportsTheRuntimeQueries()
715727
driver.CurrentSettings.TryGet<SimulatorUtility.Parameters>(out var restored);
716728
Assert.AreEqual(original.PacketDelayMs, restored.PacketDelayMs, "Turning the simulator off restores the transport's own settings.");
717729

730+
// A script recompile in play mode forgets the timeout and the saved settings, but the drivers keep dropping every packet.
731+
NgoCliQueries.SetTimeout(true);
732+
typeof(NgoCliSimulator).GetProperty("TimeoutActive", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, false);
733+
((IDictionary)typeof(NgoCliSimulator).GetField("s_Baselines", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null)).Clear();
734+
NgoCliQueries.SetSimulator(false, null, -1, -1, -1);
735+
NgoCliQueries.SetTimeout(false);
736+
driver.CurrentSettings.TryGet<NetworkSimulatorParameter>(out var lifted);
737+
Assert.AreEqual(0f, lifted.ReceivePacketLossPercent, "timeout false still lifts a blackout the CLI left on.");
738+
718739
// Losing every packet on purpose, e.g. from a Network Simulator component, isn't mistaken for a CLI timeout.
719740
driver.CurrentSettings.TryGet<NetworkSimulatorParameter>(out var userLoss);
720741
userLoss.ReceivePacketLossPercent = 100f;
@@ -747,8 +768,14 @@ public IEnumerator RunningHost_SupportsTheRuntimeQueries()
747768
Assert.AreEqual(0, NgoCliQueries.ListObjects(null, "CliSpawnable", -1, 10).Total);
748769

749770
NgoCliQueries.Stop(null, false);
750-
yield return null;
751-
yield return null;
771+
yield return WaitForCondition(() => !manager.IsListening && !manager.ShutdownInProgress);
772+
773+
// However it was started, a stopped server isn't started again as a client.
774+
Assert.IsTrue(manager.StartServer());
775+
manager.Shutdown();
776+
yield return WaitForCondition(() => !manager.IsListening && !manager.ShutdownInProgress);
777+
var stoppedServer = Assert.ThrowsAsync<InvalidOperationException>(() => NgoCliQueries.ReconnectAsync(null, 5));
778+
StringAssert.Contains("stopped as the server", stoppedServer.Message);
752779
}
753780

754781
/// <summary>

0 commit comments

Comments
 (0)