Skip to content

Commit

Permalink
fix: synchronizing connected clients additively loaded scenes only wh…
Browse files Browse the repository at this point in the history
…en server (#3133)

* fix

This fixes the issue when starting NetworkManager as server only, connecting one or more clients, and then loading a scene and the in-scene placed NetworkObjects do not get synchronized with the clients (but once the scene is loaded late joining clients do get synchronized).

This had to do with an  adjustment to determining which in-scene placed NetworkObjects should be synchronized based on their observers coupled with both when running as just a server the server would not add itself to the observer list (in-scene placed only) and the update to NetworkSceneManager.SendSceneEventData where it never was updated with the more recent changes to SceneEventData (i.e. only serializing the in-scene placed NetworkObjects a client is observing) and still just queueing the message to be sent with a full list of target ids as opposed to sending each individual message while setting the SceneEventData.TargetClientId.

* test

Updating some of the scene management related integration tests to validate the issue associated with this PR as well as adding a server (non-host) set to some of the integration tests that were only validating with a host.

* style

adding comment and removing extra CR/LF.

* update

adding changelog entries

* test - fix

Fixing issue with the updated InScenePlacedNetworkObjectTests that were using the server-side player for some of the tests which doesn't exist when it is just a server.
Fixing issue with the expected number of spawned objects since the base value was derived from TotalClients that changes when running a server.
  • Loading branch information
NoelStephensUnity authored Nov 22, 2024
1 parent 487e469 commit 1845a33
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 41 deletions.
2 changes: 2 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133)
- Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133)
- Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122)
- Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113)
- Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1081,14 +1081,19 @@ private void SendSceneEventData(uint sceneEventId, ulong[] targetClientIds)
}
else
{
var message = new SceneEventMessage
// Send to each individual client to assure only the in-scene placed NetworkObjects being observed by the client
// is serialized
foreach (var clientId in targetClientIds)
{
EventData = sceneEvent,
};
var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, targetClientIds);
NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size);
sceneEvent.TargetClientId = clientId;
var message = new SceneEventMessage
{
EventData = sceneEvent,
};
var size = NetworkManager.ConnectionManager.SendMessage(ref message, k_DeliveryType, clientId);
NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEvent.SceneEventType, SceneNameFromHash(sceneEvent.SceneHash), size);
}
}

}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,12 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong
// then add all connected clients as observers
if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkObject.SpawnWithObservers)
{
// If running as a server only, then make sure to always add the server's client identifier
if (!NetworkManager.IsHost)
{
networkObject.Observers.Add(NetworkManager.LocalClientId);
}

// Add client observers
for (int i = 0; i < NetworkManager.ConnectedClientsIds.Count; i++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ protected Vector3 GetRandomVector3(float min, float max)
return new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max));
}

public IntegrationTestWithApproximation(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { }

public IntegrationTestWithApproximation(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { }

public IntegrationTestWithApproximation(HostOrServer hostOrServer) : base(hostOrServer) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,13 @@ public NetcodeIntegrationTest(NetworkTopologyTypes networkTopologyType)
m_DistributedAuthority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority;
}

public NetcodeIntegrationTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer)
{
m_NetworkTopologyType = networkTopologyType;
m_DistributedAuthority = m_NetworkTopologyType == NetworkTopologyTypes.DistributedAuthority;
m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost;
}

/// <summary>
/// Optional Host or Server integration tests
/// Constructor that allows you To break tests up as a host
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

namespace TestProject.RuntimeTests
{
[TestFixture(NetworkTopologyTypes.DistributedAuthority)]
[TestFixture(NetworkTopologyTypes.ClientServer)]
[TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
[TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
[TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Server)]
public class ClientSynchronizationValidationTest : NetcodeIntegrationTest
{
protected override int NumberOfClients => 0;
Expand All @@ -22,7 +23,7 @@ public class ClientSynchronizationValidationTest : NetcodeIntegrationTest
private bool m_RuntimeSceneWasExcludedFromSynch;

private List<ClientSceneVerificationHandler> m_ClientSceneVerifiers = new List<ClientSceneVerificationHandler>();
public ClientSynchronizationValidationTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { }
public ClientSynchronizationValidationTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { }

protected override void OnNewClientStarted(NetworkManager networkManager)
{
Expand Down Expand Up @@ -78,11 +79,17 @@ public IEnumerator ClientSynchWithServerSideRuntimeGeneratedScene()
/// Validates that connecting clients will exclude scenes using <see cref="NetworkSceneManager.VerifySceneBeforeLoading"/>
/// </summary>
[UnityTest]
public IEnumerator ClientVerifySceneBeforeLoading()
public IEnumerator ClientVerifySceneBeforeLoading([Values] bool startClientBefore)
{
m_IncludeSceneVerificationHandler = true;
var scenesToLoad = new List<string>() { k_FirstSceneToLoad, k_SecondSceneToLoad, k_ThirdSceneToSkip };
m_ServerNetworkManager.SceneManager.OnLoadComplete += OnLoadComplete;

if (startClientBefore)
{
yield return CreateAndStartNewClient();
}

foreach (var sceneToLoad in scenesToLoad)
{
m_SceneBeingLoadedIsLoaded = false;
Expand All @@ -91,9 +98,25 @@ public IEnumerator ClientVerifySceneBeforeLoading()

yield return WaitForConditionOrTimeOut(() => m_SceneBeingLoadedIsLoaded);
AssertOnTimeout($"Timed out waiting for scene {m_SceneBeingLoaded} to finish loading!");

var serverId = m_ServerNetworkManager.LocalClientId;
var serverOrHost = m_ServerNetworkManager.IsHost ? "Host" : "Server";
foreach (var spawnedObjectEntry in m_ServerNetworkManager.SpawnManager.SpawnedObjects)
{
var networkObject = spawnedObjectEntry.Value;
if (!networkObject.IsSceneObject.Value)
{
continue;
}

Assert.True(networkObject.Observers.Contains(serverId), $"The {serverOrHost} is not an observer of in-scene placed {nameof(NetworkObject)} {networkObject.name}!");
}
}

yield return CreateAndStartNewClient();
if (!startClientBefore)
{
yield return CreateAndStartNewClient();
}

yield return WaitForConditionOrTimeOut(m_ClientSceneVerifiers[0].HasLoadedExpectedScenes);
AssertOnTimeout($"Timed out waiting for the client to have loaded the expected scenes");
Expand All @@ -104,6 +127,18 @@ public IEnumerator ClientVerifySceneBeforeLoading()
{
clientSceneVerifier.ValidateScenesLoaded();
}

// Finally, validate that all in-scene placed NetworkObjects were properly synchronized/spawned on the client side
foreach (var spawnedObjectEntry in m_ServerNetworkManager.SpawnManager.SpawnedObjects)
{
var networkObject = spawnedObjectEntry.Value;
if (!networkObject.IsSceneObject.Value)
{
continue;
}
Assert.True(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId), $"{nameof(NetworkObject)}-{networkObject.NetworkObjectId} " +
$"did not synchronize on Client-{m_ClientNetworkManagers[0].LocalClientId}!");
}
}

private string m_SceneBeingLoaded;
Expand Down
Loading

0 comments on commit 1845a33

Please sign in to comment.