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.4] - 2021-01-04 ### Added - Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565) ### Removed - Removed `com.unity.modules.ai` package dependency (#1565) - Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398) ### Fixed - Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354) - Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393) - Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379) - Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402) - NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385) - Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383) - Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434) - Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390) - Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500) - Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511) - Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323) ### Changed - The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384) - Updated com.unity.collections to 1.1.0 (#1451)
847 lines
35 KiB
C#
847 lines
35 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.Collections;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.Netcode
|
|
{
|
|
/// <summary>
|
|
/// Class that handles object spawning
|
|
/// </summary>
|
|
public class NetworkSpawnManager
|
|
{
|
|
/// <summary>
|
|
/// The currently spawned objects
|
|
/// </summary>
|
|
public readonly Dictionary<ulong, NetworkObject> SpawnedObjects = new Dictionary<ulong, NetworkObject>();
|
|
|
|
/// <summary>
|
|
/// A list of the spawned objects
|
|
/// </summary>
|
|
public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>();
|
|
|
|
private struct TriggerData
|
|
{
|
|
public FastBufferReader Reader;
|
|
public MessageHeader Header;
|
|
public ulong SenderId;
|
|
public float Timestamp;
|
|
public int SerializedHeaderSize;
|
|
}
|
|
private struct TriggerInfo
|
|
{
|
|
public float Expiry;
|
|
public NativeList<TriggerData> TriggerData;
|
|
}
|
|
|
|
private readonly Dictionary<ulong, TriggerInfo> m_Triggers = new Dictionary<ulong, TriggerInfo>();
|
|
|
|
/// <summary>
|
|
/// Gets the NetworkManager associated with this SpawnManager.
|
|
/// </summary>
|
|
public NetworkManager NetworkManager { get; }
|
|
|
|
internal NetworkSpawnManager(NetworkManager networkManager)
|
|
{
|
|
NetworkManager = networkManager;
|
|
}
|
|
|
|
internal readonly Queue<ReleasedNetworkId> ReleasedNetworkObjectIds = new Queue<ReleasedNetworkId>();
|
|
private ulong m_NetworkObjectIdCounter;
|
|
|
|
// A list of target ClientId, use when sending despawn commands. Kept as a member to reduce memory allocations
|
|
private List<ulong> m_TargetClientIds = new List<ulong>();
|
|
|
|
internal ulong GetNetworkObjectId()
|
|
{
|
|
if (ReleasedNetworkObjectIds.Count > 0 && NetworkManager.NetworkConfig.RecycleNetworkIds && (Time.unscaledTime - ReleasedNetworkObjectIds.Peek().ReleaseTime) >= NetworkManager.NetworkConfig.NetworkIdRecycleDelay)
|
|
{
|
|
return ReleasedNetworkObjectIds.Dequeue().NetworkId;
|
|
}
|
|
|
|
m_NetworkObjectIdCounter++;
|
|
|
|
return m_NetworkObjectIdCounter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the local player object or null if one does not exist
|
|
/// </summary>
|
|
/// <returns>The local player object or null if one does not exist</returns>
|
|
public NetworkObject GetLocalPlayerObject()
|
|
{
|
|
return GetPlayerNetworkObject(NetworkManager.LocalClientId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the player object with a given clientId or null if one does not exist. This is only valid server side.
|
|
/// </summary>
|
|
/// <returns>The player object with a given clientId or null if one does not exist</returns>
|
|
public NetworkObject GetPlayerNetworkObject(ulong clientId)
|
|
{
|
|
if (!NetworkManager.IsServer && NetworkManager.LocalClientId != clientId)
|
|
{
|
|
throw new NotServerException("Only the server can find player objects from other clients.");
|
|
}
|
|
|
|
if (TryGetNetworkClient(clientId, out NetworkClient networkClient))
|
|
{
|
|
return networkClient.PlayerObject;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <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.
|
|
///
|
|
/// 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.
|
|
/// </summary>
|
|
internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader reader, in NetworkContext context)
|
|
{
|
|
if (!m_Triggers.ContainsKey(networkObjectId))
|
|
{
|
|
m_Triggers[networkObjectId] = new TriggerInfo
|
|
{
|
|
Expiry = Time.realtimeSinceStartup + 1,
|
|
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
|
};
|
|
}
|
|
|
|
m_Triggers[networkObjectId].TriggerData.Add(new TriggerData
|
|
{
|
|
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
|
Header = context.Header,
|
|
Timestamp = context.Timestamp,
|
|
SenderId = context.SenderId,
|
|
SerializedHeaderSize = context.SerializedHeaderSize
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up any trigger that's existed for more than a second.
|
|
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
|
/// </summary>
|
|
internal unsafe void CleanupStaleTriggers()
|
|
{
|
|
ulong* staleKeys = stackalloc ulong[m_Triggers.Count()];
|
|
int index = 0;
|
|
foreach (var kvp in m_Triggers)
|
|
{
|
|
if (kvp.Value.Expiry < Time.realtimeSinceStartup)
|
|
{
|
|
|
|
staleKeys[index++] = kvp.Key;
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning($"Deferred messages were received for {nameof(NetworkObject)} #{kvp.Key}, but it did not spawn within 1 second.");
|
|
}
|
|
|
|
foreach (var data in kvp.Value.TriggerData)
|
|
{
|
|
data.Reader.Dispose();
|
|
}
|
|
|
|
kvp.Value.TriggerData.Dispose();
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < index; ++i)
|
|
{
|
|
m_Triggers.Remove(staleKeys[i]);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Cleans up any trigger that's existed for more than a second.
|
|
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
|
/// </summary>
|
|
internal void CleanupAllTriggers()
|
|
{
|
|
foreach (var kvp in m_Triggers)
|
|
{
|
|
foreach (var data in kvp.Value.TriggerData)
|
|
{
|
|
data.Reader.Dispose();
|
|
}
|
|
|
|
kvp.Value.TriggerData.Dispose();
|
|
}
|
|
|
|
m_Triggers.Clear();
|
|
}
|
|
|
|
internal void RemoveOwnership(NetworkObject networkObject)
|
|
{
|
|
if (!NetworkManager.IsServer)
|
|
{
|
|
throw new NotServerException("Only the server can change ownership");
|
|
}
|
|
|
|
if (!networkObject.IsSpawned)
|
|
{
|
|
throw new SpawnStateException("Object is not spawned");
|
|
}
|
|
|
|
// If we made it here then we are the server and if the server is determined to already be the owner
|
|
// then ignore the RemoveOwnership invocation.
|
|
if (networkObject.OwnerClientId == NetworkManager.ServerClientId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make sure the connected client entry exists before trying to remove ownership.
|
|
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
|
{
|
|
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
|
|
{
|
|
if (networkClient.OwnedObjects[i] == networkObject)
|
|
{
|
|
networkClient.OwnedObjects.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
networkObject.OwnerClientIdInternal = null;
|
|
|
|
var message = new ChangeOwnershipMessage
|
|
{
|
|
NetworkObjectId = networkObject.NetworkObjectId,
|
|
OwnerClientId = networkObject.OwnerClientId
|
|
};
|
|
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
|
|
|
foreach (var client in NetworkManager.ConnectedClients)
|
|
{
|
|
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function to get a network client for a clientId from the NetworkManager.
|
|
/// On the server this will check the <see cref="NetworkManager.ConnectedClients"/> list.
|
|
/// On a non-server this will check the <see cref="NetworkManager.LocalClient"/> only.
|
|
/// </summary>
|
|
/// <param name="clientId">The clientId for which to try getting the NetworkClient for.</param>
|
|
/// <param name="networkClient">The found NetworkClient. Null if no client was found.</param>
|
|
/// <returns>True if a NetworkClient with a matching id was found else false.</returns>
|
|
private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient)
|
|
{
|
|
if (NetworkManager.IsServer)
|
|
{
|
|
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient);
|
|
}
|
|
|
|
if (NetworkManager.LocalClient != null && clientId == NetworkManager.LocalClient.ClientId)
|
|
{
|
|
networkClient = NetworkManager.LocalClient;
|
|
return true;
|
|
}
|
|
|
|
networkClient = null;
|
|
return false;
|
|
}
|
|
|
|
internal void ChangeOwnership(NetworkObject networkObject, ulong clientId)
|
|
{
|
|
if (!NetworkManager.IsServer)
|
|
{
|
|
throw new NotServerException("Only the server can change ownership");
|
|
}
|
|
|
|
if (!networkObject.IsSpawned)
|
|
{
|
|
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;
|
|
|
|
|
|
var message = new ChangeOwnershipMessage
|
|
{
|
|
NetworkObjectId = networkObject.NetworkObjectId,
|
|
OwnerClientId = networkObject.OwnerClientId
|
|
};
|
|
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
|
|
|
foreach (var client in NetworkManager.ConnectedClients)
|
|
{
|
|
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should only run on the client
|
|
/// </summary>
|
|
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
|
{
|
|
NetworkObject parentNetworkObject = null;
|
|
|
|
if (parentNetworkId != null && !isReparented)
|
|
{
|
|
if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject))
|
|
{
|
|
parentNetworkObject = networkObject;
|
|
}
|
|
else
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
|
|
{
|
|
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
|
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
|
{
|
|
// Let the handler spawn the NetworkObject
|
|
var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity));
|
|
|
|
networkObject.NetworkManagerOwner = NetworkManager;
|
|
|
|
if (parentNetworkObject != null)
|
|
{
|
|
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
|
}
|
|
|
|
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
|
{
|
|
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
|
}
|
|
|
|
return networkObject;
|
|
}
|
|
else
|
|
{
|
|
// See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash
|
|
GameObject networkPrefabReference = null;
|
|
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
|
|
{
|
|
switch (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].Override)
|
|
{
|
|
default:
|
|
case NetworkPrefabOverride.None:
|
|
networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].Prefab;
|
|
break;
|
|
case NetworkPrefabOverride.Hash:
|
|
case NetworkPrefabOverride.Prefab:
|
|
networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If not, then there is an issue (user possibly didn't register the prefab properly?)
|
|
if (networkPrefabReference == null)
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
{
|
|
NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash
|
|
var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent<NetworkObject>();
|
|
|
|
networkObject.NetworkManagerOwner = NetworkManager;
|
|
|
|
if (parentNetworkObject != null)
|
|
{
|
|
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
|
}
|
|
|
|
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
|
{
|
|
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
|
}
|
|
|
|
return networkObject;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
|
|
|
if (networkObject == null)
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
|
{
|
|
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (parentNetworkObject != null)
|
|
{
|
|
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
|
}
|
|
|
|
return networkObject;
|
|
}
|
|
}
|
|
|
|
// Ran on both server and client
|
|
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
|
|
{
|
|
if (networkObject == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(networkObject), "Cannot spawn null object");
|
|
}
|
|
|
|
if (networkObject.IsSpawned)
|
|
{
|
|
throw new SpawnStateException("Object is already spawned");
|
|
}
|
|
|
|
SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene);
|
|
}
|
|
|
|
// Ran on both server and client
|
|
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject,
|
|
FastBufferReader variableData, bool destroyWithScene)
|
|
{
|
|
if (networkObject == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(networkObject), "Cannot spawn null object");
|
|
}
|
|
|
|
if (networkObject.IsSpawned)
|
|
{
|
|
throw new SpawnStateException("Object is already spawned");
|
|
}
|
|
|
|
if (sceneObject.Header.HasNetworkVariables)
|
|
{
|
|
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)
|
|
{
|
|
if (SpawnedObjects.ContainsKey(networkId))
|
|
{
|
|
Debug.LogWarning($"Trying to spawn {nameof(NetworkObject.NetworkObjectId)} {networkId} that already exists!");
|
|
return;
|
|
}
|
|
|
|
// 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
|
|
// 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
|
|
networkObject.IsSceneObject = sceneObject;
|
|
networkObject.NetworkObjectId = networkId;
|
|
|
|
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
|
|
|
networkObject.OwnerClientIdInternal = ownerClientId;
|
|
networkObject.IsPlayerObject = playerObject;
|
|
|
|
SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject);
|
|
SpawnedObjectsList.Add(networkObject);
|
|
|
|
if (ownerClientId != null)
|
|
{
|
|
if (NetworkManager.IsServer)
|
|
{
|
|
if (playerObject)
|
|
{
|
|
NetworkManager.ConnectedClients[ownerClientId.Value].PlayerObject = networkObject;
|
|
}
|
|
else
|
|
{
|
|
NetworkManager.ConnectedClients[ownerClientId.Value].OwnedObjects.Add(networkObject);
|
|
}
|
|
}
|
|
else if (playerObject && ownerClientId.Value == NetworkManager.LocalClientId)
|
|
{
|
|
NetworkManager.LocalClient.PlayerObject = networkObject;
|
|
}
|
|
}
|
|
|
|
if (NetworkManager.IsServer)
|
|
{
|
|
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
|
{
|
|
if (networkObject.CheckObjectVisibility == null || networkObject.CheckObjectVisibility(NetworkManager.ConnectedClientsList[i].ClientId))
|
|
{
|
|
networkObject.Observers.Add(NetworkManager.ConnectedClientsList[i].ClientId);
|
|
}
|
|
}
|
|
}
|
|
|
|
networkObject.SetCachedParent(networkObject.transform.parent);
|
|
networkObject.ApplyNetworkParenting();
|
|
NetworkObject.CheckOrphanChildren();
|
|
|
|
networkObject.InvokeBehaviourNetworkSpawn();
|
|
|
|
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
|
|
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
|
if (m_Triggers.ContainsKey(networkId))
|
|
{
|
|
var triggerInfo = m_Triggers[networkId];
|
|
foreach (var trigger in triggerInfo.TriggerData)
|
|
{
|
|
// Reader will be disposed within HandleMessage
|
|
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp, trigger.SerializedHeaderSize);
|
|
}
|
|
|
|
triggerInfo.TriggerData.Dispose();
|
|
m_Triggers.Remove(networkId);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var message = new CreateObjectMessage
|
|
{
|
|
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
|
|
};
|
|
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
|
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
|
|
|
networkObject.MarkVariablesDirty();
|
|
}
|
|
}
|
|
|
|
internal ulong? GetSpawnParentId(NetworkObject networkObject)
|
|
{
|
|
NetworkObject parentNetworkObject = null;
|
|
|
|
if (!networkObject.AlwaysReplicateAsRoot && networkObject.transform.parent != null)
|
|
{
|
|
parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
|
|
}
|
|
|
|
if (parentNetworkObject == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return parentNetworkObject.NetworkObjectId;
|
|
}
|
|
|
|
internal void DespawnObject(NetworkObject networkObject, bool destroyObject = false)
|
|
{
|
|
if (!networkObject.IsSpawned)
|
|
{
|
|
throw new SpawnStateException("Object is not spawned");
|
|
}
|
|
|
|
if (!NetworkManager.IsServer)
|
|
{
|
|
throw new NotServerException("Only server can despawn objects");
|
|
}
|
|
|
|
OnDespawnObject(networkObject, destroyObject);
|
|
}
|
|
|
|
// Makes scene objects ready to be reused
|
|
internal void ServerResetShudownStateForSceneObjects()
|
|
{
|
|
foreach (var sobj in SpawnedObjectsList)
|
|
{
|
|
if ((sobj.IsSceneObject != null && sobj.IsSceneObject == true) || sobj.DestroyWithScene)
|
|
{
|
|
sobj.IsSpawned = false;
|
|
sobj.DestroyWithScene = false;
|
|
sobj.IsSceneObject = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets called only by NetworkSceneManager.SwitchScene
|
|
/// </summary>
|
|
internal void ServerDestroySpawnedSceneObjects()
|
|
{
|
|
// This Allocation is "OK" for now because this code only executes when a new scene is switched to
|
|
// We need to create a new copy the HashSet of NetworkObjects (SpawnedObjectsList) so we can remove
|
|
// objects from the HashSet (SpawnedObjectsList) without causing a list has been modified exception to occur.
|
|
var spawnedObjects = SpawnedObjectsList.ToList();
|
|
|
|
foreach (var sobj in spawnedObjects)
|
|
{
|
|
if (sobj.IsSceneObject != null && sobj.IsSceneObject.Value && sobj.DestroyWithScene && sobj.gameObject.scene != NetworkManager.SceneManager.DontDestroyOnLoadScene)
|
|
{
|
|
SpawnedObjectsList.Remove(sobj);
|
|
UnityEngine.Object.Destroy(sobj.gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void DespawnAndDestroyNetworkObjects()
|
|
{
|
|
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
|
|
|
for (int i = 0; i < networkObjects.Length; i++)
|
|
{
|
|
if (networkObjects[i].NetworkManager == NetworkManager)
|
|
{
|
|
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
|
|
{
|
|
OnDespawnObject(networkObjects[i], false);
|
|
// Leave destruction up to the handler
|
|
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
|
|
}
|
|
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);
|
|
|
|
OnDespawnObject(networkObjects[i], shouldDestroy);
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void DestroySceneObjects()
|
|
{
|
|
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
|
|
|
for (int i = 0; i < networkObjects.Length; i++)
|
|
{
|
|
if (networkObjects[i].NetworkManager == NetworkManager)
|
|
{
|
|
if (networkObjects[i].IsSceneObject == null || networkObjects[i].IsSceneObject.Value == true)
|
|
{
|
|
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
|
|
{
|
|
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
|
|
if (SpawnedObjects.ContainsKey(networkObjects[i].NetworkObjectId))
|
|
{
|
|
OnDespawnObject(networkObjects[i], false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void ServerSpawnSceneObjectsOnStartSweep()
|
|
{
|
|
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
|
|
|
for (int i = 0; i < networkObjects.Length; i++)
|
|
{
|
|
if (networkObjects[i].NetworkManager == NetworkManager)
|
|
{
|
|
if (networkObjects[i].IsSceneObject == null)
|
|
{
|
|
SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, null, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject)
|
|
{
|
|
if (NetworkManager == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// We have to do this check first as subsequent checks assume we can access NetworkObjectId.
|
|
if (networkObject == null)
|
|
{
|
|
Debug.LogWarning($"Trying to destroy network object but it is null");
|
|
return;
|
|
}
|
|
|
|
// Removal of spawned object
|
|
if (!SpawnedObjects.ContainsKey(networkObject.NetworkObjectId))
|
|
{
|
|
Debug.LogWarning($"Trying to destroy object {networkObject.NetworkObjectId} but it doesn't seem to exist anymore!");
|
|
return;
|
|
}
|
|
|
|
// If we are shutting down the NetworkManager, then ignore resetting the parent
|
|
if (!NetworkManager.ShutdownInProgress)
|
|
{
|
|
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
|
|
foreach (var spawnedNetObj in SpawnedObjectsList)
|
|
{
|
|
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
|
|
if (isReparented && latestParent == networkObject.NetworkObjectId)
|
|
{
|
|
spawnedNetObj.gameObject.transform.parent = null;
|
|
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (NetworkManager.NetworkConfig.RecycleNetworkIds)
|
|
{
|
|
ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId()
|
|
{
|
|
NetworkId = networkObject.NetworkObjectId,
|
|
ReleaseTime = Time.unscaledTime
|
|
});
|
|
}
|
|
|
|
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
|
{
|
|
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)
|
|
{
|
|
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))
|
|
{
|
|
m_TargetClientIds.Add(clientId);
|
|
}
|
|
}
|
|
|
|
var message = new DestroyObjectMessage
|
|
{
|
|
NetworkObjectId = networkObject.NetworkObjectId
|
|
};
|
|
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
|
foreach (var targetClientId in m_TargetClientIds)
|
|
{
|
|
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
networkObject.IsSpawned = false;
|
|
|
|
if (SpawnedObjects.Remove(networkObject.NetworkObjectId))
|
|
{
|
|
SpawnedObjectsList.Remove(networkObject);
|
|
}
|
|
|
|
var gobj = networkObject.gameObject;
|
|
if (destroyGameObject && gobj != null)
|
|
{
|
|
if (NetworkManager.PrefabHandler.ContainsHandler(networkObject))
|
|
{
|
|
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObject);
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Object.Destroy(gobj);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates all spawned <see cref="NetworkObject.Observers"/> for the specified client
|
|
/// Note: if the clientId is the server then it is observable to all spawned <see cref="NetworkObject"/>'s
|
|
/// </summary>
|
|
internal void UpdateObservedNetworkObjects(ulong clientId)
|
|
{
|
|
foreach (var sobj in SpawnedObjectsList)
|
|
{
|
|
if (sobj.CheckObjectVisibility == null || NetworkManager.IsServer)
|
|
{
|
|
if (!sobj.Observers.Contains(clientId))
|
|
{
|
|
sobj.Observers.Add(clientId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (sobj.CheckObjectVisibility(clientId))
|
|
{
|
|
sobj.Observers.Add(clientId);
|
|
}
|
|
else if (sobj.Observers.Contains(clientId))
|
|
{
|
|
sobj.Observers.Remove(clientId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|