This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Unity Technologies 22d877d1b2 com.unity.netcode.gameobjects@1.0.0-pre.2
# Changelog

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

## [1.0.0-pre.2] - 2020-12-20

### Added

- Associated Known Issues for the 1.0.0-pre.1 release in the changelog

### Changed

- Updated label for `1.0.0-pre.1` changelog section

## [1.0.0-pre.1] - 2020-12-20

### Added

- Added `ClientNetworkTransform` sample to the SDK package (#1168)
- Added `Bootstrap` sample to the SDK package (#1140)
- Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913)
  - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons
- Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101)
- Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060)
- Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727)
- Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855)
- Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670)
- When a client tries to spawn a `NetworkObject` an exception is thrown to indicate unsupported behavior. (#981)
- Added a `NetworkTime` and `NetworkTickSystem` which allows for improved control over time and ticks. (#845)
- Added a `OnNetworkDespawn` function to `NetworkObject` which gets called when a `NetworkObject` gets despawned and can be overriden. (#865)
- Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192)
  - Disabled by default for now, except spawn/despawn messages
  - Will leverage unreliable messages with eventual consistency
- `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762)
- Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878)
- `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190)
- Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175)
- Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173)
- Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872)

### Changed

- Bumped minimum Unity version, renamed package as "Unity Netcode for GameObjects", replaced `MLAPI` namespace and its variants with `Unity.Netcode` namespace and per asm-def variants (#1007, #1009, #1015, #1017, #1019, #1025, #1026, #1065)
  - Minimum Unity version:
    - 2019.4 → 2020.3+
  - Package rename:
    - Display name: `MLAPI Networking Library` → `Netcode for GameObjects`
    - Name: `com.unity.multiplayer.mlapi` → `com.unity.netcode.gameobjects`
    - Updated package description
  - All `MLAPI.x` namespaces are replaced with `Unity.Netcode`
    - `MLAPI.Messaging` → `Unity.Netcode`
    - `MLAPI.Connection` → `Unity.Netcode`
    - `MLAPI.Logging` → `Unity.Netcode`
    - `MLAPI.SceneManagement` → `Unity.Netcode`
    - and other `MLAPI.x` variants to `Unity.Netcode`
  - All assembly definitions are renamed with `Unity.Netcode.x` variants
    - `Unity.Multiplayer.MLAPI.Runtime` → `Unity.Netcode.Runtime`
    - `Unity.Multiplayer.MLAPI.Editor` → `Unity.Netcode.Editor`
    - and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants
- Renamed `Prototyping` namespace and assembly definition to `Components` (#1145)
- Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217)
- Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080)
- `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955)
- `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080)
- `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698)
- `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865)
- Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074)
- `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788)
- Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer<T>` instead of `NetworkSerializer` (#1187)
- Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187)
- Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196)

### Removed

- Removed `NetworkNavMeshAgent` (#1150)
- Removed `NetworkDictionary`, `NetworkSet` (#1149)
- Removed `NetworkVariableSettings` (#1097)
- Removed predefined `NetworkVariable<T>` types (#1093)
    - Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion`
- Removed `NetworkChannel` and `MultiplexTransportAdapter` (#1133)
- Removed ILPP backend for 2019.4, minimum required version is 2020.3+ (#895)
- `NetworkManager.NetworkConfig` had the following properties removed: (#1080)
  - Scene Registrations no longer exists
  - Allow Runtime Scene Changes was no longer needed and was removed
- Removed the NetworkObject.Spawn payload parameter (#1005)
- Removed `ProfilerCounter`, the original MLAPI network profiler, and the built-in network profiler module (2020.3). A replacement can now be found in the Multiplayer Tools package. (#1048)
- Removed UNet RelayTransport and related relay functionality in UNetTransport (#1081)
- Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187)
- Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187)
- Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179)
- Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101)
- Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108)

### Fixed

- Fixed ServerRpc ownership check to `Debug.LogError` instead of `Debug.LogWarning` (#1126)
- Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092)
- Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803)
- Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678)
- Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011)
  - Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s
- Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676)
- Fixed connection approval not being triggered for host (#675)
- Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218)
- Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266)
- Improved runtime performance and reduced GC pressure (#1187)
- Fixed #915 - clients are receiving data from objects not visible to them (#1099)
- Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826)
- Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844)

### Known Issues

- `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation
- `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions
- `NetworkTransform` interpolates from the origin when switching Local Space synchronization
- Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects
- Cannot send an array of `INetworkSerializable` in RPCs
- ILPP generation fails with special characters in project path

## [0.2.0] - 2021-06-03

WIP version increment to pass package validation checks. Changelog & final version number TBD.

## [0.1.1] - 2021-06-01

This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package.

### Changed

- Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release.

## [0.1.0] - 2021-03-23

This is the initial experimental Unity MLAPI Package, v0.1.0.

### Added

- Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)).
- Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details.
- `INetworkSerializable` interface replaces `IBitWritable`.
- Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally.
- Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details:
  - It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution.
  - You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation.
  - `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs.
- Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately.
- [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute.
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later:
  - Added new profiler modules for MLAPI that report important network data.
  - Attached the profiler to a remote player to view network data over the wire.
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`.

### Changed

- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management.
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings.

For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514).

| Previous MLAPI Versions | V 0.1.0 Name |
| -- | -- |
| `NetworkingManager` | `NetworkManager` |
| `NetworkedObject` | `NetworkObject` |
| `NetworkedBehaviour` | `NetworkBehaviour` |
| `NetworkedClient` | `NetworkClient` |
| `NetworkedPrefab` | `NetworkPrefab` |
| `NetworkedVar` | `NetworkVariable` |
| `NetworkedTransform` | `NetworkTransform` |
| `NetworkedAnimator` | `NetworkAnimator` |
| `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` |
| `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` |
| `SpawnManager` | `NetworkSpawnManager` |
| `BitStream` | `NetworkBuffer` |
| `BitReader` | `NetworkReader` |
| `BitWriter` | `NetworkWriter` |
| `NetEventType` | `NetworkEventType` |
| `ChannelType` | `NetworkDelivery` |
| `Channel` | `NetworkChannel` |
| `Transport` | `NetworkTransport` |
| `NetworkedDictionary` | `NetworkDictionary` |
| `NetworkedList` | `NetworkList` |
| `NetworkedSet` | `NetworkSet` |
| `MLAPIConstants` | `NetworkConstants` |
| `UnetTransport` | `UNetTransport` |

### Fixed

- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame.
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method.
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode.
- [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions.
- [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits.
- [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`.

### Removed

With a new release of MLAPI in Unity, some features have been removed:

- SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 -->
- [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo.
- [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes:
    - Removed `SecuritySendFlags` from all APIs.
    - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`.
    - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries.
    - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing.
- Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later.
- Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details.
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer.

### Known Issues

- `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination.
- For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error.
- For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work.
- For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel.
- `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them
- `NetworkTransform` have the following issues:
  - Replicated objects may have jitter.
  - The owner is always authoritative about the object's position.
  - Scale is not synchronized.
- Connection Approval is not called on the host client.
- For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages.
- For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly.

## [0.0.1-preview.1] - 2020-12-20

This was an internally-only-used version of the Unity MLAPI Package
2020-12-20 00:00:00 +00:00

1619 lines
65 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if MULTIPLAYER_TOOLS
using Unity.Multiplayer.Tools;
#endif
using Unity.Profiling;
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
namespace Unity.Netcode
{
/// <summary>
/// The main component of the library
/// </summary>
[AddComponentMenu("Netcode/" + nameof(NetworkManager), -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `public`
internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, RpcReceiveHandler> __rpc_func_table = new Dictionary<uint, RpcReceiveHandler>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, string> __rpc_name_table = new Dictionary<uint, string>();
#endif
#pragma warning restore IDE1006 // restore naming rule violation check
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private static ProfilerMarker s_SyncTime = new ProfilerMarker($"{nameof(NetworkManager)}.SyncTime");
private static ProfilerMarker s_TransportPoll = new ProfilerMarker($"{nameof(NetworkManager)}.TransportPoll");
private static ProfilerMarker s_TransportConnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportConnect");
private static ProfilerMarker s_HandleIncomingData = new ProfilerMarker($"{nameof(NetworkManager)}.{nameof(HandleIncomingData)}");
private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect");
#endif
private const double k_TimeSyncFrequency = 1.0d; // sync every second, TODO will be removed once timesync is done via snapshots
private const float k_DefaultBufferSizeSec = 0.05f; // todo talk with UX/Product, find good default value for this
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
{
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
}
internal SnapshotSystem SnapshotSystem { get; private set; }
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; }
internal MessagingSystem MessagingSystem { get; private set; }
private NetworkPrefabHandler m_PrefabHandler;
public NetworkPrefabHandler PrefabHandler
{
get
{
if (m_PrefabHandler == null)
{
m_PrefabHandler = new NetworkPrefabHandler();
}
return m_PrefabHandler;
}
}
private class NetworkManagerHooks : INetworkHooks
{
private NetworkManager m_NetworkManager;
internal NetworkManagerHooks(NetworkManager manager)
{
m_NetworkManager = manager;
}
public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery)
{
}
public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes)
{
}
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
{
}
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
{
}
public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
{
}
public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
{
}
public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
{
}
public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
{
}
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
{
return true;
}
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
{
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
(client.ConnectionState == PendingClient.State.PendingApproval ||
(client.ConnectionState == PendingClient.State.PendingConnection &&
messageType != typeof(ConnectionRequestMessage))))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId.ToString()} before it has been accepted");
}
return false;
}
return true;
}
}
private class NetworkManagerMessageSender : IMessageSender
{
private NetworkManager m_NetworkManager;
public NetworkManagerMessageSender(NetworkManager manager)
{
m_NetworkManager = manager;
}
public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
{
var length = batchData.Length;
//TODO: Transport needs to have a way to send it data without copying and allocating here.
var bytes = batchData.ToArray();
var sendBuffer = new ArraySegment<byte>(bytes, 0, length);
m_NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, delivery);
}
}
/// <summary>
/// Returns the <see cref="GameObject"/> to use as the override as could be defined within the NetworkPrefab list
/// Note: This should be used to create <see cref="GameObject"/> pools (with <see cref="NetworkObject"/> components)
/// under the scenario where you are using the Host model as it spawns everything locally. As such, the override
/// will not be applied when spawning locally on a Host.
/// Related Classes and Interfaces:
/// <see cref="NetworkPrefabHandler"/>
/// <see cref="INetworkPrefabInstanceHandler"/>
/// </summary>
/// <param name="gameObject">the <see cref="GameObject"/> to be checked for a <see cref="NetworkManager"/> defined NetworkPrefab override</param>
/// <returns>a <see cref="GameObject"/> that is either the override or if no overrides exist it returns the same as the one passed in as a parameter</returns>
public GameObject GetNetworkPrefabOverride(GameObject gameObject)
{
var networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject != null)
{
if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{
switch (NetworkConfig.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override)
{
case NetworkPrefabOverride.Hash:
case NetworkPrefabOverride.Prefab:
{
return NetworkConfig.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab;
}
}
}
}
return gameObject;
}
public NetworkTimeSystem NetworkTimeSystem { get; private set; }
public NetworkTickSystem NetworkTickSystem { get; private set; }
public NetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default;
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
/// <summary>
/// Gets or sets if the NetworkManager should be marked as DontDestroyOnLoad
/// </summary>
[HideInInspector] public bool DontDestroy = true;
/// <summary>
/// Gets or sets if the application should be set to run in background
/// </summary>
[HideInInspector] public bool RunInBackground = true;
/// <summary>
/// The log level to use
/// </summary>
[HideInInspector] public LogLevel LogLevel = LogLevel.Normal;
/// <summary>
/// The singleton instance of the NetworkManager
/// </summary>
public static NetworkManager Singleton { get; private set; }
/// <summary>
/// Gets the SpawnManager for this NetworkManager
/// </summary>
public NetworkSpawnManager SpawnManager { get; private set; }
public CustomMessagingManager CustomMessagingManager { get; private set; }
public NetworkSceneManager SceneManager { get; private set; }
/// <summary>
/// Gets the networkId of the server
/// </summary>
public ulong ServerClientId => NetworkConfig.NetworkTransport?.ServerClientId ??
throw new NullReferenceException(
$"The transport in the active {nameof(NetworkConfig)} is null");
/// <summary>
/// Returns ServerClientId if IsServer or LocalClientId if not
/// </summary>
public ulong LocalClientId
{
get => IsServer ? NetworkConfig.NetworkTransport.ServerClientId : m_LocalClientId;
internal set => m_LocalClientId = value;
}
private ulong m_LocalClientId;
private Dictionary<ulong, NetworkClient> m_ConnectedClients = new Dictionary<ulong, NetworkClient>();
private List<NetworkClient> m_ConnectedClientsList = new List<NetworkClient>();
private List<ulong> m_ConnectedClientIds = new List<ulong>();
/// <summary>
/// Gets a dictionary of connected clients and their clientId keys. This is only accessible on the server.
/// </summary>
public IReadOnlyDictionary<ulong, NetworkClient> ConnectedClients
{
get
{
if (IsServer == false)
{
throw new NotServerException($"{nameof(ConnectedClients)} should only be accessed on server.");
}
return m_ConnectedClients;
}
}
/// <summary>
/// Gets a list of connected clients. This is only accessible on the server.
/// </summary>
public IReadOnlyList<NetworkClient> ConnectedClientsList
{
get
{
if (IsServer == false)
{
throw new NotServerException($"{nameof(ConnectedClientsList)} should only be accessed on server.");
}
return m_ConnectedClientsList;
}
}
/// <summary>
/// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
/// </summary>
public IReadOnlyList<ulong> ConnectedClientsIds
{
get
{
if (IsServer == false)
{
throw new NotServerException($"{nameof(m_ConnectedClientIds)} should only be accessed on server.");
}
return m_ConnectedClientIds;
}
}
/// <summary>
/// Gets the local <see cref="NetworkClient"/> for this client.
/// </summary>
public NetworkClient LocalClient { get; internal set; }
/// <summary>
/// Gets a dictionary of the clients that have been accepted by the transport but are still pending by the Netcode. This is only populated on the server.
/// </summary>
public readonly Dictionary<ulong, PendingClient> PendingClients = new Dictionary<ulong, PendingClient>();
/// <summary>
/// Gets Whether or not a server is running
/// </summary>
public bool IsServer { get; internal set; }
/// <summary>
/// Gets Whether or not a client is running
/// </summary>
public bool IsClient { get; internal set; }
/// <summary>
/// Gets if we are running as host
/// </summary>
public bool IsHost => IsServer && IsClient;
/// <summary>
/// Gets Whether or not we are listening for connections
/// </summary>
public bool IsListening { get; internal set; }
/// <summary>
/// Gets if we are connected as a client
/// </summary>
public bool IsConnectedClient { get; internal set; }
/// <summary>
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
/// </summary>
public event Action<ulong> OnClientConnectedCallback = null;
internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId);
/// <summary>
/// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
/// </summary>
public event Action<ulong> OnClientDisconnectCallback = null;
internal void InvokeOnClientDisconnectCallback(ulong clientId) => OnClientDisconnectCallback?.Invoke(clientId);
/// <summary>
/// The callback to invoke once the server is ready
/// </summary>
public event Action OnServerStarted = null;
/// <summary>
/// Delegate type called when connection has been approved. This only has to be set on the server.
/// </summary>
/// <param name="createPlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
/// <param name="playerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
/// <param name="approved">Whether or not the client was approved</param>
/// <param name="position">The position to spawn the client at. If null, the prefab position is used.</param>
/// <param name="rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved,
Vector3? position, Quaternion? rotation);
/// <summary>
/// The callback to invoke during connection approval
/// </summary>
public event Action<byte[], ulong, ConnectionApprovedDelegate> ConnectionApprovalCallback = null;
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) =>
ConnectionApprovalCallback?.Invoke(payload, clientId, action);
/// <summary>
/// The current NetworkConfig
/// </summary>
[HideInInspector] public NetworkConfig NetworkConfig;
/// <summary>
/// The current host name we are connected to, used to validate certificate
/// </summary>
public string ConnectedHostname { get; private set; }
internal INetworkMetrics NetworkMetrics { get; private set; }
internal static event Action OnSingletonReady;
#if UNITY_EDITOR
private void OnValidate()
{
if (NetworkConfig == null)
{
return; // May occur when the component is added
}
if (GetComponentInChildren<NetworkObject>() != null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkManager)} cannot be a {nameof(NetworkObject)}.");
}
}
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
// If the scene is not dirty or the asset database is currently updating then we can skip updating the NetworkPrefab information
if (!activeScene.isDirty || EditorApplication.isUpdating)
{
return;
}
// During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
// Check network prefabs and assign to dictionary for quick look up
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
{
var networkPrefab = NetworkConfig.NetworkPrefabs[i];
var networkPrefabGo = networkPrefab?.Prefab;
if (networkPrefabGo != null)
{
var networkObject = networkPrefabGo.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"Cannot register {PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root");
}
}
else
{
{
var childNetworkObjects = new List<NetworkObject>();
networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects);
if (childNetworkObjects.Count > 1) // total count = 1 root NetworkObject + n child NetworkObjects
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} has child {nameof(NetworkObject)}(s) but they will not be spawned across the network (unsupported {nameof(NetworkPrefab)} setup)");
}
}
}
// Default to the standard NetworkPrefab.Prefab's NetworkObject first
var globalObjectIdHash = networkObject.GlobalObjectIdHash;
// Now check to see if it has an override
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Prefab:
{
if (NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride == null &&
NetworkConfig.NetworkPrefabs[i].Prefab != null)
{
if (networkPrefab.SourcePrefabToOverride == null)
{
networkPrefab.SourcePrefabToOverride = networkPrefabGo;
}
globalObjectIdHash = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>().GlobalObjectIdHash;
}
break;
}
case NetworkPrefabOverride.Hash:
globalObjectIdHash = networkPrefab.SourceHashToOverride;
break;
}
// Add to the NetworkPrefabOverrideLinks or handle a new (blank) entries
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(globalObjectIdHash, networkPrefab);
}
else
{
// Duplicate entries can happen when adding a new entry into a list of existing entries
// Either this is user error or a new entry, either case we replace it with a new, blank, NetworkPrefab under this condition
NetworkConfig.NetworkPrefabs[i] = new NetworkPrefab();
}
}
}
}
}
#endif
private void Initialize(bool server)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(Initialize));
}
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
MessagingSystem = new MessagingSystem(new NetworkManagerMessageSender(this), this);
MessagingSystem.Hook(new NetworkManagerHooks(this));
#if DEVELOPMENT_BUILD || UNITY_EDITOR
MessagingSystem.Hook(new ProfilingHooks());
#endif
#if MULTIPLAYER_TOOLS
MessagingSystem.Hook(new MetricHooks(this));
#endif
LocalClientId = ulong.MaxValue;
PendingClients.Clear();
m_ConnectedClients.Clear();
m_ConnectedClientsList.Clear();
m_ConnectedClientIds.Clear();
LocalClient = null;
NetworkObject.OrphanChildren.Clear();
// Create spawn manager instance
SpawnManager = new NetworkSpawnManager(this);
CustomMessagingManager = new CustomMessagingManager(this);
SceneManager = new NetworkSceneManager(this);
BehaviourUpdater = new NetworkBehaviourUpdater();
if (NetworkMetrics == null)
{
#if MULTIPLAYER_TOOLS
NetworkMetrics = new NetworkMetrics();
#else
NetworkMetrics = new NullNetworkMetrics();
#endif
}
#if MULTIPLAYER_TOOLS
NetworkSolutionInterface.SetInterface(new NetworkSolutionInterfaceParameters
{
NetworkObjectProvider = new NetworkObjectProvider(this),
});
#endif
if (NetworkConfig.NetworkTransport == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError("No transport has been selected!");
}
return;
}
//This 'if' should never enter
if (SnapshotSystem != null)
{
SnapshotSystem.Dispose();
SnapshotSystem = null;
}
SnapshotSystem = new SnapshotSystem(this);
if (server)
{
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
}
else
{
NetworkTimeSystem = new NetworkTimeSystem(1.0 / NetworkConfig.TickRate, k_DefaultBufferSizeSec, 0.2);
}
NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0);
NetworkTickSystem.Tick += OnNetworkManagerTick;
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
// This is used to remove entries not needed or invalid
var removeEmptyPrefabs = new List<int>();
// Always clear our prefab override links before building
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
// Build the NetworkPrefabOverrideLinks dictionary
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
{
var sourcePrefabGlobalObjectIdHash = (uint)0;
var targetPrefabGlobalObjectIdHash = (uint)0;
var networkObject = (NetworkObject)null;
if (NetworkConfig.NetworkPrefabs[i] == null || (NetworkConfig.NetworkPrefabs[i].Prefab == null && NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning(
$"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {i})");
}
removeEmptyPrefabs.Add(i);
continue;
}
else if (NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None)
{
var networkPrefab = NetworkConfig.NetworkPrefabs[i];
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} is missing " +
$"a {nameof(NetworkObject)} component (entry will be ignored).");
}
removeEmptyPrefabs.Add(i);
continue;
}
// Otherwise get the GlobalObjectIdHash value
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
}
else // Validate Overrides
{
// Validate source prefab override values first
switch (NetworkConfig.NetworkPrefabs[i].Override)
{
case NetworkPrefabOverride.Hash:
{
if (NetworkConfig.NetworkPrefabs[i].SourceHashToOverride == 0)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourceHashToOverride)} is zero " +
"(entry will be ignored).");
}
removeEmptyPrefabs.Add(i);
continue;
}
sourcePrefabGlobalObjectIdHash = NetworkConfig.NetworkPrefabs[i].SourceHashToOverride;
break;
}
case NetworkPrefabOverride.Prefab:
{
if (NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourcePrefabToOverride)} is null (entry will be ignored).");
}
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {NetworkConfig.NetworkPrefabs[i].SourceHashToOverride} will be removed and ignored.");
removeEmptyPrefabs.Add(i);
continue;
}
else
{
networkObject = NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}) " +
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
}
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry (\"{NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}\") will be removed and ignored.");
removeEmptyPrefabs.Add(i);
continue;
}
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
}
break;
}
}
// Validate target prefab override values next
if (NetworkConfig.NetworkPrefabs[i].OverridingTargetPrefab == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.OverridingTargetPrefab)} is null!");
}
removeEmptyPrefabs.Add(i);
switch (NetworkConfig.NetworkPrefabs[i].Override)
{
case NetworkPrefabOverride.Hash:
{
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {NetworkConfig.NetworkPrefabs[i].SourceHashToOverride} will be removed and ignored.");
break;
}
case NetworkPrefabOverride.Prefab:
{
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}) will be removed and ignored.");
break;
}
}
continue;
}
else
{
targetPrefabGlobalObjectIdHash = NetworkConfig.NetworkPrefabs[i].OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
}
}
// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(sourcePrefabGlobalObjectIdHash))
{
if (NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None)
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
}
else
{
if (!NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetPrefabGlobalObjectIdHash))
{
switch (NetworkConfig.NetworkPrefabs[i].Override)
{
case NetworkPrefabOverride.Prefab:
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
}
break;
case NetworkPrefabOverride.Hash:
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
}
break;
}
}
else
{
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {targetPrefabGlobalObjectIdHash}! Removing entry from list!");
removeEmptyPrefabs.Add(i);
}
}
}
else
{
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {sourcePrefabGlobalObjectIdHash}! Removing entry from list!");
removeEmptyPrefabs.Add(i);
}
}
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
if (NetworkConfig.PlayerPrefab != null)
{
var playerPrefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
if (playerPrefabNetworkObject != null)
{
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject
.GlobalObjectIdHash))
{
//Then add a new entry for the player prefab
var playerNetworkPrefab = new NetworkPrefab();
playerNetworkPrefab.Prefab = NetworkConfig.PlayerPrefab;
NetworkConfig.NetworkPrefabs.Insert(0, playerNetworkPrefab);
NetworkConfig.NetworkPrefabOverrideLinks.Add(playerPrefabNetworkObject.GlobalObjectIdHash,
playerNetworkPrefab);
}
}
else
{
// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{NetworkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!.");
}
}
// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
// Iterate backwards so indices don't shift as we remove
for (int i = removeEmptyPrefabs.Count - 1; i >= 0; i--)
{
NetworkConfig.NetworkPrefabs.RemoveAt(removeEmptyPrefabs[i]);
}
removeEmptyPrefabs.Clear();
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
NetworkConfig.NetworkTransport.Initialize();
}
/// <summary>
/// Starts a server
/// </summary>
public bool StartServer()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo("StartServer()");
}
if (IsServer || IsClient)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Cannot start server while an instance is already running");
}
return false;
}
if (NetworkConfig.ConnectionApproval)
{
if (ConnectionApprovalCallback == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
"No ConnectionApproval callback defined. Connection approval will timeout");
}
}
}
Initialize(true);
var result = NetworkConfig.NetworkTransport.StartServer();
IsServer = true;
IsClient = false;
IsListening = true;
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
OnServerStarted?.Invoke();
return result;
}
/// <summary>
/// Starts a client
/// </summary>
public bool StartClient()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(StartClient));
}
if (IsServer || IsClient)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Cannot start client while an instance is already running");
}
return false;
}
Initialize(false);
MessagingSystem.ClientConnected(ServerClientId);
var result = NetworkConfig.NetworkTransport.StartClient();
IsServer = false;
IsClient = true;
IsListening = true;
return result;
}
/// <summary>
/// Starts a Host
/// </summary>
public bool StartHost()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(StartHost));
}
if (IsServer || IsClient)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Cannot start host while an instance is already running");
}
return false;
}
if (NetworkConfig.ConnectionApproval)
{
if (ConnectionApprovalCallback == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
"No ConnectionApproval callback defined. Connection approval will timeout");
}
}
}
Initialize(true);
var result = NetworkConfig.NetworkTransport.StartServer();
MessagingSystem.ClientConnected(ServerClientId);
LocalClientId = ServerClientId;
NetworkMetrics.SetConnectionId(LocalClientId);
IsServer = true;
IsClient = true;
IsListening = true;
if (NetworkConfig.ConnectionApproval)
{
InvokeConnectionApproval(NetworkConfig.ConnectionData, ServerClientId,
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
{
// You cannot decline the local server. Force approved to true
if (!approved)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
"You cannot decline the host connection. The connection was automatically approved.");
}
}
HandleApproval(ServerClientId, createPlayerObject, playerPrefabHash, true, position, rotation);
});
}
else
{
HandleApproval(ServerClientId, NetworkConfig.PlayerPrefab != null, null, true, null, null);
}
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
OnServerStarted?.Invoke();
return result;
}
public void SetSingleton()
{
Singleton = this;
OnSingletonReady?.Invoke();
}
private void OnEnable()
{
if (DontDestroy)
{
DontDestroyOnLoad(gameObject);
}
if (RunInBackground)
{
Application.runInBackground = true;
}
if (Singleton == null)
{
SetSingleton();
}
}
private void Awake()
{
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
}
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
private void OnSceneUnloaded(Scene scene)
{
if (scene == gameObject.scene)
{
OnDestroy();
}
}
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application.
private void OnApplicationQuit()
{
OnDestroy();
}
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
private void OnDestroy()
{
Shutdown();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
if (Singleton == this)
{
Singleton = null;
}
}
/// <summary>
/// Globally shuts down the library.
/// Disconnects clients if connected and stops server if running.
/// </summary>
public void Shutdown()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(Shutdown));
}
if (IsServer)
{
// make sure all messages are flushed before transport disconnect clients
if (MessagingSystem != null)
{
MessagingSystem.ProcessSendQueues();
}
var disconnectedIds = new HashSet<ulong>();
//Don't know if I have to disconnect the clients. I'm assuming the NetworkTransport does all the cleaning on shutdown. But this way the clients get a disconnect message from server (so long it does't get lost)
foreach (KeyValuePair<ulong, NetworkClient> pair in ConnectedClients)
{
if (!disconnectedIds.Contains(pair.Key))
{
disconnectedIds.Add(pair.Key);
if (pair.Key == NetworkConfig.NetworkTransport.ServerClientId)
{
continue;
}
NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key);
}
}
foreach (KeyValuePair<ulong, PendingClient> pair in PendingClients)
{
if (!disconnectedIds.Contains(pair.Key))
{
disconnectedIds.Add(pair.Key);
if (pair.Key == NetworkConfig.NetworkTransport.ServerClientId)
{
continue;
}
NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key);
}
}
}
if (IsClient && IsConnectedClient)
{
// Client only, send disconnect to server
NetworkConfig.NetworkTransport.DisconnectLocalClient();
}
IsConnectedClient = false;
IsServer = false;
IsClient = false;
this.UnregisterAllNetworkUpdates();
if (SnapshotSystem != null)
{
SnapshotSystem.Dispose();
SnapshotSystem = null;
}
if (NetworkTickSystem != null)
{
NetworkTickSystem.Tick -= OnNetworkManagerTick;
NetworkTickSystem = null;
}
if (MessagingSystem != null)
{
MessagingSystem.Dispose();
MessagingSystem = null;
}
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
if (SpawnManager != null)
{
SpawnManager.DestroyNonSceneObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
if (SceneManager != null)
{
// Let the NetworkSceneManager clean up its two SceneEvenData instances
SceneManager.Dispose();
SceneManager = null;
}
if (CustomMessagingManager != null)
{
CustomMessagingManager = null;
}
if (BehaviourUpdater != null)
{
BehaviourUpdater = null;
}
// This is required for handling the potential scenario where multiple NetworkManager instances are created.
// See MTT-860 for more information
if (IsListening)
{
//The Transport is set during initialization, thus it is possible for the Transport to be null
NetworkConfig?.NetworkTransport?.Shutdown();
}
IsListening = false;
}
// INetworkUpdateSystem
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
{
case NetworkUpdateStage.EarlyUpdate:
OnNetworkEarlyUpdate();
break;
case NetworkUpdateStage.PreUpdate:
OnNetworkPreUpdate();
break;
case NetworkUpdateStage.PostLateUpdate:
OnNetworkPostLateUpdate();
break;
}
}
private void OnNetworkEarlyUpdate()
{
if (!IsListening)
{
return;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportPoll.Begin();
#endif
NetworkEvent networkEvent;
do
{
networkEvent = NetworkConfig.NetworkTransport.PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime);
HandleRawTransportPoll(networkEvent, clientId, payload, receiveTime);
// Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum)
} while (IsListening && networkEvent != NetworkEvent.Nothing);
MessagingSystem.ProcessIncomingMessageQueue();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportPoll.End();
#endif
}
// TODO Once we have a way to subscribe to NetworkUpdateLoop with order we can move this out of NetworkManager but for now this needs to be here because we need strict ordering.
private void OnNetworkPreUpdate()
{
if (IsServer == false && IsConnectedClient == false)
{
// As a client wait to run the time system until we are connected.
return;
}
// Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.deltaTime);
if (reset)
{
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
}
NetworkTickSystem.UpdateTick(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
if (IsServer == false)
{
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.deltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
}
}
private void OnNetworkPostLateUpdate()
{
MessagingSystem.ProcessSendQueues();
NetworkMetrics.DispatchFrame();
SpawnManager.CleanupStaleTriggers();
}
/// <summary>
/// This function runs once whenever the local tick is incremented and is responsible for the following (in order):
/// - collect commands/inputs and send them to the server (TBD)
/// - call NetworkFixedUpdate on all NetworkBehaviours in prediction/client authority mode
/// - create a snapshot from resulting state
/// </summary>
private void OnNetworkManagerTick()
{
// Do NetworkVariable updates
BehaviourUpdater.NetworkBehaviourUpdate(this);
int timeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * NetworkConfig.TickRate);
if (IsServer && NetworkTickSystem.ServerTime.Tick % timeSyncFrequencyTicks == 0)
{
SyncTime();
}
}
private void SendConnectionRequest()
{
var message = new ConnectionRequestMessage
{
ConfigHash = NetworkConfig.GetConfig(),
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData
};
SendMessage(message, NetworkDelivery.ReliableSequenced, ServerClientId);
}
private IEnumerator ApprovalTimeout(ulong clientId)
{
NetworkTime timeStarted = LocalTime;
//We yield every frame incase a pending client disconnects and someone else gets its connection id
while ((LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId))
{
yield return null;
}
if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId))
{
// Timeout
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out");
}
DisconnectClient(clientId);
}
}
private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment<byte> payload, float receiveTime)
{
switch (networkEvent)
{
case NetworkEvent.Connect:
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportConnect.Begin();
#endif
MessagingSystem.ClientConnected(clientId);
if (IsServer)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo("Client Connected");
}
PendingClients.Add(clientId, new PendingClient()
{
ClientId = clientId,
ConnectionState = PendingClient.State.PendingConnection
});
StartCoroutine(ApprovalTimeout(clientId));
}
else
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo("Connected");
}
SendConnectionRequest();
StartCoroutine(ApprovalTimeout(clientId));
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportConnect.End();
#endif
break;
case NetworkEvent.Data:
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes");
}
HandleIncomingData(clientId, payload, receiveTime);
break;
}
case NetworkEvent.Disconnect:
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.Begin();
#endif
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo($"Disconnect Event From {clientId}");
}
if (IsServer)
{
OnClientDisconnectFromServer(clientId);
}
else
{
Shutdown();
}
OnClientDisconnectCallback?.Invoke(clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
break;
}
}
internal unsafe int SendMessage<TMessageType, TClientIdListType>(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
where TMessageType : INetworkMessage
where TClientIdListType : IReadOnlyList<ulong>
{
// Prevent server sending to itself
if (IsServer)
{
ulong* nonServerIds = stackalloc ulong[clientIds.Count];
int newIdx = 0;
for (int idx = 0; idx < clientIds.Count; ++idx)
{
if (clientIds[idx] == ServerClientId)
{
continue;
}
nonServerIds[newIdx++] = clientIds[idx];
}
if (newIdx == 0)
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx);
}
return MessagingSystem.SendMessage(message, delivery, clientIds);
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery,
ulong* clientIds, int numClientIds)
where T : INetworkMessage
{
// Prevent server sending to itself
if (IsServer)
{
ulong* nonServerIds = stackalloc ulong[numClientIds];
int newIdx = 0;
for (int idx = 0; idx < numClientIds; ++idx)
{
if (clientIds[idx] == ServerClientId)
{
continue;
}
nonServerIds[newIdx++] = clientIds[idx];
}
if (newIdx == 0)
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx);
}
return MessagingSystem.SendMessage(message, delivery, clientIds, numClientIds);
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
where T : INetworkMessage
{
return SendMessage(message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length);
}
internal int SendMessage<T>(in T message, NetworkDelivery delivery, ulong clientId)
where T : INetworkMessage
{
// Prevent server sending to itself
if (IsServer && clientId == ServerClientId)
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, clientId);
}
internal void HandleIncomingData(ulong clientId, ArraySegment<byte> payload, float receiveTime)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_HandleIncomingData.Begin();
#endif
MessagingSystem.HandleIncomingData(clientId, payload, receiveTime);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_HandleIncomingData.End();
#endif
}
/// <summary>
/// Disconnects the remote client.
/// </summary>
/// <param name="clientId">The ClientId to disconnect</param>
public void DisconnectClient(ulong clientId)
{
if (!IsServer)
{
throw new NotServerException("Only server can disconnect remote clients. Use StopClient instead.");
}
OnClientDisconnectFromServer(clientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(clientId);
}
private void OnClientDisconnectFromServer(ulong clientId)
{
PendingClients.Remove(clientId);
if (ConnectedClients.TryGetValue(clientId, out NetworkClient networkClient))
{
if (IsServer)
{
var playerObject = networkClient.PlayerObject;
if (playerObject != null)
{
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
{
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
}
else
{
Destroy(playerObject.gameObject);
}
}
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
{
var ownedObject = networkClient.OwnedObjects[i];
if (ownedObject != null)
{
if (!ownedObject.DontDestroyWithOwner)
{
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].OwnedObjects[i]
.GlobalObjectIdHash))
{
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].OwnedObjects[i]);
}
else
{
Destroy(ownedObject.gameObject);
}
}
else
{
ownedObject.RemoveOwnership();
}
}
}
// TODO: Could(should?) be replaced with more memory per client, by storing the visibility
foreach (var sobj in SpawnManager.SpawnedObjectsList)
{
sobj.Observers.Remove(clientId);
}
}
for (int i = 0; i < ConnectedClientsList.Count; i++)
{
if (ConnectedClientsList[i].ClientId == clientId)
{
m_ConnectedClientsList.RemoveAt(i);
break;
}
}
for (int i = 0; i < ConnectedClientsIds.Count; i++)
{
if (ConnectedClientsIds[i] == clientId)
{
m_ConnectedClientIds.RemoveAt(i);
break;
}
}
m_ConnectedClients.Remove(clientId);
}
MessagingSystem.ClientDisconnected(clientId);
}
private void SyncTime()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_SyncTime.Begin();
#endif
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo("Syncing Time To Clients");
}
var message = new TimeSyncMessage
{
Tick = NetworkTickSystem.ServerTime.Tick
};
SendMessage(message, NetworkDelivery.Unreliable, ConnectedClientsIds);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_SyncTime.End();
#endif
}
/// <summary>
/// Server Side: Handles the approval of a client
/// </summary>
/// <param name="ownerClientId">client being approved</param>
/// <param name="createPlayerObject">whether we want to create a player or not</param>
/// <param name="playerPrefabHash">the GlobalObjectIdHash value for the Network Prefab to create as the player</param>
/// <param name="approved">Is the player approved or not?</param>
/// <param name="position">Used when createPlayerObject is true, position of the player when spawned </param>
/// <param name="rotation">Used when createPlayerObject is true, rotation of the player when spawned</param>
internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation)
{
if (approved)
{
// Inform new client it got approved
PendingClients.Remove(ownerClientId);
var client = new NetworkClient { ClientId = ownerClientId, };
m_ConnectedClients.Add(ownerClientId, client);
m_ConnectedClientsList.Add(client);
m_ConnectedClientIds.Add(client.ClientId);
if (createPlayerObject)
{
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, null, position, rotation);
SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, false);
ConnectedClients[ownerClientId].PlayerObject = networkObject;
}
// Server doesn't send itself the connection approved message
if (ownerClientId != ServerClientId)
{
var message = new ConnectionApprovedMessage
{
OwnerClientId = ownerClientId,
NetworkTick = LocalTime.Tick
};
if (!NetworkConfig.EnableSceneManagement)
{
if (SpawnManager.SpawnedObjectsList.Count != 0)
{
message.SceneObjectCount = SpawnManager.SpawnedObjectsList.Count;
message.SpawnedObjectsList = SpawnManager.SpawnedObjectsList;
}
}
SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement)
{
InvokeOnClientConnectedCallback(ownerClientId);
}
else
{
SceneManager.SynchronizeNetworkObjects(ownerClientId);
}
}
else // Server just adds itself as an observer to all spawned NetworkObjects
{
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId);
}
if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
{
return;
}
// Separating this into a contained function call for potential further future separation of when this notification is sent.
ApprovedPlayerSpawn(ownerClientId, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
}
else
{
PendingClients.Remove(ownerClientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(ownerClientId);
}
}
/// <summary>
/// Spawns the newly approved player
/// </summary>
/// <param name="clientId">new player client identifier</param>
/// <param name="playerPrefabHash">the prefab GlobalObjectIdHash value for this player</param>
internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash)
{
foreach (var clientPair in ConnectedClients)
{
if (clientPair.Key == clientId ||
clientPair.Key == ServerClientId || // Server already spawned it
ConnectedClients[clientId].PlayerObject == null ||
!ConnectedClients[clientId].PlayerObject.Observers.Contains(clientPair.Key))
{
continue; //The new client.
}
var message = new CreateObjectMessage
{
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key, false)
};
message.ObjectInfo.Header.Hash = playerPrefabHash;
message.ObjectInfo.Header.IsSceneObject = false;
message.ObjectInfo.Header.HasParent = false;
message.ObjectInfo.Header.IsPlayerObject = true;
message.ObjectInfo.Header.OwnerClientId = clientId;
var size = SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
}
}
}
}