Skip to content

Commit ac625b2

Browse files
update
Fixes for connection sequence where clients don't announce they are ready to receive snapshots until fully synchronized by NGO. Fixes for removing pending ghosts and ghost spawn data (especially when being destroyed). Fixes for NetworkSceneManager detecting pending ghosts as in-scene placed. Fixes for NetworkSpawnManager.RegisterGhostPendingSpawn trying to double process triggers and/or process triggers when it should not.
1 parent f2f6580 commit ac625b2

File tree

5 files changed

+70
-39
lines changed

5 files changed

+70
-39
lines changed

com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ namespace Unity.Netcode
77
{
88

99
/// <summary>
10-
/// TODO-UNIFIED: Would need to be reviewed for alternate ways of handling this.
10+
/// TODO-UNIFIED: Needs further peer review and exploring alternate ways of handling this.
1111
/// </summary>
1212
/// <remarks>
1313
/// If used, we most likely would make this internal
1414
/// </remarks>
1515
public partial class NetworkObjectBridge : GhostBehaviour
1616
{
17-
public Action<ulong> NetworkObjectIdChanged;
18-
17+
/// <summary>
18+
/// This is used to link <see cref="NetworkObject.SerializedObject"/> data to
19+
/// N4E-spawned hybrid prefab instances.
20+
/// </summary>
1921
internal GhostField<ulong> NetworkObjectId = new GhostField<ulong>();
2022

2123
public void SetNetworkObjectId(ulong value)

com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ public struct NetcodeConnection
1313
internal Entity Entity;
1414
public int NetworkId;
1515

16-
internal float ConnectedTime;
17-
1816
public bool IsServer => World.IsServer();
1917
public void GoInGame()
2018
{
@@ -38,6 +36,8 @@ protected override void OnUpdate()
3836
{
3937
var isServer = World.IsServer();
4038
var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
39+
var networkManager = NetworkManager.Singleton;
40+
4141
foreach (var (networkId, connectionState, entity) in SystemAPI.Query<NetworkId, ConnectionState>().WithNone<NetworkStreamConnection>().WithEntityAccess())
4242
{
4343
commandBuffer.RemoveComponent<ConnectionState>(entity);
@@ -50,16 +50,12 @@ protected override void OnUpdate()
5050

5151
m_TempConnections.Clear();
5252

53-
53+
// TODO: We should figure out how to associate the N4E NetworkId with the NGO ClientId
5454
foreach (var (networkId, entity) in SystemAPI.Query<NetworkId>().WithAll<NetworkStreamConnection>().WithNone<NetworkStreamInGame>().WithEntityAccess())
5555
{
56-
// TODO-Unified: For new connections, we have a delay before the N4E in-game state for the client to provide time for the NGO side of the client to synchronize.
57-
// Note: Once both are using the same transport we should be able to get the transport id and determine the NGO assigned client-id and at that point once the
58-
// client has signaled that it has synchronized (or has been sent the synchronization data) we finalize the in-game connection state (or something along those lines).
5956
if (!m_NewConnections.ContainsKey(networkId.Value))
6057
{
61-
var delayTime = 0.0f;// isServer ? 0.2f : 0.1f;
62-
var newConnection = new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value, ConnectedTime = UnityEngine.Time.realtimeSinceStartup + delayTime};
58+
var newConnection = new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value };
6359
m_NewConnections.Add(networkId.Value, newConnection);
6460
}
6561
}
@@ -69,8 +65,9 @@ protected override void OnUpdate()
6965
{
7066
foreach (var entry in m_NewConnections)
7167
{
72-
// Check if the delay time has passed.
73-
if (entry.Value.ConnectedTime < UnityEngine.Time.realtimeSinceStartup)
68+
// Server: always connect
69+
// Client: wait until we have synchronized before announcing we are ready to receive snapshots
70+
if (networkManager.IsServer || (!networkManager.IsServer && networkManager.IsConnectedClient))
7471
{
7572
// Set the connection in-game
7673
commandBuffer.AddComponent<NetworkStreamInGame>(entry.Value.Entity);
@@ -87,21 +84,29 @@ protected override void OnUpdate()
8784
}
8885
m_TempConnections.Clear();
8986

87+
// If the local NetworkManager is shutting down or no longer connected, then
88+
// make sure we have disconnected all known connections.
89+
if (networkManager.ShutdownInProgress || !networkManager.IsListening)
90+
{
91+
foreach (var (networkId, entity) in SystemAPI.Query<NetworkId>().WithEntityAccess())
92+
{
93+
commandBuffer.RemoveComponent<ConnectionState>(entity);
94+
NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value });
95+
}
96+
}
9097
commandBuffer.Playback(EntityManager);
9198
}
9299

100+
/// <summary>
101+
/// Always disconnect all known connections when being destroyed.
102+
/// </summary>
93103
protected override void OnDestroy()
94104
{
95105
var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
96106
foreach (var (networkId, entity) in SystemAPI.Query<NetworkId>().WithEntityAccess())
97107
{
98108
commandBuffer.RemoveComponent<ConnectionState>(entity);
99-
// TODO: maybe disconnect reason?
100-
m_TempConnections.Add(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value });
101-
}
102-
foreach (var con in m_TempConnections)
103-
{
104-
NetworkManager.OnNetCodeDisconnect?.Invoke(con);
109+
NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value });
105110
}
106111
commandBuffer.Playback(EntityManager);
107112
base.OnDestroy();

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,8 +1732,15 @@ private void OnDestroy()
17321732
return;
17331733
}
17341734

1735+
var spawnManager = NetworkManager.SpawnManager;
1736+
17351737
// Always attempt to remove from scene changed updates
1736-
networkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this);
1738+
spawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this);
1739+
1740+
#if UNIFIED_NETCODE
1741+
spawnManager?.GhostsPendingSpawn.Remove(NetworkObjectId);
1742+
spawnManager?.GhostsPendingSynchronization.Remove(NetworkObjectId);
1743+
#endif
17371744

17381745
if (IsSpawned && !networkManager.ShutdownInProgress)
17391746
{
@@ -1763,11 +1770,11 @@ private void OnDestroy()
17631770
}
17641771
}
17651772

1766-
if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
1773+
if (spawnManager != null && spawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
17671774
{
17681775
if (this == networkObject)
17691776
{
1770-
networkManager.SpawnManager.OnDespawnObject(networkObject, false);
1777+
spawnManager.OnDespawnObject(networkObject, false);
17711778
}
17721779
}
17731780
}
@@ -3846,7 +3853,6 @@ private void InitGhost()
38463853
{
38473854
Debug.Log($"[{nameof(NetworkObject)}] GhostBridge {name} detected and instantiated.");
38483855
}
3849-
NetworkObjectBridge.NetworkObjectIdChanged += OnNetworkObjectIdChanged;
38503856
if (NetworkObjectBridge.NetworkObjectId.Value != 0)
38513857
{
38523858
RegisterGhostBridge();
@@ -3866,11 +3872,6 @@ internal void RegisterGhostBridge()
38663872
NetworkManager.SpawnManager.RegisterGhostPendingSpawn(this, NetworkObjectBridge.NetworkObjectId.Value);
38673873
}
38683874
}
3869-
3870-
private void OnNetworkObjectIdChanged(ulong networkObjectId)
3871-
{
3872-
RegisterGhostBridge();
3873-
}
38743875
#endif
38753876

38763877
/// <summary>

com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2740,8 +2740,14 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP
27402740
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
27412741
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
27422742
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
2743+
#if UNIFIED_NETCODE
2744+
if (!networkObjectInstance.HasGhost && networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager ||
2745+
networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle)
2746+
#else
27432747
if (networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager ||
27442748
networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle)
2749+
2750+
#endif
27452751
{
27462752
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
27472753
{

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,35 @@ internal void RegisterGhostPendingSpawn(NetworkObject networkObject, ulong netwo
4343
if (GhostsPendingSpawn.TryAdd(networkObjectId, networkObject))
4444
{
4545
// TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn.
46-
// For now, move any pending object into the DDOL.
47-
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
46+
// Edge-Case scenario: During initial client synchronization (i.e. !NetworkManager.IsConnectedClient).
47+
//
48+
// Description: A client can receive snapshots before finishing the NGO synchronization process.
49+
// This is when an edge case scenario can happen where the initial NGO synchronization information
50+
// can include new scenes to load. If one of those scenes is configured to load in SingleMode, then
51+
// any instantiated ghosts pending synchronization would be instantiated in whatever the currently
52+
// active scene was when the client was processing the synchronization data. If the ghosts pending
53+
// synchrpnization are in the currently active scene when the new scene is loaded in SingleMode, then
54+
// they would be destroyed.
55+
//
56+
// Current Fix:
57+
// If the client is not yet synchronized, then any ghost pending spawn get migrated into the DDOL.
58+
//
59+
// Further review:
60+
// We need to make sure that we are migrating NetworkObjects into their assigned scene (if scene
61+
// management is enabled). Currently, we assume all instances were in the DDOL and just migrate
62+
// them into the currently active scene upon spawn.
63+
if (!NetworkManager.IsConnectedClient && !GhostsPendingSynchronization.ContainsKey(networkObjectId))
64+
{
65+
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
66+
}
67+
else // There is matching spawn data for this pending Ghost, process the pending spawn for this hybrid instance.
68+
{
69+
NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, networkObjectId);
70+
}
4871
}
49-
50-
NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, networkObjectId);
51-
if (GhostsArePendingSynchronization && GhostsPendingSynchronization.ContainsKey(networkObjectId))
72+
else
5273
{
53-
// TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn.
54-
// NOTE: We might be able to use the NetworkSceneHandle to get the associated local scene handle to which we can use to get the targeted scene.
55-
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, UnityEngine.SceneManagement.SceneManager.GetActiveScene());
56-
57-
// When the object is spawned, it will invoke GetGhostNetworkObjectForSpawn below which removes the entry from GhostsPendingSpawn
58-
ProcessGhostPendingSynchronization(networkObjectId);
74+
Debug.LogError($"[{networkObject.name}-{networkObjectId}] Has already been registered as a pending ghost!");
5975
}
6076
}
6177

@@ -109,6 +125,7 @@ internal void ProcessGhostPendingSynchronization(ulong networkObjectId, bool rem
109125
//}
110126
if (removeUponSpawn)
111127
{
128+
GhostsPendingSynchronization.Remove(networkObjectId);
112129
GhostsArePendingSynchronization = GhostsPendingSynchronization.Count > 0;
113130
ghostPendingSynch.Buffer.Dispose();
114131
}

0 commit comments

Comments
 (0)