Skip to content

fix: NetworkSceneManager clearing scene placed NetworkObjects list when ClientSynchronizationMode is additive #2383

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
Show all changes
32 commits
Select commit Hold shift + click to select a range
124c2b1
fix
NoelStephensUnity Jan 15, 2023
b5f8459
update
NoelStephensUnity Jan 16, 2023
673d753
fix
NoelStephensUnity Jan 16, 2023
5532515
test
NoelStephensUnity Jan 16, 2023
27c601a
Update com.unity.netcode.gameobjects/CHANGELOG.md
NoelStephensUnity Jan 19, 2023
22c5d6d
update
NoelStephensUnity Jan 19, 2023
5fc02fa
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Jan 19, 2023
f5e112c
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Feb 8, 2023
f20cf08
update
NoelStephensUnity Feb 10, 2023
5c1d1a2
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Feb 10, 2023
fbed872
update
NoelStephensUnity Feb 10, 2023
f709d76
update
NoelStephensUnity Feb 10, 2023
dcf19f5
style
NoelStephensUnity Feb 10, 2023
14287f8
revert
NoelStephensUnity Feb 10, 2023
ce39f8d
update
NoelStephensUnity Feb 10, 2023
cb391eb
update
NoelStephensUnity Feb 11, 2023
364b094
test
NoelStephensUnity Feb 11, 2023
189214d
update
NoelStephensUnity Feb 11, 2023
d793587
update
NoelStephensUnity Feb 11, 2023
79fbe81
update
NoelStephensUnity Feb 11, 2023
1ff5380
test and fix
NoelStephensUnity Feb 12, 2023
ca15b80
update and test
NoelStephensUnity Feb 12, 2023
9973752
style
NoelStephensUnity Feb 12, 2023
31d23e5
fix
NoelStephensUnity Feb 13, 2023
6f5612b
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Feb 16, 2023
db0a075
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Feb 24, 2023
8d372e6
changelog fix
NoelStephensUnity Feb 24, 2023
c39e0f7
update
NoelStephensUnity Feb 28, 2023
18a3f38
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Mar 2, 2023
dcd5053
Merge branch 'develop' into fix/clientsynchronizationmode-failure-act…
NoelStephensUnity Mar 2, 2023
3aee658
update and fix
NoelStephensUnity Mar 2, 2023
0ee065b
test
NoelStephensUnity Mar 2, 2023
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
18 changes: 17 additions & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [Unreleased]

### Added

- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)

### Changed

- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)

### Fixed

- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)

## [1.3.0]

### Changed

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,23 @@ private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
}

~NetworkPrefabs()
{
Shutdown();
}

/// <summary>
/// Deregister from add and remove events
/// Clear the list
/// </summary>
internal void Shutdown()
{
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd -= AddTriggeredByNetworkPrefabList;
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
}

NetworkPrefabsLists.Clear();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,8 @@ internal void ShutdownInternal()
m_StopProcessingMessages = false;

ClearClients();
// This cleans up the internal prefabs list
NetworkConfig?.Prefabs.Shutdown();
}

/// <inheritdoc />
Expand Down Expand Up @@ -1395,6 +1397,10 @@ private void OnNetworkPostLateUpdate()

if (!m_ShuttingDown || !m_StopProcessingMessages)
{
// This should be invoked just prior to the MessagingSystem
// processes its outbound queue.
SceneManager.CheckForAndSendNetworkObjectSceneChanged();

MessagingSystem.ProcessSendQueues();
NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
Expand Down Expand Up @@ -1974,6 +1980,9 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe
var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;

// Generate a SceneObject for the player object to spawn
// Note: This is only to create the local NetworkObject,
// many of the serialized properties of the player prefab
// will be set when instantiated.
var sceneObject = new NetworkObject.SceneObject
{
OwnerClientId = ownerClientId,
Expand Down
210 changes: 191 additions & 19 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,55 @@ internal void GenerateGlobalObjectIdHash()
/// </summary>
public bool DestroyWithScene { get; set; }

/// <summary>
/// When set to true and the active scene is changed, this will automatically migrate the <see cref="NetworkObject"/>
/// into the new active scene on both the server and client instances.
/// </summary>
/// <remarks>
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
///
/// If there are more than one scenes loaded and the currently active scene is unloaded, then typically
/// the <see cref="SceneManager"/> will automatically assign a new active scene. Similar to <see cref="DestroyWithScene"/>
/// being set to <see cref="false"/>, this prevents any <see cref="NetworkObject"/> from being destroyed
/// with the unloaded active scene by migrating it into the automatically assigned active scene.
/// Additionally, this is can be useful in some seamless scene streaming implementations.
/// Note:
/// Only having <see cref="ActiveSceneSynchronization"/> set to true will *not* synchronize clients when
/// changing a <see cref="NetworkObject"/>'s scene via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>.
/// To synchronize clients of a <see cref="NetworkObject"/>'s scene being changed via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>,
/// make sure <see cref="SceneMigrationSynchronization"/> is enabled (it is by default).
/// </remarks>
public bool ActiveSceneSynchronization;

/// <summary>
/// When enabled (the default), if a <see cref="NetworkObject"/> is migrated to a different scene (active or not)
/// via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/> on the server side all client
/// instances will be synchronized and the <see cref="NetworkObject"/> migrated into the newly assigned scene.
/// The updated scene migration will get synchronized with late joining clients as well.
/// </summary>
/// <remarks>
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
/// Note:
/// You can have both <see cref="ActiveSceneSynchronization"/> and <see cref="SceneMigrationSynchronization"/> enabled.
/// The primary difference between the two is that <see cref="SceneMigrationSynchronization"/> only synchronizes clients
/// when the server migrates a <see cref="NetworkObject"/> to a new scene. If the scene is unloaded and <see cref="DestroyWithScene"/>
/// is <see cref="true"/> and <see cref="ActiveSceneSynchronization"/> is <see cref="false"/> and the scene is not the currently
/// active scene, then the <see cref="NetworkObject"/> will be destroyed.
/// </remarks>
public bool SceneMigrationSynchronization = true;

/// <summary>
/// Notifies when the NetworkObject is migrated into a new scene
/// </summary>
/// <remarks>
/// - <see cref="ActiveSceneSynchronization"/> or <see cref="SceneMigrationSynchronization"/> (or both) need to be enabled
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
/// </remarks>
public Action OnMigratedToNewScene;

/// <summary>
/// Delegate type for checking visibility
/// </summary>
Expand Down Expand Up @@ -188,6 +237,11 @@ public bool IsNetworkVisibleTo(ulong clientId)
/// </summary>
internal int SceneOriginHandle = 0;

/// <summary>
/// The server-side scene origin handle
/// </summary>
internal int NetworkSceneHandle = 0;

private Scene m_SceneOrigin;
/// <summary>
/// The scene where the NetworkObject was first instantiated
Expand Down Expand Up @@ -1118,6 +1172,18 @@ public bool WorldPositionStays
set => ByteUtility.SetBit(ref m_BitField, 5, value);
}

/// <summary>
/// Even though the server sends notifications for NetworkObjects that get
/// destroyed when a scene is unloaded, we want to synchronize this so
/// the client side can use it as part of a filter for automatically migrating
/// to the current active scene when its scene is unloaded. (only for dynamically spawned)
/// </summary>
public bool DestroyWithScene
{
get => ByteUtility.GetBit(m_BitField, 6);
set => ByteUtility.SetBit(ref m_BitField, 6, value);
}

//If(Metadata.HasParent)
public ulong ParentObjectId;

Expand Down Expand Up @@ -1160,7 +1226,7 @@ public void Serialize(FastBufferWriter writer)

var writeSize = 0;
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
writeSize += FastBufferWriter.GetWriteSize<int>();

if (!writer.TryBeginWrite(writeSize))
{
Expand All @@ -1172,14 +1238,9 @@ public void Serialize(FastBufferWriter writer)
writer.WriteValue(Transform);
}

// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
// this to locate their local instance of the in-scene placed NetworkObject instance.
// Only written for in-scene placed NetworkObjects.
if (IsSceneObject)
{
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
}
// The NetworkSceneHandle is the server-side relative
// scene handle that the NetworkObject resides in.
writer.WriteValue(OwnerObject.GetSceneOriginHandle());

// Synchronize NetworkVariables and NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
Expand All @@ -1205,7 +1266,7 @@ public void Deserialize(FastBufferReader reader)

var readSize = 0;
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
readSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
readSize += FastBufferWriter.GetWriteSize<int>();

// Try to begin reading the remaining bytes
if (!reader.TryBeginRead(readSize))
Expand All @@ -1218,14 +1279,9 @@ public void Deserialize(FastBufferReader reader)
reader.ReadValue(out Transform);
}

// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
// this to locate their local instance of the in-scene placed NetworkObject instance.
// Only read for in-scene placed NetworkObjects
if (IsSceneObject)
{
reader.ReadValue(out NetworkSceneHandle);
}
// The NetworkSceneHandle is the server-side relative
// scene handle that the NetworkObject resides in.
reader.ReadValue(out NetworkSceneHandle);
}
}

Expand Down Expand Up @@ -1317,6 +1373,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId)
OwnerClientId = OwnerClientId,
IsPlayerObject = IsPlayerObject,
IsSceneObject = IsSceneObject ?? true,
DestroyWithScene = DestroyWithScene,
Hash = HostCheckForGlobalObjectIdHashOverride(),
OwnerObject = this,
TargetClientId = targetClientId
Expand Down Expand Up @@ -1435,11 +1492,126 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);

// Spawn the NetworkObject
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false);
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene);

return networkObject;
}

/// <summary>
/// Subscribes to changes in the currently active scene
/// </summary>
/// <remarks>
/// Only for dynamically spawned NetworkObjects
/// </remarks>
internal void SubscribeToActiveSceneForSynch()
{
if (ActiveSceneSynchronization)
{
if (IsSceneObject.HasValue && !IsSceneObject.Value)
{
// Just in case it is a recycled NetworkObject, unsubscribe first
SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged;
SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged;
}
}
}

/// <summary>
/// If AutoSynchActiveScene is enabled, then this is the callback that handles updating
/// a NetworkObject's scene information.
/// </summary>
private void CurrentlyActiveSceneChanged(Scene current, Scene next)
{
// Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject
// is not spawned, or an in-scene placed NetworkObject
if (NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false)
{
return;
}
// This check is here in the event a user wants to disable this for some reason but also wants
// the NetworkObject to synchronize to changes in the currently active scene at some later time.
if (ActiveSceneSynchronization)
{
// Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate
// and update their scene handles
if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null)
{
SceneManager.MoveGameObjectToScene(gameObject, next);
SceneChangedUpdate(next);
}
}
}

/// <summary>
/// Handles updating the NetworkObject's tracked scene handles
/// </summary>
internal void SceneChangedUpdate(Scene scene, bool notify = false)
{
// Avoiding edge case scenarios, if no NetworkSceneManager exit early
if (NetworkManager.SceneManager == null)
{
return;
}

SceneOriginHandle = scene.handle;
// Clients need to update the NetworkSceneHandle
if (!NetworkManager.IsServer && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle))
{
NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle];
}
else if (NetworkManager.IsServer)
{
// Since the server is the source of truth for the NetworkSceneHandle,
// the NetworkSceneHandle is the same as the SceneOriginHandle.
NetworkSceneHandle = SceneOriginHandle;
}
else // Otherwise, the client did not find the client to server scene handle
if (NetworkManager.LogLevel == LogLevel.Developer)
{
// There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject
// into, but that scenario seemed very edge case and under most instances a user should be notified that this
// server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
// the server-side too.
NetworkLog.LogWarningServer($"[Client-{NetworkManager.LocalClientId}][{gameObject.name}] Server - " +
$"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
$"has no associated server side (network) scene handle!");
}
OnMigratedToNewScene?.Invoke();

// Only the server side will notify clients of non-parented NetworkObject scene changes
if (NetworkManager.IsServer && notify && transform.parent == null)
{
NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this);
}
}

/// <summary>
/// Update
/// Detects if a NetworkObject's scene has changed for both server and client instances
/// </summary>
/// <remarks>
/// About In-Scene Placed NetworkObjects:
/// Since the same scene can be loaded more than once and in-scene placed NetworkObjects GlobalObjectIdHash
/// values are only unique to the scene asset itself (and not per scene instance loaded), we will not be able
/// to add this same functionality to in-scene placed NetworkObjects until we have a way to generate
/// per-NetworkObject-instance unique GlobalObjectIdHash values for in-scene placed NetworkObjects.
/// </remarks>
private void Update()
{
// Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned,
// the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed
// NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle
if (!SceneMigrationSynchronization || NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned
|| IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle)
{
return;
}

// Otherwise, this has to be a dynamically spawned NetworkObject that has been
// migrated to a new scene.
SceneChangedUpdate(gameObject.scene, true);
}

/// <summary>
/// Only applies to Host mode.
/// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists.
Expand Down
Loading