com.unity.netcode.gameobjects@1.0.0-pre.7

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).

## [1.0.0-pre.7] - 2022-04-01

### Added

- Added editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828)
- Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823)
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
- `UnityTransport` settings can now be set programmatically. (#1845)
- `FastBufferWriter` and Reader IsInitialized property. (#1859)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)

### Removed

- Removed `SnapshotSystem` (#1852)
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)
- Removed `com.unity.collections` dependency from the package (#1849)

### Fixed
- Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850)
- Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847)
- Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
- Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838)
- Fixed ClientRpcs would always send to all connected clients by default as opposed to only sending to the NetworkObject's Observers list by default. (#1836)
- Fixed clarity for NetworkSceneManager client side notification when it receives a scene hash value that does not exist in its local hash table. (#1828)
- Fixed client throws a key not found exception when it times out using UNet or UTP. (#1821)
- Fixed network variable updates are no longer limited to 32,768 bytes when NetworkConfig.EnsureNetworkVariableLengthSafety is enabled. The limits are now determined by what the transport can send in a message. (#1811)
- Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809)
- Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808)
- Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801)
- Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780)
- Fixed issue when spawning new player if an already existing player exists it does not remove IsPlayer from the previous player (#1779)
- Fixed lack of notification that NetworkManager and NetworkObject cannot be added to the same GameObject with in-editor notifications (#1777)
- Fixed parenting warning printing for false positives (#1855)
This commit is contained in:
Unity Technologies
2022-04-01 00:00:00 +00:00
parent 5b4aaa8b59
commit 60e2dabef4
123 changed files with 5751 additions and 3419 deletions

View File

@@ -21,6 +21,122 @@ namespace Unity.Netcode
/// </summary>
public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>();
/// <summary>
/// Use to get all NetworkObjects owned by a client
/// Ownership to Objects Table Format:
/// [ClientId][NetworkObjectId][NetworkObject]
/// Server: Keeps track of all clients' ownership
/// Client: Keeps track of only its ownership
/// </summary>
public readonly Dictionary<ulong, Dictionary<ulong, NetworkObject>> OwnershipToObjectsTable = new Dictionary<ulong, Dictionary<ulong, NetworkObject>>();
/// <summary>
/// Object to Ownership Table:
/// [NetworkObjectId][ClientId]
/// Used internally to find the client Id that currently owns
/// the NetworkObject
/// </summary>
private Dictionary<ulong, ulong> m_ObjectToOwnershipTable = new Dictionary<ulong, ulong>();
/// <summary>
/// Used to update a NetworkObject's ownership
/// </summary>
internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, bool isRemoving = false)
{
var previousOwner = newOwner;
// Use internal lookup table to see if the NetworkObject has a previous owner
if (m_ObjectToOwnershipTable.ContainsKey(networkObject.NetworkObjectId))
{
// Keep track of the previous owner's ClientId
previousOwner = m_ObjectToOwnershipTable[networkObject.NetworkObjectId];
// We are either despawning (remove) or changing ownership (assign)
if (isRemoving)
{
m_ObjectToOwnershipTable.Remove(networkObject.NetworkObjectId);
}
else
{
m_ObjectToOwnershipTable[networkObject.NetworkObjectId] = newOwner;
}
}
else
{
// Otherwise, just add a new lookup entry
m_ObjectToOwnershipTable.Add(networkObject.NetworkObjectId, newOwner);
}
// Check to see if we had a previous owner
if (previousOwner != newOwner && OwnershipToObjectsTable.ContainsKey(previousOwner))
{
// Before updating the previous owner, assure this entry exists
if (OwnershipToObjectsTable[previousOwner].ContainsKey(networkObject.NetworkObjectId))
{
// Remove the previous owner's entry
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
// Server or Host alway invokes the lost ownership notification locally
if (NetworkManager.IsServer)
{
networkObject.InvokeBehaviourOnLostOwnership();
}
// If we are removing the entry (i.e. despawning or client lost ownership)
if (isRemoving)
{
return;
}
}
else
{
// Really, as long as UpdateOwnershipTable is invoked when ownership is gained or lost this should never happen
throw new Exception($"Client-ID {previousOwner} had a partial {nameof(m_ObjectToOwnershipTable)} entry! Potentially corrupted {nameof(OwnershipToObjectsTable)}?");
}
}
// If the owner doesn't have an entry then create one
if (!OwnershipToObjectsTable.ContainsKey(newOwner))
{
OwnershipToObjectsTable.Add(newOwner, new Dictionary<ulong, NetworkObject>());
}
// Sanity check to make sure we don't already have this entry (we shouldn't)
if (!OwnershipToObjectsTable[newOwner].ContainsKey(networkObject.NetworkObjectId))
{
// Add the new ownership entry
OwnershipToObjectsTable[newOwner].Add(networkObject.NetworkObjectId, networkObject);
// Server or Host always invokes the gained ownership notification locally
if (NetworkManager.IsServer)
{
networkObject.InvokeBehaviourOnGainedOwnership();
}
}
else if (isRemoving)
{
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"Setting ownership twice? Client-ID {previousOwner} already owns NetworkObject ID {networkObject.NetworkObjectId}!");
}
}
/// <summary>
/// Returns a list of all NetworkObjects that belong to a client.
/// </summary>
/// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param>
public List<NetworkObject> GetClientOwnedObjects(ulong clientId)
{
if (!OwnershipToObjectsTable.ContainsKey(clientId))
{
OwnershipToObjectsTable.Add(clientId, new Dictionary<ulong, NetworkObject>());
}
return OwnershipToObjectsTable[clientId].Values.ToList();
}
private struct TriggerData
{
public FastBufferReader Reader;
@@ -96,8 +212,7 @@ namespace Unity.Netcode
/// <summary>
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed, or with
/// snapshot spawns enabled where the spawn is sent unreliably and not until the end of the frame.
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
///
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
@@ -194,37 +309,21 @@ namespace Unity.Netcode
return;
}
// Make sure the connected client entry exists before trying to remove ownership.
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
// Server removes the entry and takes over ownership before notifying
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
networkObject.OwnerClientId = NetworkManager.ServerClientId;
var message = new ChangeOwnershipMessage
{
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
if (networkClient.OwnedObjects[i] == networkObject)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
networkObject.OwnerClientIdInternal = null;
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
else
foreach (var client in NetworkManager.ConnectedClients)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
}
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
@@ -265,25 +364,10 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned");
}
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
{
if (networkClient.OwnedObjects[i] == networkObject)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
networkClient.OwnedObjects.Add(networkObject);
}
networkObject.OwnerClientId = clientId;
if (TryGetNetworkClient(clientId, out NetworkClient newNetworkClient))
{
newNetworkClient.OwnedObjects.Add(networkObject);
}
// Server adds entries for all client ownership
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
var message = new ChangeOwnershipMessage
{
@@ -414,7 +498,7 @@ namespace Unity.Netcode
}
// Ran on both server and client
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
{
if (networkObject == null)
{
@@ -452,15 +536,12 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is already spawned");
}
if (sceneObject.Header.HasNetworkVariables)
{
networkObject.SetNetworkVariableData(variableData);
}
networkObject.SetNetworkVariableData(variableData);
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
}
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
{
if (SpawnedObjects.ContainsKey(networkId))
{
@@ -471,38 +552,45 @@ namespace Unity.Netcode
// this initialization really should be at the bottom of the function
networkObject.IsSpawned = true;
// this initialization really should be at the top of this function. If and when we break the
// this initialization really should be at the top of this function. If and when we break the
// NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there;
// the current design banks on getting the network behaviour set and then only reading from it
// after the below initialization code. However cowardice compels me to hold off on moving this until
// that commit
// the current design banks on getting the network behaviour set and then only reading from it after the
// below initialization code. However cowardice compels me to hold off on moving this until that commit
networkObject.IsSceneObject = sceneObject;
networkObject.NetworkObjectId = networkId;
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
networkObject.OwnerClientIdInternal = ownerClientId;
networkObject.OwnerClientId = ownerClientId;
networkObject.IsPlayerObject = playerObject;
SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject);
SpawnedObjectsList.Add(networkObject);
if (ownerClientId != null)
if (NetworkManager.IsServer)
{
if (NetworkManager.IsServer)
if (playerObject)
{
if (playerObject)
// If there was an already existing player object for this player, then mark it as no longer
// a player object.
if (NetworkManager.ConnectedClients[ownerClientId].PlayerObject != null)
{
NetworkManager.ConnectedClients[ownerClientId.Value].PlayerObject = networkObject;
}
else
{
NetworkManager.ConnectedClients[ownerClientId.Value].OwnedObjects.Add(networkObject);
NetworkManager.ConnectedClients[ownerClientId].PlayerObject.IsPlayerObject = false;
}
NetworkManager.ConnectedClients[ownerClientId].PlayerObject = networkObject;
}
else if (playerObject && ownerClientId.Value == NetworkManager.LocalClientId)
}
else if (ownerClientId == NetworkManager.LocalClientId)
{
if (playerObject)
{
// If there was an already existing player object for this player, then mark it as no longer a player object.
if (NetworkManager.LocalClient.PlayerObject != null)
{
NetworkManager.LocalClient.PlayerObject.IsPlayerObject = false;
}
NetworkManager.LocalClient.PlayerObject = networkObject;
}
}
@@ -549,25 +637,21 @@ namespace Unity.Netcode
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
{
if (!NetworkManager.NetworkConfig.UseSnapshotSpawn)
//Currently, if this is called and the clientId (destination) is the server's client Id, this case will be checked
// within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer placing this check here. [NSS]
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
{
//Currently, if this is called and the clientId (destination) is the server's client Id, this case
//will be checked within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer
//placing this check here. [NSS]
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
{
return;
}
var message = new CreateObjectMessage
{
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
return;
}
var message = new CreateObjectMessage
{
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
}
internal ulong? GetSpawnParentId(NetworkObject networkObject)
@@ -605,14 +689,12 @@ namespace Unity.Netcode
// Makes scene objects ready to be reused
internal void ServerResetShudownStateForSceneObjects()
{
foreach (var sobj in SpawnedObjectsList)
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
foreach (var sobj in networkObjects)
{
if ((sobj.IsSceneObject != null && sobj.IsSceneObject == true) || sobj.DestroyWithScene)
{
sobj.IsSpawned = false;
sobj.DestroyWithScene = false;
sobj.IsSceneObject = null;
}
sobj.IsSpawned = false;
sobj.DestroyWithScene = false;
sobj.IsSceneObject = null;
}
}
@@ -653,14 +735,12 @@ namespace Unity.Netcode
else if (networkObjects[i].IsSpawned)
{
// If it is an in-scene placed NetworkObject then just despawn
// and let it be destroyed when the scene is unloaded. Otherwise,
// despawn and destroy it.
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
&& networkObjects[i].IsSceneObject.Value);
// and let it be destroyed when the scene is unloaded. Otherwise, despawn and destroy it.
var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
OnDespawnObject(networkObjects[i], shouldDestroy);
}
else
else if (networkObjects[i].IsSceneObject != null && !networkObjects[i].IsSceneObject.Value)
{
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
@@ -711,9 +791,10 @@ namespace Unity.Netcode
}
}
foreach (var networkObject in networkObjectsToSpawn)
{
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, null, true);
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true);
}
}
@@ -757,18 +838,6 @@ namespace Unity.Netcode
}
}
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
//Someone owns it.
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
if (networkClient.OwnedObjects[i].NetworkObjectId == networkObject.NetworkObjectId)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
}
networkObject.InvokeBehaviourNetworkDespawn();
if (NetworkManager != null && NetworkManager.IsServer)
@@ -782,38 +851,31 @@ namespace Unity.Netcode
});
}
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
if (networkObject != null)
{
networkObject.SnapshotDespawn();
}
else
{
if (networkObject != null)
// As long as we have any remaining clients, then notify of the object being destroy.
if (NetworkManager.ConnectedClientsList.Count > 0)
{
// As long as we have any remaining clients, then notify of the object being destroy.
if (NetworkManager.ConnectedClientsList.Count > 0)
m_TargetClientIds.Clear();
// We keep only the client for which the object is visible
// as the other clients have them already despawned
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
m_TargetClientIds.Clear();
// We keep only the client for which the object is visible
// as the other clients have them already despawned
foreach (var clientId in NetworkManager.ConnectedClientsIds)
if (networkObject.IsNetworkVisibleTo(clientId))
{
if (networkObject.IsNetworkVisibleTo(clientId))
{
m_TargetClientIds.Add(clientId);
}
m_TargetClientIds.Add(clientId);
}
}
var message = new DestroyObjectMessage
{
NetworkObjectId = networkObject.NetworkObjectId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
foreach (var targetClientId in m_TargetClientIds)
{
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
}
var message = new DestroyObjectMessage
{
NetworkObjectId = networkObject.NetworkObjectId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
foreach (var targetClientId in m_TargetClientIds)
{
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
}
}
}