Skip to content

fix: synchronizing connected clients additively loaded scenes only when server #3133

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading