Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8ebf679ec | ||
|
|
07f206ff9e | ||
|
|
514166e159 | ||
|
|
ffef45b50f | ||
|
|
b3bd4727ab |
112
CHANGELOG.md
112
CHANGELOG.md
@@ -6,12 +6,122 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|||||||
|
|
||||||
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||||
|
|
||||||
## [1.5.2] - 2023-07-24
|
## [1.8.1] - 2024-02-05
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed a compile error when compiling for IL2CPP targets when using the new `[Rpc]` attribute. (#2824)
|
||||||
|
|
||||||
|
## [1.8.0] - 2023-12-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a new RPC attribute, which is simply `Rpc`. (#2762)
|
||||||
|
- This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs.
|
||||||
|
- This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately.
|
||||||
|
- This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option.
|
||||||
|
- Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762)
|
||||||
|
- Added `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762)
|
||||||
|
- Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735)
|
||||||
|
- Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735)
|
||||||
|
- Added `NetworkObject.InstantiateAndSpawn` and `NetworkSpawnManager.InstantiateAndSpawn` methods to simplify prefab spawning by assuring that the prefab is valid and applies any override prior to instantiating the `GameObject` and spawning the `NetworkObject` instance. (#2710)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789)
|
||||||
|
- Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789)
|
||||||
|
- Fixed issue where a host could disconnect its local client but remain running as a server. (#2789)
|
||||||
|
- Fixed issue where `OnClientDisconnectedCallback` was not being invoked under certain conditions. (#2789)
|
||||||
|
- Fixed issue where `OnClientDisconnectedCallback` was always returning 0 as the client identifier. (#2789)
|
||||||
|
- Fixed issue where if a host or server shutdown while a client owned NetworkObjects (other than the player) it would throw an exception. (#2789)
|
||||||
|
- Fixed issue where setting values on a `NetworkVariable` or `NetworkList` within `OnNetworkDespawn` during a shutdown sequence would throw an exception. (#2789)
|
||||||
|
- Fixed issue where a teleport state could potentially be overridden by a previous unreliable delta state. (#2777)
|
||||||
|
- Fixed issue where `NetworkTransform` was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777)
|
||||||
|
- Fixed issue where a parented in-scene placed NetworkObject would be destroyed upon a client or server exiting a network session but not unloading the original scene in which the NetworkObject was placed. (#2737)
|
||||||
|
- Fixed issue where during client synchronization and scene loading, when client synchronization or the scene loading mode are set to `LoadSceneMode.Single`, a `CreateObjectMessage` could be received, processed, and the resultant spawned `NetworkObject` could be instantiated in the client's currently active scene that could, towards the end of the client synchronization or loading process, be unloaded and cause the newly created `NetworkObject` to be destroyed (and throw and exception). (#2735)
|
||||||
|
- Fixed issue where a `NetworkTransform` instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713)
|
||||||
|
- Fixed issue where you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789)
|
||||||
|
- Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789)
|
||||||
|
- Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777)
|
||||||
|
- `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762)
|
||||||
|
- Changed `NetworkSceneManager` to return a `SceneEventProgress` status and not throw exceptions for methods invoked when scene management is disabled and when a client attempts to access a `NetworkSceneManager` method by a client. (#2735)
|
||||||
|
- Changed `NetworkTransform` authoritative instance tick registration so a single `NetworkTransform` specific tick event update will update all authoritative instances to improve perofmance. (#2713)
|
||||||
|
- Changed `NetworkPrefabs.OverrideToNetworkPrefab` dictionary is no longer used/populated due to it ending up being related to a regression bug and not allowing more than one override to be assigned to a network prefab asset. (#2710)
|
||||||
|
- Changed in-scene placed `NetworkObject`s now store their source network prefab asset's `GlobalObjectIdHash` internally that is used, when scene management is disabled, by clients to spawn the correct prefab even if the `NetworkPrefab` entry has an override. This does not impact dynamically spawning the same prefab which will yield the override on both host and client. (#2710)
|
||||||
|
- Changed in-scene placed `NetworkObject`s no longer require a `NetworkPrefab` entry with `GlobalObjectIdHash` override in order for clients to properly synchronize. (#2710)
|
||||||
|
- Changed in-scene placed `NetworkObject`s now set their `IsSceneObject` value when generating their `GlobalObjectIdHash` value. (#2710)
|
||||||
|
- Changed the default `NetworkConfig.SpawnTimeout` value from 1.0s to 10.0s. (#2710)
|
||||||
|
|
||||||
|
## [1.7.1] - 2023-11-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed a bug where having a class with Rpcs that inherits from a class without Rpcs that inherits from NetworkVariable would cause a compile error. (#2751)
|
||||||
|
- Fixed issue where `NetworkBehaviour.Synchronize` was not truncating the write buffer if nothing was serialized during `NetworkBehaviour.OnSynchronize` causing an additional 6 bytes to be written per `NetworkBehaviour` component instance. (#2749)
|
||||||
|
|
||||||
|
## [1.7.0] - 2023-10-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- exposed NetworkObject.GetNetworkBehaviourAtOrderIndex as a public API (#2724)
|
||||||
|
- Added context menu tool that provides users with the ability to quickly update the GlobalObjectIdHash value for all in-scene placed prefab instances that were created prior to adding a NetworkObject component to it. (#2707)
|
||||||
|
- Added methods NetworkManager.SetPeerMTU and NetworkManager.GetPeerMTU to be able to set MTU sizes per-peer (#2676)
|
||||||
|
- Added `GenerateSerializationForGenericParameterAttribute`, which can be applied to user-created Network Variable types to ensure the codegen generates serialization for the generic types they wrap. (#2694)
|
||||||
|
- Added `GenerateSerializationForTypeAttribute`, which can be applied to any class or method to ensure the codegen generates serialization for the specific provided type. (#2694)
|
||||||
|
- Exposed `NetworkVariableSerialization<T>.Read`, `NetworkVariableSerialization<T>.Write`, `NetworkVariableSerialization<T>.AreEqual`, and `NetworkVariableSerialization<T>.Duplicate` to further support the creation of user-created network variables by allowing users to access the generated serialization methods and serialize generic types efficiently without boxing. (#2694)
|
||||||
|
- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed issue where the server side `NetworkSceneManager` instance was not adding the currently active scene to its list of scenes loaded. (#2723)
|
||||||
|
- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720)
|
||||||
|
- Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720)
|
||||||
|
- Errors are no longer thrown when entering play mode with domain reload disabled (#2720)
|
||||||
|
- NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720)
|
||||||
|
- NetworkVariables of non-integer types will no longer break the inspector (#2714)
|
||||||
|
- NetworkVariables with NonSerializedAttribute will not appear in the inspector (#2714)
|
||||||
|
- Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695)
|
||||||
|
- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685)
|
||||||
|
- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682)
|
||||||
|
- Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674)
|
||||||
|
- Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670)
|
||||||
|
- Fixed issue where generation of the `DefaultNetworkPrefabs` asset was not enabled by default. (#2662)
|
||||||
|
- Fixed issue where the `GlobalObjectIdHash` value could be updated but the asset not marked as dirty. (#2662)
|
||||||
|
- Fixed issue where the `GlobalObjectIdHash` value of a (network) prefab asset could be assigned an incorrect value when editing the prefab in a temporary scene. (#2662)
|
||||||
|
- Fixed issue where the `GlobalObjectIdHash` value generated after creating a (network) prefab from an object constructed within the scene would not be the correct final value in a stand alone build. (#2662)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated dependency on `com.unity.transport` to version 1.4.0. (#2716)
|
||||||
|
|
||||||
|
## [1.6.0] - 2023-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a protected virtual method `NetworkTransform.OnInitialize(ref NetworkTransformState replicatedState)` that just returns the replicated state reference.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661)
|
||||||
|
- Fixed issue where ARMv7 Android builds would crash when trying to validate the batch header. (#2654)
|
||||||
|
- Fixed issue with client synchronization of position when using half precision and the delta position reaches the maximum value and is collapsed on the host prior to being forwarded to the non-owner clients. (#2636)
|
||||||
|
- Fixed issue with scale not synchronizing properly depending upon the spawn order of NetworkObjects. (#2636)
|
||||||
|
- Fixed issue position was not properly transitioning between ownership changes with an owner authoritative NetworkTransform. (#2636)
|
||||||
|
- Fixed issue where a late joining non-owner client could update an owner authoritative NetworkTransform if ownership changed without any updates to position prior to the non-owner client joining. (#2636)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
## [1.5.2] - 2023-07-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bumped minimum Unity version supported to 2021.3 LTS
|
||||||
- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)
|
- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)
|
||||||
- Fixed a crash when calling TrySetParent with a null Transform (#2625)
|
- Fixed a crash when calling TrySetParent with a null Transform (#2625)
|
||||||
- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)
|
- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Unity.Netcode.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The half float precision value of the z-axis as a <see cref="half"/>.
|
/// The half float precision value of the z-axis as a <see cref="half"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public half Z => Axis.x;
|
public half Z => Axis.z;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to store the half float precision values as a <see cref="half3"/>
|
/// Used to store the half float precision values as a <see cref="half3"/>
|
||||||
@@ -39,6 +39,17 @@ namespace Unity.Netcode.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool3 AxisToSynchronize;
|
public bool3 AxisToSynchronize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Directly sets each axial value to the passed in full precision values
|
||||||
|
/// that are converted to half precision
|
||||||
|
/// </summary>
|
||||||
|
internal void Set(float x, float y, float z)
|
||||||
|
{
|
||||||
|
Axis.x = math.half(x);
|
||||||
|
Axis.y = math.half(y);
|
||||||
|
Axis.z = math.half(z);
|
||||||
|
}
|
||||||
|
|
||||||
private void SerializeWrite(FastBufferWriter writer)
|
private void SerializeWrite(FastBufferWriter writer)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Length; i++)
|
for (int i = 0; i < Length; i++)
|
||||||
|
|||||||
@@ -833,8 +833,7 @@ namespace Unity.Netcode.Components
|
|||||||
stateChangeDetected = true;
|
stateChangeDetected = true;
|
||||||
//Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
|
//Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
|
||||||
}
|
}
|
||||||
else
|
else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer])
|
||||||
if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer])
|
|
||||||
{
|
{
|
||||||
// first time in this transition for this layer
|
// first time in this transition for this layer
|
||||||
m_TransitionHash[layer] = tt.fullPathHash;
|
m_TransitionHash[layer] = tt.fullPathHash;
|
||||||
@@ -1053,8 +1052,7 @@ namespace Unity.Netcode.Components
|
|||||||
BytePacker.WriteValuePacked(writer, valueBool);
|
BytePacker.WriteValuePacked(writer, valueBool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
||||||
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
|
||||||
{
|
{
|
||||||
var valueFloat = m_Animator.GetFloat(hash);
|
var valueFloat = m_Animator.GetFloat(hash);
|
||||||
fixed (void* value = cacheValue.Value)
|
fixed (void* value = cacheValue.Value)
|
||||||
@@ -1137,6 +1135,7 @@ namespace Unity.Netcode.Components
|
|||||||
if (m_LayerWeights[animationState.Layer] != animationState.Weight)
|
if (m_LayerWeights[animationState.Layer] != animationState.Weight)
|
||||||
{
|
{
|
||||||
m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
|
m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
|
||||||
|
m_LayerWeights[animationState.Layer] = animationState.Weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,20 @@ namespace Unity.Netcode.Components
|
|||||||
internal Vector3 DeltaPosition;
|
internal Vector3 DeltaPosition;
|
||||||
internal int NetworkTick;
|
internal int NetworkTick;
|
||||||
|
|
||||||
|
internal bool SynchronizeBase;
|
||||||
|
|
||||||
|
internal bool CollapsedDeltaIntoBase;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The serialization implementation of <see cref="INetworkSerializable"/>
|
/// The serialization implementation of <see cref="INetworkSerializable"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
{
|
{
|
||||||
HalfVector3.NetworkSerialize(serializer);
|
HalfVector3.NetworkSerialize(serializer);
|
||||||
|
if (SynchronizeBase)
|
||||||
|
{
|
||||||
|
serializer.SerializeValue(ref CurrentBasePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -122,6 +130,7 @@ namespace Unity.Netcode.Components
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void UpdateFrom(ref Vector3 vector3, int networkTick)
|
public void UpdateFrom(ref Vector3 vector3, int networkTick)
|
||||||
{
|
{
|
||||||
|
CollapsedDeltaIntoBase = false;
|
||||||
NetworkTick = networkTick;
|
NetworkTick = networkTick;
|
||||||
DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition;
|
DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition;
|
||||||
for (int i = 0; i < HalfVector3.Length; i++)
|
for (int i = 0; i < HalfVector3.Length; i++)
|
||||||
@@ -136,6 +145,7 @@ namespace Unity.Netcode.Components
|
|||||||
CurrentBasePosition[i] += HalfDeltaConvertedBack[i];
|
CurrentBasePosition[i] += HalfDeltaConvertedBack[i];
|
||||||
HalfDeltaConvertedBack[i] = 0.0f;
|
HalfDeltaConvertedBack[i] = 0.0f;
|
||||||
DeltaPosition[i] = 0.0f;
|
DeltaPosition[i] = 0.0f;
|
||||||
|
CollapsedDeltaIntoBase = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,6 +174,8 @@ namespace Unity.Netcode.Components
|
|||||||
DeltaPosition = Vector3.zero;
|
DeltaPosition = Vector3.zero;
|
||||||
HalfDeltaConvertedBack = Vector3.zero;
|
HalfDeltaConvertedBack = Vector3.zero;
|
||||||
HalfVector3 = new HalfVector3(vector3, axisToSynchronize);
|
HalfVector3 = new HalfVector3(vector3, axisToSynchronize);
|
||||||
|
SynchronizeBase = false;
|
||||||
|
CollapsedDeltaIntoBase = false;
|
||||||
UpdateFrom(ref vector3, networkTick);
|
UpdateFrom(ref vector3, networkTick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,14 @@ See guides below to install Unity Netcode for GameObjects, set up your project,
|
|||||||
- [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about)
|
- [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about)
|
||||||
- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/installation)
|
- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/installation)
|
||||||
- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/get-started-ngo)
|
- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/get-started-ngo)
|
||||||
- [API Reference](https://docs-multiplayer.unity3d.com/netcode/current/api/introduction)
|
- [API Reference](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.6/api/index.html)
|
||||||
|
|
||||||
# Technical details
|
# Technical details
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Netcode for GameObjects targets the following Unity versions:
|
Netcode for GameObjects targets the following Unity versions:
|
||||||
- Unity 2020.3, 2021.1, 2021.2 and 2021.3
|
- Unity 2021.3 (LTS), 2022.3 (LTS) and 2023.2
|
||||||
|
|
||||||
On the following runtime platforms:
|
On the following runtime platforms:
|
||||||
- Windows, MacOS, and Linux
|
- Windows, MacOS, and Linux
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName;
|
public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName;
|
||||||
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
|
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
|
||||||
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
|
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
|
||||||
|
public static readonly string RpcAttribute_FullName = typeof(RpcAttribute).FullName;
|
||||||
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
|
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
|
||||||
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
|
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
|
||||||
|
public static readonly string RpcParams_FullName = typeof(RpcParams).FullName;
|
||||||
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
|
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
|
||||||
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
|
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
|
||||||
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
|
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Mono.Cecil.Cil;
|
|||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||||
|
using UnityEngine;
|
||||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||||
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
||||||
|
|
||||||
@@ -101,6 +102,8 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
private ModuleDefinition m_NetcodeModule;
|
private ModuleDefinition m_NetcodeModule;
|
||||||
private PostProcessorAssemblyResolver m_AssemblyResolver;
|
private PostProcessorAssemblyResolver m_AssemblyResolver;
|
||||||
|
|
||||||
|
private MethodReference m_RuntimeInitializeOnLoadAttribute_Ctor;
|
||||||
|
|
||||||
private MethodReference m_MessageManager_ReceiveMessage_MethodRef;
|
private MethodReference m_MessageManager_ReceiveMessage_MethodRef;
|
||||||
private MethodReference m_MessageManager_CreateMessageAndGetVersion_MethodRef;
|
private MethodReference m_MessageManager_CreateMessageAndGetVersion_MethodRef;
|
||||||
private TypeReference m_MessageManager_MessageWithHandler_TypeRef;
|
private TypeReference m_MessageManager_MessageWithHandler_TypeRef;
|
||||||
@@ -125,6 +128,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
// (i.e., there's no #if UNITY_EDITOR in them that could create invalid IL code)
|
// (i.e., there's no #if UNITY_EDITOR in them that could create invalid IL code)
|
||||||
TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve();
|
TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve();
|
||||||
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
|
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
|
||||||
|
m_RuntimeInitializeOnLoadAttribute_Ctor = moduleDefinition.ImportReference(typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new Type[] { }));
|
||||||
|
|
||||||
TypeDefinition messageHandlerTypeDef = null;
|
TypeDefinition messageHandlerTypeDef = null;
|
||||||
TypeDefinition versionGetterTypeDef = null;
|
TypeDefinition versionGetterTypeDef = null;
|
||||||
@@ -232,25 +236,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
|
||||||
{
|
|
||||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
|
||||||
if (staticCtorMethodDef == null)
|
|
||||||
{
|
|
||||||
staticCtorMethodDef = new MethodDefinition(
|
|
||||||
".cctor", // Static Constructor (constant-constructor)
|
|
||||||
MethodAttributes.HideBySig |
|
|
||||||
MethodAttributes.SpecialName |
|
|
||||||
MethodAttributes.RTSpecialName |
|
|
||||||
MethodAttributes.Static,
|
|
||||||
typeDefinition.Module.TypeSystem.Void);
|
|
||||||
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
|
||||||
typeDefinition.Methods.Add(staticCtorMethodDef);
|
|
||||||
}
|
|
||||||
|
|
||||||
return staticCtorMethodDef;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateInstructionsToRegisterType(ILProcessor processor, List<Instruction> instructions, TypeReference type, MethodReference receiveMethod, MethodReference versionMethod)
|
private void CreateInstructionsToRegisterType(ILProcessor processor, List<Instruction> instructions, TypeReference type, MethodReference receiveMethod, MethodReference versionMethod)
|
||||||
{
|
{
|
||||||
// NetworkMessageManager.__network_message_types.Add(new NetworkMessageManager.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
|
// NetworkMessageManager.__network_message_types.Add(new NetworkMessageManager.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
|
||||||
@@ -295,29 +280,32 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
|
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
|
||||||
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkMessageTypes)
|
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkMessageTypes)
|
||||||
{
|
{
|
||||||
foreach (var typeDefinition in assembly.MainModule.Types)
|
var typeDefinition = new TypeDefinition("__GEN", "INetworkMessageHelper", TypeAttributes.NotPublic | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, assembly.MainModule.TypeSystem.Object);
|
||||||
|
|
||||||
|
var staticCtorMethodDef = new MethodDefinition(
|
||||||
|
$"InitializeMessages",
|
||||||
|
MethodAttributes.Assembly |
|
||||||
|
MethodAttributes.Static,
|
||||||
|
assembly.MainModule.TypeSystem.Void);
|
||||||
|
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||||
|
staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_RuntimeInitializeOnLoadAttribute_Ctor));
|
||||||
|
typeDefinition.Methods.Add(staticCtorMethodDef);
|
||||||
|
|
||||||
|
var instructions = new List<Instruction>();
|
||||||
|
var processor = staticCtorMethodDef.Body.GetILProcessor();
|
||||||
|
|
||||||
|
foreach (var type in networkMessageTypes)
|
||||||
{
|
{
|
||||||
if (typeDefinition.FullName == "<Module>")
|
var receiveMethod = new GenericInstanceMethod(m_MessageManager_ReceiveMessage_MethodRef);
|
||||||
{
|
receiveMethod.GenericArguments.Add(type);
|
||||||
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
|
var versionMethod = new GenericInstanceMethod(m_MessageManager_CreateMessageAndGetVersion_MethodRef);
|
||||||
|
versionMethod.GenericArguments.Add(type);
|
||||||
var processor = staticCtorMethodDef.Body.GetILProcessor();
|
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod, versionMethod);
|
||||||
|
|
||||||
var instructions = new List<Instruction>();
|
|
||||||
|
|
||||||
foreach (var type in networkMessageTypes)
|
|
||||||
{
|
|
||||||
var receiveMethod = new GenericInstanceMethod(m_MessageManager_ReceiveMessage_MethodRef);
|
|
||||||
receiveMethod.GenericArguments.Add(type);
|
|
||||||
var versionMethod = new GenericInstanceMethod(m_MessageManager_CreateMessageAndGetVersion_MethodRef);
|
|
||||||
versionMethod.GenericArguments.Add(type);
|
|
||||||
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod, versionMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||||
|
|
||||||
|
assembly.MainModule.Types.Add(typeDefinition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
|
using Mono.Cecil.Rocks;
|
||||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||||
@@ -52,7 +53,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
case nameof(NetworkBehaviour):
|
case nameof(NetworkBehaviour):
|
||||||
ProcessNetworkBehaviour(typeDefinition);
|
ProcessNetworkBehaviour(typeDefinition);
|
||||||
break;
|
break;
|
||||||
|
case nameof(RpcAttribute):
|
||||||
|
foreach (var methodDefinition in typeDefinition.GetConstructors())
|
||||||
|
{
|
||||||
|
if (methodDefinition.Parameters.Count == 0)
|
||||||
|
{
|
||||||
|
methodDefinition.IsPublic = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case nameof(__RpcParams):
|
case nameof(__RpcParams):
|
||||||
|
case nameof(RpcFallbackSerialization):
|
||||||
typeDefinition.IsPublic = true;
|
typeDefinition.IsPublic = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -79,6 +90,9 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Deprecate...
|
||||||
|
// This is changing accessibility for values that are no longer used, but since our validator runs
|
||||||
|
// after ILPP and sees those values as public, they cannot be removed until a major version change.
|
||||||
private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines)
|
private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines)
|
||||||
{
|
{
|
||||||
foreach (var fieldDefinition in typeDefinition.Fields)
|
foreach (var fieldDefinition in typeDefinition.Fields)
|
||||||
@@ -116,6 +130,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
{
|
{
|
||||||
nestedType.IsNestedFamily = true;
|
nestedType.IsNestedFamily = true;
|
||||||
}
|
}
|
||||||
|
if (nestedType.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
|
||||||
|
{
|
||||||
|
nestedType.IsNestedPublic = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var fieldDefinition in typeDefinition.Fields)
|
foreach (var fieldDefinition in typeDefinition.Fields)
|
||||||
@@ -124,6 +142,20 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
{
|
{
|
||||||
fieldDefinition.IsFamilyOrAssembly = true;
|
fieldDefinition.IsFamilyOrAssembly = true;
|
||||||
}
|
}
|
||||||
|
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_func_table))
|
||||||
|
{
|
||||||
|
fieldDefinition.IsFamilyOrAssembly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldDefinition.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
|
||||||
|
{
|
||||||
|
fieldDefinition.IsFamilyOrAssembly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table))
|
||||||
|
{
|
||||||
|
fieldDefinition.IsFamilyOrAssembly = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var methodDefinition in typeDefinition.Methods)
|
foreach (var methodDefinition in typeDefinition.Methods)
|
||||||
@@ -132,7 +164,11 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
|
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
|
||||||
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
|
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
|
||||||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) ||
|
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) ||
|
||||||
|
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendRpc) ||
|
||||||
|
methodDefinition.Name == nameof(NetworkBehaviour.__endSendRpc) ||
|
||||||
methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) ||
|
methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) ||
|
||||||
|
methodDefinition.Name == nameof(NetworkBehaviour.__initializeRpcs) ||
|
||||||
|
methodDefinition.Name == nameof(NetworkBehaviour.__registerRpc) ||
|
||||||
methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) ||
|
methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) ||
|
||||||
methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList))
|
methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Unity.Netcode.Editor.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
public bool GenerateDefaultNetworkPrefabs;
|
public bool GenerateDefaultNetworkPrefabs = true;
|
||||||
|
|
||||||
internal void SaveSettings()
|
internal void SaveSettings()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ namespace Unity.Netcode.Editor
|
|||||||
for (int i = 0; i < fields.Length; i++)
|
for (int i = 0; i < fields.Length; i++)
|
||||||
{
|
{
|
||||||
var ft = fields[i].FieldType;
|
var ft = fields[i].FieldType;
|
||||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
|
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true) && !fields[i].IsDefined(typeof(NonSerializedAttribute), true))
|
||||||
{
|
{
|
||||||
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
|
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
|
||||||
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
|
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
|
||||||
}
|
}
|
||||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
|
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true) && !fields[i].IsDefined(typeof(NonSerializedAttribute), true))
|
||||||
{
|
{
|
||||||
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
|
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
|
||||||
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
|
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
|
||||||
@@ -345,8 +345,9 @@ namespace Unity.Netcode.Editor
|
|||||||
/// <param name="networkObjectRemoved">used internally</param>
|
/// <param name="networkObjectRemoved">used internally</param>
|
||||||
public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false)
|
public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false)
|
||||||
{
|
{
|
||||||
// If there are no NetworkBehaviours or no gameObject, then exit early
|
// If there are no NetworkBehaviours or gameObjects then exit early
|
||||||
if (gameObject == null || (gameObject.GetComponent<NetworkBehaviour>() == null && gameObject.GetComponentInChildren<NetworkBehaviour>() == null))
|
// If we are in play mode and a user is inspecting something then exit early (we don't add NetworkObjects to something when in play mode)
|
||||||
|
if (EditorApplication.isPlaying || gameObject == null || (gameObject.GetComponent<NetworkBehaviour>() == null && gameObject.GetComponentInChildren<NetworkBehaviour>() == null))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -416,6 +417,48 @@ namespace Unity.Netcode.Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (networkObject != null)
|
||||||
|
{
|
||||||
|
OrderNetworkObject(networkObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assures the NetworkObject precedes any NetworkBehaviour on the same GameObject as the NetworkObject
|
||||||
|
private static void OrderNetworkObject(NetworkObject networkObject)
|
||||||
|
{
|
||||||
|
var monoBehaviours = networkObject.gameObject.GetComponents<MonoBehaviour>();
|
||||||
|
var networkObjectIndex = 0;
|
||||||
|
var firstNetworkBehaviourIndex = -1;
|
||||||
|
for (int i = 0; i < monoBehaviours.Length; i++)
|
||||||
|
{
|
||||||
|
if (monoBehaviours[i] == networkObject)
|
||||||
|
{
|
||||||
|
networkObjectIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkBehaviour = monoBehaviours[i] as NetworkBehaviour;
|
||||||
|
if (networkBehaviour != null)
|
||||||
|
{
|
||||||
|
// Get the index of the first NetworkBehaviour Component
|
||||||
|
if (firstNetworkBehaviourIndex == -1)
|
||||||
|
{
|
||||||
|
firstNetworkBehaviourIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstNetworkBehaviourIndex != -1 && networkObjectIndex > firstNetworkBehaviourIndex)
|
||||||
|
{
|
||||||
|
var positionsToMove = networkObjectIndex - firstNetworkBehaviourIndex;
|
||||||
|
for (int i = 0; i < positionsToMove; i++)
|
||||||
|
{
|
||||||
|
UnityEditorInternal.ComponentUtility.MoveComponentUp(networkObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorUtility.SetDirty(networkObject.gameObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#if UNITY_2022_3_OR_NEWER && (RELAY_SDK_INSTALLED && !UNITY_WEBGL ) || (RELAY_SDK_INSTALLED && UNITY_WEBGL && UTP_TRANSPORT_2_0_ABOVE)
|
||||||
|
#define RELAY_INTEGRATION_AVAILABLE
|
||||||
|
#endif
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -48,6 +51,27 @@ namespace Unity.Netcode.Editor
|
|||||||
private readonly List<Type> m_TransportTypes = new List<Type>();
|
private readonly List<Type> m_TransportTypes = new List<Type>();
|
||||||
private string[] m_TransportNames = { "Select transport..." };
|
private string[] m_TransportNames = { "Select transport..." };
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
CheckNullProperties();
|
||||||
|
|
||||||
|
#if !MULTIPLAYER_TOOLS
|
||||||
|
DrawInstallMultiplayerToolsTip();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (m_NetworkManager.IsServer || m_NetworkManager.IsClient)
|
||||||
|
{
|
||||||
|
DrawDisconnectButton();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawAllPropertyFields();
|
||||||
|
ShowStartConnectionButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ReloadTransports()
|
private void ReloadTransports()
|
||||||
{
|
{
|
||||||
m_TransportTypes.Clear();
|
m_TransportTypes.Clear();
|
||||||
@@ -138,209 +162,311 @@ namespace Unity.Netcode.Editor
|
|||||||
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
|
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
private void DrawAllPropertyFields()
|
||||||
public override void OnInspectorGUI()
|
|
||||||
{
|
{
|
||||||
Initialize();
|
serializedObject.Update();
|
||||||
CheckNullProperties();
|
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||||
|
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
#if !MULTIPLAYER_TOOLS
|
EditorGUILayout.PropertyField(m_PlayerPrefabProperty);
|
||||||
DrawInstallMultiplayerToolsTip();
|
EditorGUILayout.Space();
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
DrawPrefabListField();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.PropertyField(m_ProtocolVersionProperty);
|
||||||
|
|
||||||
|
DrawTransportField();
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(m_TickRateProperty);
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty);
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("Connection", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.PropertyField(m_ConnectionApprovalProperty);
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.ConnectionApproval))
|
||||||
{
|
{
|
||||||
serializedObject.Update();
|
EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty);
|
||||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
|
||||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_PlayerPrefabProperty);
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
|
|
||||||
if (m_NetworkManager.NetworkConfig.HasOldPrefabList())
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
|
|
||||||
if (GUILayout.Button(new GUIContent("Migrate Prefab List", "Converts the old format Network Prefab list to a new Scriptable Object")))
|
|
||||||
{
|
|
||||||
// Default directory
|
|
||||||
var directory = "Assets/";
|
|
||||||
var assetPath = AssetDatabase.GetAssetPath(m_NetworkManager);
|
|
||||||
if (assetPath == "")
|
|
||||||
{
|
|
||||||
assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(m_NetworkManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assetPath != "")
|
|
||||||
{
|
|
||||||
directory = Path.GetDirectoryName(assetPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#if UNITY_2021_1_OR_NEWER
|
|
||||||
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
|
|
||||||
#else
|
|
||||||
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
|
|
||||||
#endif
|
|
||||||
if (prefabStage != null)
|
|
||||||
{
|
|
||||||
var prefabPath = prefabStage.assetPath;
|
|
||||||
if (!string.IsNullOrEmpty(prefabPath))
|
|
||||||
{
|
|
||||||
directory = Path.GetDirectoryName(prefabPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_NetworkManager.gameObject.scene != null)
|
|
||||||
{
|
|
||||||
var scenePath = m_NetworkManager.gameObject.scene.path;
|
|
||||||
if (!string.IsNullOrEmpty(scenePath))
|
|
||||||
{
|
|
||||||
directory = Path.GetDirectoryName(scenePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var networkPrefabs = m_NetworkManager.NetworkConfig.MigrateOldNetworkPrefabsToNetworkPrefabsList();
|
|
||||||
string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset");
|
|
||||||
Debug.Log("Saving migrated Network Prefabs List to " + path);
|
|
||||||
AssetDatabase.CreateAsset(networkPrefabs, path);
|
|
||||||
EditorUtility.SetDirty(m_NetworkManager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 0)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning);
|
|
||||||
}
|
|
||||||
EditorGUILayout.PropertyField(m_PrefabsList);
|
|
||||||
}
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.PropertyField(m_ProtocolVersionProperty);
|
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_NetworkTransportProperty);
|
|
||||||
|
|
||||||
if (m_NetworkTransportProperty.objectReferenceValue == null)
|
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("You have no transport selected. A transport is required for netcode to work. Which one do you want?", MessageType.Warning);
|
|
||||||
|
|
||||||
int selection = EditorGUILayout.Popup(0, m_TransportNames);
|
|
||||||
|
|
||||||
if (selection > 0)
|
|
||||||
{
|
|
||||||
ReloadTransports();
|
|
||||||
|
|
||||||
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
|
|
||||||
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
|
|
||||||
|
|
||||||
Repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_TickRateProperty);
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel);
|
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty);
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Connection", EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.PropertyField(m_ConnectionApprovalProperty);
|
|
||||||
|
|
||||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.ConnectionApproval))
|
|
||||||
{
|
|
||||||
EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Spawning", EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty);
|
|
||||||
|
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty);
|
|
||||||
|
|
||||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.RecycleNetworkIds))
|
|
||||||
{
|
|
||||||
EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.PropertyField(m_RpcHashSizeProperty);
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Scene Management", EditorStyles.boldLabel);
|
|
||||||
EditorGUILayout.PropertyField(m_EnableSceneManagementProperty);
|
|
||||||
|
|
||||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement))
|
|
||||||
{
|
|
||||||
EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
|
|
||||||
|
|
||||||
// Start buttons below
|
|
||||||
{
|
|
||||||
string buttonDisabledReasonSuffix = "";
|
|
||||||
|
|
||||||
if (!EditorApplication.isPlaying)
|
|
||||||
{
|
|
||||||
buttonDisabledReasonSuffix = ". This can only be done in play mode";
|
|
||||||
GUI.enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix)))
|
|
||||||
{
|
|
||||||
m_NetworkManager.StartHost();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix)))
|
|
||||||
{
|
|
||||||
m_NetworkManager.StartServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix)))
|
|
||||||
{
|
|
||||||
m_NetworkManager.StartClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EditorApplication.isPlaying)
|
|
||||||
{
|
|
||||||
GUI.enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
EditorGUILayout.LabelField("Spawning", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty);
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty);
|
||||||
|
|
||||||
|
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.RecycleNetworkIds))
|
||||||
{
|
{
|
||||||
string instanceType = string.Empty;
|
EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_NetworkManager.IsHost)
|
EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel);
|
||||||
{
|
EditorGUILayout.PropertyField(m_RpcHashSizeProperty);
|
||||||
instanceType = "Host";
|
|
||||||
}
|
|
||||||
else if (m_NetworkManager.IsServer)
|
|
||||||
{
|
|
||||||
instanceType = "Server";
|
|
||||||
}
|
|
||||||
else if (m_NetworkManager.IsClient)
|
|
||||||
{
|
|
||||||
instanceType = "Client";
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.HelpBox("You cannot edit the NetworkConfig when a " + instanceType + " is running.", MessageType.Info);
|
EditorGUILayout.LabelField("Scene Management", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.PropertyField(m_EnableSceneManagementProperty);
|
||||||
|
|
||||||
if (GUILayout.Button(new GUIContent("Stop " + instanceType, "Stops the " + instanceType + " instance.")))
|
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement))
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTransportField()
|
||||||
|
{
|
||||||
|
#if RELAY_INTEGRATION_AVAILABLE
|
||||||
|
var useRelay = EditorPrefs.GetBool(k_UseEasyRelayIntegrationKey, false);
|
||||||
|
#else
|
||||||
|
var useRelay = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (useRelay)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Test connection with relay is enabled, so the default Unity Transport will be used", MessageType.Info);
|
||||||
|
GUI.enabled = false;
|
||||||
|
EditorGUILayout.PropertyField(m_NetworkTransportProperty);
|
||||||
|
GUI.enabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(m_NetworkTransportProperty);
|
||||||
|
|
||||||
|
if (m_NetworkTransportProperty.objectReferenceValue == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("You have no transport selected. A transport is required for netcode to work. Which one do you want?", MessageType.Warning);
|
||||||
|
|
||||||
|
int selection = EditorGUILayout.Popup(0, m_TransportNames);
|
||||||
|
|
||||||
|
if (selection > 0)
|
||||||
{
|
{
|
||||||
m_NetworkManager.Shutdown();
|
ReloadTransports();
|
||||||
|
|
||||||
|
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
|
||||||
|
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
|
||||||
|
|
||||||
|
Repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if RELAY_INTEGRATION_AVAILABLE
|
||||||
|
private readonly string k_UseEasyRelayIntegrationKey = "NetworkManagerUI_UseRelay_" + Application.dataPath.GetHashCode();
|
||||||
|
private string m_JoinCode = "";
|
||||||
|
private string m_StartConnectionError = null;
|
||||||
|
private string m_Region = "";
|
||||||
|
|
||||||
|
// wait for next frame so that ImGui finishes the current frame
|
||||||
|
private static void RunNextFrame(Action action) => EditorApplication.delayCall += () => action();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private void ShowStartConnectionButtons()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Start Connection", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
#if RELAY_INTEGRATION_AVAILABLE
|
||||||
|
// use editor prefs to persist the setting when entering / leaving play mode / exiting Unity
|
||||||
|
var useRelay = EditorPrefs.GetBool(k_UseEasyRelayIntegrationKey, false);
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
useRelay = GUILayout.Toggle(useRelay, "Try Relay in the Editor");
|
||||||
|
|
||||||
|
var icon = EditorGUIUtility.IconContent("_Help");
|
||||||
|
icon.tooltip = "This will help you test relay in the Editor. Click here to know how to integrate Relay in your build";
|
||||||
|
if (GUILayout.Button(icon, GUIStyle.none, GUILayout.Width(20)))
|
||||||
|
{
|
||||||
|
Application.OpenURL("https://docs-multiplayer.unity3d.com/netcode/current/relay/");
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorPrefs.SetBool(k_UseEasyRelayIntegrationKey, useRelay);
|
||||||
|
if (useRelay && !Application.isPlaying && !CloudProjectSettings.projectBound)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("To use relay, you need to setup your project in the Project Settings in the Services section.", MessageType.Warning);
|
||||||
|
if (GUILayout.Button("Open Project settings"))
|
||||||
|
{
|
||||||
|
SettingsService.OpenProjectSettings("Project/Services");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
var useRelay = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
string buttonDisabledReasonSuffix = "";
|
||||||
|
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
buttonDisabledReasonSuffix = ". This can only be done in play mode";
|
||||||
|
GUI.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useRelay)
|
||||||
|
{
|
||||||
|
ShowStartConnectionButtons_Relay(buttonDisabledReasonSuffix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowStartConnectionButtons_Standard(buttonDisabledReasonSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
GUI.enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowStartConnectionButtons_Relay(string buttonDisabledReasonSuffix)
|
||||||
|
{
|
||||||
|
#if RELAY_INTEGRATION_AVAILABLE
|
||||||
|
|
||||||
|
void AddStartServerOrHostButton(bool isServer)
|
||||||
|
{
|
||||||
|
var type = isServer ? "Server" : "Host";
|
||||||
|
if (GUILayout.Button(new GUIContent($"Start {type}", $"Starts a {type} instance with Relay{buttonDisabledReasonSuffix}")))
|
||||||
|
{
|
||||||
|
m_StartConnectionError = null;
|
||||||
|
RunNextFrame(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (joinCode, allocation) = isServer ? await m_NetworkManager.StartServerWithRelay() : await m_NetworkManager.StartHostWithRelay();
|
||||||
|
m_JoinCode = joinCode;
|
||||||
|
m_Region = allocation.Region;
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
m_StartConnectionError = e.Message;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddStartServerOrHostButton(isServer: true);
|
||||||
|
AddStartServerOrHostButton(isServer: false);
|
||||||
|
|
||||||
|
GUILayout.Space(8f);
|
||||||
|
m_JoinCode = EditorGUILayout.TextField("Relay Join Code", m_JoinCode);
|
||||||
|
if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance with Relay" + buttonDisabledReasonSuffix)))
|
||||||
|
{
|
||||||
|
m_StartConnectionError = null;
|
||||||
|
RunNextFrame(async () =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(m_JoinCode))
|
||||||
|
{
|
||||||
|
m_StartConnectionError = "Please provide a join code!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allocation = await m_NetworkManager.StartClientWithRelay(m_JoinCode);
|
||||||
|
m_Region = allocation.Region;
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
m_StartConnectionError = e.Message;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Application.isPlaying && !string.IsNullOrEmpty(m_StartConnectionError))
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox(m_StartConnectionError, MessageType.Error);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowStartConnectionButtons_Standard(string buttonDisabledReasonSuffix)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix)))
|
||||||
|
{
|
||||||
|
m_NetworkManager.StartHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix)))
|
||||||
|
{
|
||||||
|
m_NetworkManager.StartServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix)))
|
||||||
|
{
|
||||||
|
m_NetworkManager.StartClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDisconnectButton()
|
||||||
|
{
|
||||||
|
string instanceType = string.Empty;
|
||||||
|
|
||||||
|
if (m_NetworkManager.IsHost)
|
||||||
|
{
|
||||||
|
instanceType = "Host";
|
||||||
|
}
|
||||||
|
else if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
instanceType = "Server";
|
||||||
|
}
|
||||||
|
else if (m_NetworkManager.IsClient)
|
||||||
|
{
|
||||||
|
instanceType = "Client";
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox($"You cannot edit the NetworkConfig when a {instanceType} is running.", MessageType.Info);
|
||||||
|
|
||||||
|
#if RELAY_INTEGRATION_AVAILABLE
|
||||||
|
if (!string.IsNullOrEmpty(m_JoinCode) && !string.IsNullOrEmpty(m_Region))
|
||||||
|
{
|
||||||
|
var style = new GUIStyle(EditorStyles.helpBox)
|
||||||
|
{
|
||||||
|
fontSize = 10,
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
};
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(style, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false), GUILayout.MaxWidth(800));
|
||||||
|
GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(k_InfoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true));
|
||||||
|
GUILayout.Space(25f);
|
||||||
|
GUILayout.BeginVertical();
|
||||||
|
GUILayout.Space(4f);
|
||||||
|
GUILayout.Label($"Connected via relay ({m_Region}).\nJoin code: {m_JoinCode}", EditorStyles.miniLabel, GUILayout.ExpandHeight(true));
|
||||||
|
|
||||||
|
if (GUILayout.Button("Copy code", GUILayout.ExpandHeight(true)))
|
||||||
|
{
|
||||||
|
GUIUtility.systemCopyBuffer = m_JoinCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Space(4f);
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
GUILayout.Space(2f);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (GUILayout.Button(new GUIContent($"Stop {instanceType}", $"Stops the {instanceType} instance.")))
|
||||||
|
{
|
||||||
|
#if RELAY_INTEGRATION_AVAILABLE
|
||||||
|
m_JoinCode = "";
|
||||||
|
#endif
|
||||||
|
m_NetworkManager.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string k_InfoIconName = "console.infoicon";
|
||||||
private static void DrawInstallMultiplayerToolsTip()
|
private static void DrawInstallMultiplayerToolsTip()
|
||||||
{
|
{
|
||||||
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
||||||
const string openDocsButtonText = "Open Docs";
|
const string openDocsButtonText = "Open Docs";
|
||||||
const string dismissButtonText = "Dismiss";
|
const string dismissButtonText = "Dismiss";
|
||||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/tools/current/install-tools";
|
const string targetUrl = "https://docs-multiplayer.unity3d.com/tools/current/install-tools";
|
||||||
const string infoIconName = "console.infoicon";
|
|
||||||
|
|
||||||
if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
|
if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
|
||||||
{
|
{
|
||||||
@@ -371,7 +497,7 @@ namespace Unity.Netcode.Editor
|
|||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
GUILayout.BeginHorizontal(s_HelpBoxStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false), GUILayout.MaxWidth(800));
|
GUILayout.BeginHorizontal(s_HelpBoxStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false), GUILayout.MaxWidth(800));
|
||||||
{
|
{
|
||||||
GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(infoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true));
|
GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(k_InfoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true));
|
||||||
GUILayout.Space(4);
|
GUILayout.Space(4);
|
||||||
GUILayout.Label(getToolsText, s_CenteredWordWrappedLabelStyle, GUILayout.ExpandHeight(true));
|
GUILayout.Label(getToolsText, s_CenteredWordWrappedLabelStyle, GUILayout.ExpandHeight(true));
|
||||||
|
|
||||||
@@ -404,5 +530,69 @@ namespace Unity.Netcode.Editor
|
|||||||
|
|
||||||
GUILayout.Space(10);
|
GUILayout.Space(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawPrefabListField()
|
||||||
|
{
|
||||||
|
if (!m_NetworkManager.NetworkConfig.HasOldPrefabList())
|
||||||
|
{
|
||||||
|
if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(m_PrefabsList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old format of prefab list
|
||||||
|
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
|
||||||
|
if (GUILayout.Button(new GUIContent("Migrate Prefab List", "Converts the old format Network Prefab list to a new Scriptable Object")))
|
||||||
|
{
|
||||||
|
// Default directory
|
||||||
|
var directory = "Assets/";
|
||||||
|
var assetPath = AssetDatabase.GetAssetPath(m_NetworkManager);
|
||||||
|
if (assetPath == "")
|
||||||
|
{
|
||||||
|
assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(m_NetworkManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetPath != "")
|
||||||
|
{
|
||||||
|
directory = Path.GetDirectoryName(assetPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
#if UNITY_2021_1_OR_NEWER
|
||||||
|
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
|
||||||
|
#else
|
||||||
|
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
|
||||||
|
#endif
|
||||||
|
if (prefabStage != null)
|
||||||
|
{
|
||||||
|
var prefabPath = prefabStage.assetPath;
|
||||||
|
if (!string.IsNullOrEmpty(prefabPath))
|
||||||
|
{
|
||||||
|
directory = Path.GetDirectoryName(prefabPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_NetworkManager.gameObject.scene != null)
|
||||||
|
{
|
||||||
|
var scenePath = m_NetworkManager.gameObject.scene.path;
|
||||||
|
if (!string.IsNullOrEmpty(scenePath))
|
||||||
|
{
|
||||||
|
directory = Path.GetDirectoryName(scenePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var networkPrefabs = m_NetworkManager.NetworkConfig.MigrateOldNetworkPrefabsToNetworkPrefabsList();
|
||||||
|
string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset");
|
||||||
|
Debug.Log("Saving migrated Network Prefabs List to " + path);
|
||||||
|
AssetDatabase.CreateAsset(networkPrefabs, path);
|
||||||
|
EditorUtility.SetDirty(m_NetworkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
Editor/NetworkManagerRelayIntegration.cs
Normal file
120
Editor/NetworkManagerRelayIntegration.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#if UNITY_2022_3_OR_NEWER && (RELAY_SDK_INSTALLED && !UNITY_WEBGL ) || (RELAY_SDK_INSTALLED && UNITY_WEBGL && UTP_TRANSPORT_2_0_ABOVE)
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Unity.Netcode.Transports.UTP;
|
||||||
|
using Unity.Networking.Transport.Relay;
|
||||||
|
using Unity.Services.Authentication;
|
||||||
|
using Unity.Services.Core;
|
||||||
|
using Unity.Services.Relay;
|
||||||
|
using Unity.Services.Relay.Models;
|
||||||
|
|
||||||
|
namespace Unity.Netcode.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Integration with Unity Relay SDK and Unity Transport that support the additional buttons in the NetworkManager inspector.
|
||||||
|
/// This code could theoretically be used at runtime, but we would like to avoid the additional dependencies in the runtime assembly of netcode for gameobjects.
|
||||||
|
/// </summary>
|
||||||
|
public static class NetworkManagerRelayIntegration
|
||||||
|
{
|
||||||
|
|
||||||
|
#if UNITY_WEBGL
|
||||||
|
private const string k_DefaultConnectionType = "wss";
|
||||||
|
#else
|
||||||
|
private const string k_DefaultConnectionType = "dtls";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Easy relay integration (host): it will initialize the unity services, sign in anonymously and start the host with a new relay allocation.
|
||||||
|
/// Note that this will force the use of Unity Transport.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkManager">The network manager that will start the connection</param>
|
||||||
|
/// <param name="maxConnections">Maximum number of connections to the created relay.</param>
|
||||||
|
/// <param name="connectionType">The connection type of the <see cref="RelayServerData"/> (wss, ws, dtls or udp) </param>
|
||||||
|
/// <returns>The join code that a potential client can use and the allocation</returns>
|
||||||
|
/// <exception cref="ServicesInitializationException"> Exception when there's an error during services initialization </exception>
|
||||||
|
/// <exception cref="UnityProjectNotLinkedException"> Exception when the project is not linked to a cloud project id </exception>
|
||||||
|
/// <exception cref="CircularDependencyException"> Exception when two registered <see cref="IInitializablePackage"/> depend on the other </exception>
|
||||||
|
/// <exception cref="AuthenticationException"> The task fails with the exception when the task cannot complete successfully due to Authentication specific errors. </exception>
|
||||||
|
/// <exception cref="RequestFailedException"> See <see cref="IAuthenticationService.SignInAnonymouslyAsync"/></exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the maxConnections argument fails validation in Relay Service SDK.</exception>
|
||||||
|
/// <exception cref="RelayServiceException">Thrown when the request successfully reach the Relay Allocation Service but results in an error.</exception>
|
||||||
|
internal static async Task<(string, Allocation)> StartHostWithRelay(this NetworkManager networkManager, int maxConnections = 5)
|
||||||
|
{
|
||||||
|
var codeAndAllocation = await InitializeAndCreateAllocAsync(networkManager, maxConnections, k_DefaultConnectionType);
|
||||||
|
return networkManager.StartHost() ? codeAndAllocation : (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Easy relay integration (server): it will initialize the unity services, sign in anonymously and start the server with a new relay allocation.
|
||||||
|
/// Note that this will force the use of Unity Transport.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkManager">The network manager that will start the connection</param>
|
||||||
|
/// <param name="maxConnections">Maximum number of connections to the created relay.</param>
|
||||||
|
/// <returns>The join code that a potential client can use and the allocation.</returns>
|
||||||
|
/// <exception cref="ServicesInitializationException"> Exception when there's an error during services initialization </exception>
|
||||||
|
/// <exception cref="UnityProjectNotLinkedException"> Exception when the project is not linked to a cloud project id </exception>
|
||||||
|
/// <exception cref="CircularDependencyException"> Exception when two registered <see cref="IInitializablePackage"/> depend on the other </exception>
|
||||||
|
/// <exception cref="AuthenticationException"> The task fails with the exception when the task cannot complete successfully due to Authentication specific errors. </exception>
|
||||||
|
/// <exception cref="RequestFailedException"> See <see cref="IAuthenticationService.SignInAnonymouslyAsync"/></exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the maxConnections argument fails validation in Relay Service SDK.</exception>
|
||||||
|
/// <exception cref="RelayServiceException">Thrown when the request successfully reach the Relay Allocation Service but results in an error.</exception>
|
||||||
|
internal static async Task<(string, Allocation)> StartServerWithRelay(this NetworkManager networkManager, int maxConnections = 5)
|
||||||
|
{
|
||||||
|
var codeAndAllocation = await InitializeAndCreateAllocAsync(networkManager, maxConnections, k_DefaultConnectionType);
|
||||||
|
return networkManager.StartServer() ? codeAndAllocation : (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Easy relay integration (client): it will initialize the unity services, sign in anonymously, join the relay with the given join code and start the client.
|
||||||
|
/// Note that this will force the use of Unity Transport.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkManager">The network manager that will start the connection</param>
|
||||||
|
/// <param name="joinCode">The join code of the allocation</param>
|
||||||
|
/// <exception cref="ServicesInitializationException"> Exception when there's an error during services initialization </exception>
|
||||||
|
/// <exception cref="UnityProjectNotLinkedException"> Exception when the project is not linked to a cloud project id </exception>
|
||||||
|
/// <exception cref="CircularDependencyException"> Exception when two registered <see cref="IInitializablePackage"/> depend on the other </exception>
|
||||||
|
/// <exception cref="AuthenticationException"> The task fails with the exception when the task cannot complete successfully due to Authentication specific errors. </exception>
|
||||||
|
/// <exception cref="RequestFailedException">Thrown when the request does not reach the Relay Allocation Service.</exception>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if the joinCode has the wrong format.</exception>
|
||||||
|
/// <exception cref="RelayServiceException">Thrown when the request successfully reach the Relay Allocation Service but results in an error.</exception>
|
||||||
|
/// <returns>True if starting the client was successful</returns>
|
||||||
|
internal static async Task<JoinAllocation> StartClientWithRelay(this NetworkManager networkManager, string joinCode)
|
||||||
|
{
|
||||||
|
await UnityServices.InitializeAsync();
|
||||||
|
if (!AuthenticationService.Instance.IsSignedIn)
|
||||||
|
{
|
||||||
|
await AuthenticationService.Instance.SignInAnonymouslyAsync();
|
||||||
|
}
|
||||||
|
var joinAllocation = await RelayService.Instance.JoinAllocationAsync(joinCode: joinCode);
|
||||||
|
GetUnityTransport(networkManager, k_DefaultConnectionType).SetRelayServerData(new RelayServerData(joinAllocation, k_DefaultConnectionType));
|
||||||
|
return networkManager.StartClient() ? joinAllocation : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<(string, Allocation)> InitializeAndCreateAllocAsync(NetworkManager networkManager, int maxConnections, string connectionType)
|
||||||
|
{
|
||||||
|
await UnityServices.InitializeAsync();
|
||||||
|
if (!AuthenticationService.Instance.IsSignedIn)
|
||||||
|
{
|
||||||
|
await AuthenticationService.Instance.SignInAnonymouslyAsync();
|
||||||
|
}
|
||||||
|
Allocation allocation = await RelayService.Instance.CreateAllocationAsync(maxConnections);
|
||||||
|
GetUnityTransport(networkManager, connectionType).SetRelayServerData(new RelayServerData(allocation, connectionType));
|
||||||
|
var joinCode = await RelayService.Instance.GetJoinCodeAsync(allocation.AllocationId);
|
||||||
|
return (joinCode, allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnityTransport GetUnityTransport(NetworkManager networkManager, string connectionType)
|
||||||
|
{
|
||||||
|
if (!networkManager.TryGetComponent<UnityTransport>(out var transport))
|
||||||
|
{
|
||||||
|
transport = networkManager.gameObject.AddComponent<UnityTransport>();
|
||||||
|
}
|
||||||
|
#if UTP_TRANSPORT_2_0_ABOVE
|
||||||
|
transport.UseWebSockets = connectionType.StartsWith("ws"); // Probably should be part of SetRelayServerData, but not possible at this point
|
||||||
|
#endif
|
||||||
|
networkManager.NetworkConfig.NetworkTransport = transport; // Force using UnityTransport
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
3
Editor/NetworkManagerRelayIntegration.cs.meta
Normal file
3
Editor/NetworkManagerRelayIntegration.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23b658b1c2e443109a8a131ef3632c9b
|
||||||
|
timeCreated: 1698673251
|
||||||
@@ -10,6 +10,7 @@ namespace Unity.Netcode.Editor
|
|||||||
[CustomEditor(typeof(NetworkTransform), true)]
|
[CustomEditor(typeof(NetworkTransform), true)]
|
||||||
public class NetworkTransformEditor : UnityEditor.Editor
|
public class NetworkTransformEditor : UnityEditor.Editor
|
||||||
{
|
{
|
||||||
|
private SerializedProperty m_UseUnreliableDeltas;
|
||||||
private SerializedProperty m_SyncPositionXProperty;
|
private SerializedProperty m_SyncPositionXProperty;
|
||||||
private SerializedProperty m_SyncPositionYProperty;
|
private SerializedProperty m_SyncPositionYProperty;
|
||||||
private SerializedProperty m_SyncPositionZProperty;
|
private SerializedProperty m_SyncPositionZProperty;
|
||||||
@@ -39,6 +40,7 @@ namespace Unity.Netcode.Editor
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnEnable()
|
public void OnEnable()
|
||||||
{
|
{
|
||||||
|
m_UseUnreliableDeltas = serializedObject.FindProperty(nameof(NetworkTransform.UseUnreliableDeltas));
|
||||||
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
|
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
|
||||||
m_SyncPositionYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionY));
|
m_SyncPositionYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionY));
|
||||||
m_SyncPositionZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionZ));
|
m_SyncPositionZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionZ));
|
||||||
@@ -129,7 +131,9 @@ namespace Unity.Netcode.Editor
|
|||||||
EditorGUILayout.PropertyField(m_PositionThresholdProperty);
|
EditorGUILayout.PropertyField(m_PositionThresholdProperty);
|
||||||
EditorGUILayout.PropertyField(m_RotAngleThresholdProperty);
|
EditorGUILayout.PropertyField(m_RotAngleThresholdProperty);
|
||||||
EditorGUILayout.PropertyField(m_ScaleThresholdProperty);
|
EditorGUILayout.PropertyField(m_ScaleThresholdProperty);
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.LabelField("Delivery", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.PropertyField(m_UseUnreliableDeltas);
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
|
||||||
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
||||||
|
|||||||
@@ -3,11 +3,21 @@
|
|||||||
"rootNamespace": "Unity.Netcode.Editor",
|
"rootNamespace": "Unity.Netcode.Editor",
|
||||||
"references": [
|
"references": [
|
||||||
"Unity.Netcode.Runtime",
|
"Unity.Netcode.Runtime",
|
||||||
"Unity.Netcode.Components"
|
"Unity.Netcode.Components",
|
||||||
|
"Unity.Services.Relay",
|
||||||
|
"Unity.Networking.Transport",
|
||||||
|
"Unity.Services.Core",
|
||||||
|
"Unity.Services.Authentication"
|
||||||
],
|
],
|
||||||
"includePlatforms": [
|
"includePlatforms": [
|
||||||
"Editor"
|
"Editor"
|
||||||
],
|
],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
"versionDefines": [
|
"versionDefines": [
|
||||||
{
|
{
|
||||||
"name": "com.unity.multiplayer.tools",
|
"name": "com.unity.multiplayer.tools",
|
||||||
@@ -33,6 +43,17 @@
|
|||||||
"name": "com.unity.modules.physics2d",
|
"name": "com.unity.modules.physics2d",
|
||||||
"expression": "",
|
"expression": "",
|
||||||
"define": "COM_UNITY_MODULES_PHYSICS2D"
|
"define": "COM_UNITY_MODULES_PHYSICS2D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "com.unity.services.relay",
|
||||||
|
"expression": "1.0",
|
||||||
|
"define": "RELAY_SDK_INSTALLED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "com.unity.transport",
|
||||||
|
"expression": "2.0",
|
||||||
|
"define": "UTP_TRANSPORT_2_0_ABOVE"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"noEngineReferences": false
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ namespace Unity.Netcode
|
|||||||
/// The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped.
|
/// The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")]
|
[Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")]
|
||||||
public float SpawnTimeout = 1f;
|
public float SpawnTimeout = 10f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not to enable network logs.
|
/// Whether or not to enable network logs.
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ namespace Unity.Netcode
|
|||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
public Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
|
public Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for the legacy way of spawning NetworkPrefabs with an override when manually instantiating and spawning.
|
||||||
|
/// To handle multiple source NetworkPrefab overrides that all point to the same target NetworkPrefab use
|
||||||
|
/// <see cref="NetworkSpawnManager.InstantiateAndSpawn(NetworkObject, ulong, bool, bool, bool, Vector3, Quaternion)"/>
|
||||||
|
/// or <see cref="NetworkObject.InstantiateAndSpawn(NetworkManager, ulong, bool, bool, bool, Vector3, Quaternion)"/>
|
||||||
|
/// </summary>
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
public Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
|
public Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
|
||||||
|
|
||||||
@@ -234,7 +240,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < m_Prefabs.Count; i++)
|
for (int i = 0; i < m_Prefabs.Count; i++)
|
||||||
{
|
{
|
||||||
if (m_Prefabs[i].Prefab == prefab)
|
// Check both values as Prefab and be different than SourcePrefabToOverride
|
||||||
|
if (m_Prefabs[i].Prefab == prefab || m_Prefabs[i].SourcePrefabToOverride == prefab)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -262,7 +269,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures <see cref="NetworkPrefabOverrideLinks"/> and <see cref="OverrideToNetworkPrefab"/> for the given <see cref="NetworkPrefab"/>
|
/// Configures <see cref="NetworkPrefabOverrideLinks"/> for the given <see cref="NetworkPrefab"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
|
private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
|
||||||
{
|
{
|
||||||
@@ -296,28 +303,16 @@ namespace Unity.Netcode
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we don't have several overrides targeting the same prefab. Apparently we don't support that... shame.
|
|
||||||
if (OverrideToNetworkPrefab.ContainsKey(target))
|
|
||||||
{
|
|
||||||
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
|
||||||
|
|
||||||
// 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: {target}!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (networkPrefab.Override)
|
switch (networkPrefab.Override)
|
||||||
{
|
{
|
||||||
case NetworkPrefabOverride.Prefab:
|
case NetworkPrefabOverride.Prefab:
|
||||||
{
|
|
||||||
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
|
|
||||||
OverrideToNetworkPrefab.Add(target, source);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NetworkPrefabOverride.Hash:
|
case NetworkPrefabOverride.Hash:
|
||||||
{
|
{
|
||||||
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
|
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
|
||||||
OverrideToNetworkPrefab.Add(target, source);
|
if (!OverrideToNetworkPrefab.ContainsKey(target))
|
||||||
|
{
|
||||||
|
OverrideToNetworkPrefab.Add(target, source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,38 @@ using Object = UnityEngine.Object;
|
|||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public enum ConnectionEvent
|
||||||
|
{
|
||||||
|
ClientConnected,
|
||||||
|
PeerConnected,
|
||||||
|
ClientDisconnected,
|
||||||
|
PeerDisconnected
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ConnectionEventData
|
||||||
|
{
|
||||||
|
public ConnectionEvent EventType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The client ID for the client that just connected
|
||||||
|
/// For the <see cref="ConnectionEvent.ClientConnected"/> and <see cref="ConnectionEvent.ClientDisconnected"/>
|
||||||
|
/// events on the client side, this will be LocalClientId.
|
||||||
|
/// On the server side, this will be the ID of the client that just connected.
|
||||||
|
///
|
||||||
|
/// For the <see cref="ConnectionEvent.PeerConnected"/> and <see cref="ConnectionEvent.PeerDisconnected"/>
|
||||||
|
/// events on the client side, this will be the client ID assigned by the server to the remote peer.
|
||||||
|
/// </summary>
|
||||||
|
public ulong ClientId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is only populated in <see cref="ConnectionEvent.ClientConnected"/> on the client side, and
|
||||||
|
/// contains the list of other peers who were present before you connected. In all other situations,
|
||||||
|
/// this array will be uninitialized.
|
||||||
|
/// </summary>
|
||||||
|
public NativeArray<ulong> PeerClientIds;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The NGO connection manager handles:
|
/// The NGO connection manager handles:
|
||||||
/// - Client Connections
|
/// - Client Connections
|
||||||
@@ -42,7 +74,105 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<ulong> OnClientDisconnectCallback = null;
|
public event Action<ulong> OnClientDisconnectCallback = null;
|
||||||
|
|
||||||
internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId);
|
/// <summary>
|
||||||
|
/// The callback to invoke once a peer connects. This callback is only ran on the server and on the local client that connects.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<NetworkManager, ConnectionEventData> OnConnectionEvent = null;
|
||||||
|
|
||||||
|
|
||||||
|
internal void InvokeOnClientConnectedCallback(ulong clientId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnClientConnectedCallback?.Invoke(clientId);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
var peerClientIds = new NativeArray<ulong>(Math.Max(NetworkManager.ConnectedClientsIds.Count - 1, 0), Allocator.Temp);
|
||||||
|
// `using var peerClientIds` or `using(peerClientIds)` renders it immutable...
|
||||||
|
using var sentinel = peerClientIds;
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
foreach (var peerId in NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (peerId == NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
peerClientIds[idx] = peerId;
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = NetworkManager.LocalClientId, EventType = ConnectionEvent.ClientConnected, PeerClientIds = peerClientIds });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientConnected });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvokeOnClientDisconnectCallback(ulong clientId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnClientDisconnectCallback?.Invoke(clientId);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientDisconnected });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvokeOnPeerConnectedCallback(ulong clientId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerConnected });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal void InvokeOnPeerDisconnectedCallback(ulong clientId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerDisconnected });
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Debug.LogException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
||||||
@@ -217,7 +347,7 @@ namespace Unity.Netcode
|
|||||||
// When this happens, the client will not have an entry within the m_TransportIdToClientIdMap or m_ClientIdToTransportIdMap lookup tables so we exit early and just return 0 to be used for the disconnect event.
|
// When this happens, the client will not have an entry within the m_TransportIdToClientIdMap or m_ClientIdToTransportIdMap lookup tables so we exit early and just return 0 to be used for the disconnect event.
|
||||||
if (!LocalClient.IsServer && !TransportIdToClientIdMap.ContainsKey(transportId))
|
if (!LocalClient.IsServer && !TransportIdToClientIdMap.ContainsKey(transportId))
|
||||||
{
|
{
|
||||||
return 0;
|
return NetworkManager.LocalClientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientId = TransportIdToClientId(transportId);
|
var clientId = TransportIdToClientId(transportId);
|
||||||
@@ -346,31 +476,38 @@ namespace Unity.Netcode
|
|||||||
s_TransportDisconnect.Begin();
|
s_TransportDisconnect.Begin();
|
||||||
#endif
|
#endif
|
||||||
var clientId = TransportIdCleanUp(transportClientId);
|
var clientId = TransportIdCleanUp(transportClientId);
|
||||||
|
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||||
{
|
{
|
||||||
NetworkLog.LogInfo($"Disconnect Event From {clientId}");
|
NetworkLog.LogInfo($"Disconnect Event From {clientId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are a client and we have gotten the ServerClientId back, then use our assigned local id as the client that was
|
||||||
|
// disconnected (either the user disconnected or the server disconnected, but the client that disconnected is the LocalClientId)
|
||||||
|
if (!NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
clientId = NetworkManager.LocalClientId;
|
||||||
|
}
|
||||||
|
|
||||||
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
|
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
|
||||||
MessageManager.ProcessIncomingMessageQueue();
|
MessageManager.ProcessIncomingMessageQueue();
|
||||||
|
|
||||||
try
|
InvokeOnClientDisconnectCallback(clientId);
|
||||||
|
|
||||||
|
if (LocalClient.IsHost)
|
||||||
{
|
{
|
||||||
OnClientDisconnectCallback?.Invoke(clientId);
|
InvokeOnPeerDisconnectedCallback(clientId);
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Debug.LogException(exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LocalClient.IsServer)
|
if (LocalClient.IsServer)
|
||||||
{
|
{
|
||||||
OnClientDisconnectFromServer(clientId);
|
OnClientDisconnectFromServer(clientId);
|
||||||
}
|
}
|
||||||
else
|
else // As long as we are not in the middle of a shutdown
|
||||||
|
if (!NetworkManager.ShutdownInProgress)
|
||||||
{
|
{
|
||||||
// We must pass true here and not process any sends messages as we are no longer connected and thus there is no one to send any messages to and this will cause an exception within UnityTransport as the client ID is no longer valid.
|
// We must pass true here and not process any sends messages as we are no longer connected.
|
||||||
|
// Otherwise, attempting to process messages here can cause an exception within UnityTransport
|
||||||
|
// as the client ID is no longer valid.
|
||||||
NetworkManager.Shutdown(true);
|
NetworkManager.Shutdown(true);
|
||||||
}
|
}
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
@@ -623,8 +760,17 @@ namespace Unity.Netcode
|
|||||||
var message = new ConnectionApprovedMessage
|
var message = new ConnectionApprovedMessage
|
||||||
{
|
{
|
||||||
OwnerClientId = ownerClientId,
|
OwnerClientId = ownerClientId,
|
||||||
NetworkTick = NetworkManager.LocalTime.Tick
|
NetworkTick = NetworkManager.LocalTime.Tick,
|
||||||
|
ConnectedClientIds = new NativeArray<ulong>(ConnectedClientIds.Count, Allocator.Temp)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
foreach (var clientId in ConnectedClientIds)
|
||||||
|
{
|
||||||
|
message.ConnectedClientIds[i] = clientId;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
// Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled
|
// Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled
|
||||||
@@ -651,12 +797,17 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
||||||
message.MessageVersions.Dispose();
|
message.MessageVersions.Dispose();
|
||||||
|
message.ConnectedClientIds.Dispose();
|
||||||
|
|
||||||
// If scene management is disabled, then we are done and notify the local host-server the client is connected
|
// If scene management is disabled, then we are done and notify the local host-server the client is connected
|
||||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
NetworkManager.ConnectedClients[ownerClientId].IsConnected = true;
|
NetworkManager.ConnectedClients[ownerClientId].IsConnected = true;
|
||||||
InvokeOnClientConnectedCallback(ownerClientId);
|
InvokeOnClientConnectedCallback(ownerClientId);
|
||||||
|
if (LocalClient.IsHost)
|
||||||
|
{
|
||||||
|
InvokeOnPeerConnectedCallback(ownerClientId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization
|
else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization
|
||||||
{
|
{
|
||||||
@@ -740,6 +891,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
ConnectedClients.Add(clientId, networkClient);
|
ConnectedClients.Add(clientId, networkClient);
|
||||||
ConnectedClientsList.Add(networkClient);
|
ConnectedClientsList.Add(networkClient);
|
||||||
|
var message = new ClientConnectedMessage { ClientId = clientId };
|
||||||
|
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
|
||||||
ConnectedClientIds.Add(clientId);
|
ConnectedClientIds.Add(clientId);
|
||||||
return networkClient;
|
return networkClient;
|
||||||
}
|
}
|
||||||
@@ -757,6 +910,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
// If we are shutting down and this is the server or host disconnecting, then ignore
|
// If we are shutting down and this is the server or host disconnecting, then ignore
|
||||||
// clean up as everything that needs to be destroyed will be during shutdown.
|
// clean up as everything that needs to be destroyed will be during shutdown.
|
||||||
|
|
||||||
if (NetworkManager.ShutdownInProgress && clientId == NetworkManager.ServerClientId)
|
if (NetworkManager.ShutdownInProgress && clientId == NetworkManager.ServerClientId)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -780,7 +934,7 @@ namespace Unity.Netcode
|
|||||||
NetworkManager.SpawnManager.DespawnObject(playerObject, true);
|
NetworkManager.SpawnManager.DespawnObject(playerObject, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (!NetworkManager.ShutdownInProgress)
|
||||||
{
|
{
|
||||||
playerObject.RemoveOwnership();
|
playerObject.RemoveOwnership();
|
||||||
}
|
}
|
||||||
@@ -816,7 +970,7 @@ namespace Unity.Netcode
|
|||||||
Object.Destroy(ownedObject.gameObject);
|
Object.Destroy(ownedObject.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (!NetworkManager.ShutdownInProgress)
|
||||||
{
|
{
|
||||||
ownedObject.RemoveOwnership();
|
ownedObject.RemoveOwnership();
|
||||||
}
|
}
|
||||||
@@ -837,6 +991,8 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConnectedClientIds.Remove(clientId);
|
ConnectedClientIds.Remove(clientId);
|
||||||
|
var message = new ClientDisconnectedMessage { ClientId = clientId };
|
||||||
|
MessageManager?.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the client ID transport map exists
|
// If the client ID transport map exists
|
||||||
@@ -845,13 +1001,11 @@ namespace Unity.Netcode
|
|||||||
var transportId = ClientIdToTransportId(clientId);
|
var transportId = ClientIdToTransportId(clientId);
|
||||||
NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
|
NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
|
||||||
|
|
||||||
try
|
InvokeOnClientDisconnectCallback(clientId);
|
||||||
|
|
||||||
|
if (LocalClient.IsHost)
|
||||||
{
|
{
|
||||||
OnClientDisconnectCallback?.Invoke(clientId);
|
InvokeOnPeerDisconnectedCallback(clientId);
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Debug.LogException(exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the transport to client (and vice versa) mappings
|
// Clean up the transport to client (and vice versa) mappings
|
||||||
@@ -889,6 +1043,12 @@ namespace Unity.Netcode
|
|||||||
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
|
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientId == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Disconnecting the local server-host client is not allowed. Use NetworkManager.Shutdown instead.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(reason))
|
if (!string.IsNullOrEmpty(reason))
|
||||||
{
|
{
|
||||||
var disconnectReason = new DisconnectReasonMessage
|
var disconnectReason = new DisconnectReasonMessage
|
||||||
@@ -933,13 +1093,8 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void Shutdown()
|
internal void Shutdown()
|
||||||
{
|
{
|
||||||
LocalClient.IsApproved = false;
|
|
||||||
LocalClient.IsConnected = false;
|
|
||||||
if (LocalClient.IsServer)
|
if (LocalClient.IsServer)
|
||||||
{
|
{
|
||||||
// make sure all messages are flushed before transport disconnect clients
|
|
||||||
MessageManager?.ProcessSendQueues();
|
|
||||||
|
|
||||||
// Build a list of all client ids to be disconnected
|
// Build a list of all client ids to be disconnected
|
||||||
var disconnectedIds = new HashSet<ulong>();
|
var disconnectedIds = new HashSet<ulong>();
|
||||||
|
|
||||||
@@ -975,9 +1130,15 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
DisconnectRemoteClient(clientId);
|
DisconnectRemoteClient(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure all messages are flushed before transport disconnects clients
|
||||||
|
MessageManager?.ProcessSendQueues();
|
||||||
}
|
}
|
||||||
else if (NetworkManager != null && NetworkManager.IsListening && LocalClient.IsClient)
|
else if (NetworkManager != null && NetworkManager.IsListening && LocalClient.IsClient)
|
||||||
{
|
{
|
||||||
|
// make sure all messages are flushed before disconnecting
|
||||||
|
MessageManager?.ProcessSendQueues();
|
||||||
|
|
||||||
// Client only, send disconnect and if transport throws and exception, log the exception and continue the shutdown sequence (or forever be shutting down)
|
// Client only, send disconnect and if transport throws and exception, log the exception and continue the shutdown sequence (or forever be shutting down)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -989,6 +1150,12 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalClient.IsApproved = false;
|
||||||
|
LocalClient.IsConnected = false;
|
||||||
|
ConnectedClients.Clear();
|
||||||
|
ConnectedClientIds.Clear();
|
||||||
|
ConnectedClientsList.Clear();
|
||||||
|
|
||||||
if (NetworkManager != null && NetworkManager.NetworkConfig?.NetworkTransport != null)
|
if (NetworkManager != null && NetworkManager.NetworkConfig?.NetworkTransport != null)
|
||||||
{
|
{
|
||||||
NetworkManager.NetworkConfig.NetworkTransport.OnTransportEvent -= HandleNetworkEvent;
|
NetworkManager.NetworkConfig.NetworkTransport.OnTransportEvent -= HandleNetworkEvent;
|
||||||
|
|||||||
@@ -3,27 +3,54 @@ using System.Collections.Generic;
|
|||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
public class RpcException : Exception
|
||||||
|
{
|
||||||
|
public RpcException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base class to override to write network code. Inherits MonoBehaviour
|
/// The base class to override to write network code. Inherits MonoBehaviour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class NetworkBehaviour : MonoBehaviour
|
public abstract class NetworkBehaviour : MonoBehaviour
|
||||||
{
|
{
|
||||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
#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<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>();
|
||||||
|
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
// RuntimeAccessModifiersILPP will make this `public`
|
||||||
|
internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>();
|
||||||
|
#endif
|
||||||
|
|
||||||
// RuntimeAccessModifiersILPP will make this `protected`
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
internal enum __RpcExecStage
|
internal enum __RpcExecStage
|
||||||
{
|
{
|
||||||
|
// Technically will overlap with None and Server
|
||||||
|
// but it doesn't matter since we don't use None and Server
|
||||||
|
Send = 0,
|
||||||
|
Execute = 1,
|
||||||
|
|
||||||
|
// Backward compatibility, not used...
|
||||||
None = 0,
|
None = 0,
|
||||||
Server = 1,
|
Server = 1,
|
||||||
Client = 2
|
Client = 2,
|
||||||
}
|
}
|
||||||
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
||||||
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
||||||
|
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
// RuntimeAccessModifiersILPP will make this `protected`
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
|
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.Send;
|
||||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||||
|
|
||||||
private const int k_RpcMessageDefaultSize = 1024; // 1k
|
private const int k_RpcMessageDefaultSize = 1024; // 1k
|
||||||
@@ -97,7 +124,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
bufferWriter.Dispose();
|
bufferWriter.Dispose();
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||||
{
|
{
|
||||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||||
NetworkManager.ServerClientId,
|
NetworkManager.ServerClientId,
|
||||||
@@ -228,7 +255,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
bufferWriter.Dispose();
|
bufferWriter.Dispose();
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||||
{
|
{
|
||||||
if (clientRpcParams.Send.TargetClientIds != null)
|
if (clientRpcParams.Send.TargetClientIds != null)
|
||||||
{
|
{
|
||||||
@@ -271,6 +298,99 @@ namespace Unity.Netcode
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
|
internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, RpcAttribute.RpcAttributeParams attributeParams, SendTo defaultTarget, RpcDelivery rpcDelivery)
|
||||||
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||||
|
{
|
||||||
|
if (attributeParams.RequireOwnership && !IsOwner)
|
||||||
|
{
|
||||||
|
throw new RpcException("This RPC can only be sent by its owner.");
|
||||||
|
}
|
||||||
|
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
|
internal void __endSendRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, RpcParams rpcParams, RpcAttribute.RpcAttributeParams attributeParams, SendTo defaultTarget, RpcDelivery rpcDelivery)
|
||||||
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||||
|
{
|
||||||
|
var rpcMessage = new RpcMessage
|
||||||
|
{
|
||||||
|
Metadata = new RpcMetadata
|
||||||
|
{
|
||||||
|
NetworkObjectId = NetworkObjectId,
|
||||||
|
NetworkBehaviourId = NetworkBehaviourId,
|
||||||
|
NetworkRpcMethodId = rpcMethodId,
|
||||||
|
},
|
||||||
|
SenderClientId = NetworkManager.LocalClientId,
|
||||||
|
WriteBuffer = bufferWriter
|
||||||
|
};
|
||||||
|
|
||||||
|
NetworkDelivery networkDelivery;
|
||||||
|
switch (rpcDelivery)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case RpcDelivery.Reliable:
|
||||||
|
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||||
|
break;
|
||||||
|
case RpcDelivery.Unreliable:
|
||||||
|
if (bufferWriter.Length > NetworkManager.MessageManager.NonFragmentedMessageMaxSize)
|
||||||
|
{
|
||||||
|
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
||||||
|
}
|
||||||
|
networkDelivery = NetworkDelivery.Unreliable;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rpcParams.Send.Target == null)
|
||||||
|
{
|
||||||
|
switch (defaultTarget)
|
||||||
|
{
|
||||||
|
case SendTo.Everyone:
|
||||||
|
rpcParams.Send.Target = RpcTarget.Everyone;
|
||||||
|
break;
|
||||||
|
case SendTo.Owner:
|
||||||
|
rpcParams.Send.Target = RpcTarget.Owner;
|
||||||
|
break;
|
||||||
|
case SendTo.Server:
|
||||||
|
rpcParams.Send.Target = RpcTarget.Server;
|
||||||
|
break;
|
||||||
|
case SendTo.NotServer:
|
||||||
|
rpcParams.Send.Target = RpcTarget.NotServer;
|
||||||
|
break;
|
||||||
|
case SendTo.NotMe:
|
||||||
|
rpcParams.Send.Target = RpcTarget.NotMe;
|
||||||
|
break;
|
||||||
|
case SendTo.NotOwner:
|
||||||
|
rpcParams.Send.Target = RpcTarget.NotOwner;
|
||||||
|
break;
|
||||||
|
case SendTo.Me:
|
||||||
|
rpcParams.Send.Target = RpcTarget.Me;
|
||||||
|
break;
|
||||||
|
case SendTo.ClientsAndHost:
|
||||||
|
rpcParams.Send.Target = RpcTarget.ClientsAndHost;
|
||||||
|
break;
|
||||||
|
case SendTo.SpecifiedInParams:
|
||||||
|
throw new RpcException("This method requires a runtime-specified send target.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (defaultTarget != SendTo.SpecifiedInParams && !attributeParams.AllowTargetOverride)
|
||||||
|
{
|
||||||
|
throw new RpcException("Target override is not allowed for this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rpcParams.Send.LocalDeferMode == LocalDeferMode.Default)
|
||||||
|
{
|
||||||
|
rpcParams.Send.LocalDeferMode = attributeParams.DeferLocal ? LocalDeferMode.Defer : LocalDeferMode.SendImmediate;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcParams.Send.Target.Send(this, ref rpcMessage, networkDelivery, rpcParams);
|
||||||
|
|
||||||
|
bufferWriter.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
// RuntimeAccessModifiersILPP will make this `protected`
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
internal static NativeList<T> __createNativeList<T>() where T : unmanaged
|
internal static NativeList<T> __createNativeList<T>() where T : unmanaged
|
||||||
@@ -302,6 +422,24 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This erroneously tries to simplify these method references but the docs do not pick it up correctly
|
||||||
|
// because they try to resolve it on the field rather than the class of the same name.
|
||||||
|
#pragma warning disable IDE0001
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the various <see cref="SendTo"/> targets at runtime, as well as
|
||||||
|
/// runtime-bound targets like <see cref="Unity.Netcode.RpcTarget.Single"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group(NativeArray{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group(NativeList{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group(ulong[])"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group{T}(T)"/>, <see cref="Unity.Netcode.RpcTarget.Not(ulong)"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeArray{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeList{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not(ulong[])"/>, and
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not{T}(T)"/>
|
||||||
|
/// </summary>
|
||||||
|
#pragma warning restore IDE0001
|
||||||
|
public RpcTarget RpcTarget => NetworkManager.RpcTarget;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
||||||
/// is the local player object. If no NetworkObject is assigned it will always return false.
|
/// is the local player object. If no NetworkObject is assigned it will always return false.
|
||||||
@@ -318,6 +456,11 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsServer { get; private set; }
|
public bool IsServer { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets if the server (local or remote) is a host - i.e., also a client
|
||||||
|
/// </summary>
|
||||||
|
public bool ServerIsHost { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets if we are executing as client
|
/// Gets if we are executing as client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -459,12 +602,13 @@ namespace Unity.Netcode
|
|||||||
IsHost = NetworkManager.IsListening && NetworkManager.IsHost;
|
IsHost = NetworkManager.IsListening && NetworkManager.IsHost;
|
||||||
IsClient = NetworkManager.IsListening && NetworkManager.IsClient;
|
IsClient = NetworkManager.IsListening && NetworkManager.IsClient;
|
||||||
IsServer = NetworkManager.IsListening && NetworkManager.IsServer;
|
IsServer = NetworkManager.IsListening && NetworkManager.IsServer;
|
||||||
|
ServerIsHost = NetworkManager.IsListening && NetworkManager.ServerIsHost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // Shouldn't happen, but if so then set the properties to their default value;
|
else // Shouldn't happen, but if so then set the properties to their default value;
|
||||||
{
|
{
|
||||||
OwnerClientId = NetworkObjectId = default;
|
OwnerClientId = NetworkObjectId = default;
|
||||||
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = default;
|
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = ServerIsHost = default;
|
||||||
NetworkBehaviourId = default;
|
NetworkBehaviourId = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -532,6 +676,23 @@ namespace Unity.Netcode
|
|||||||
OnGainedOwnership();
|
OnGainedOwnership();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on all clients, override this method to be notified of any
|
||||||
|
/// ownership changes (even if the instance was niether the previous or
|
||||||
|
/// newly assigned current owner).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="previous">the previous owner</param>
|
||||||
|
/// <param name="current">the current owner</param>
|
||||||
|
protected virtual void OnOwnershipChanged(ulong previous, ulong current)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InternalOnOwnershipChanged(ulong previous, ulong current)
|
||||||
|
{
|
||||||
|
OnOwnershipChanged(previous, current);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets called when we loose ownership of this object
|
/// Gets called when we loose ownership of this object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -565,6 +726,25 @@ namespace Unity.Netcode
|
|||||||
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables.
|
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
|
internal virtual void __initializeRpcs()
|
||||||
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||||
|
{
|
||||||
|
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's RPCs.
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
|
internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName)
|
||||||
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||||
|
{
|
||||||
|
__rpc_func_table[GetType()][hash] = handler;
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
__rpc_name_table[GetType()][hash] = rpcMethodName;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
// RuntimeAccessModifiersILPP will make this `protected`
|
// RuntimeAccessModifiersILPP will make this `protected`
|
||||||
// Using this method here because ILPP doesn't seem to let us do visibility modification on properties.
|
// Using this method here because ILPP doesn't seem to let us do visibility modification on properties.
|
||||||
@@ -583,6 +763,14 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
m_VarInit = true;
|
m_VarInit = true;
|
||||||
|
|
||||||
|
if (!__rpc_func_table.ContainsKey(GetType()))
|
||||||
|
{
|
||||||
|
__rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>();
|
||||||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||||
|
__rpc_name_table[GetType()] = new Dictionary<uint, string>();
|
||||||
|
#endif
|
||||||
|
__initializeRpcs();
|
||||||
|
}
|
||||||
__initializeVariables();
|
__initializeVariables();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -936,6 +1124,8 @@ namespace Unity.Netcode
|
|||||||
if (finalPosition == positionBeforeSynchronize || threwException)
|
if (finalPosition == positionBeforeSynchronize || threwException)
|
||||||
{
|
{
|
||||||
writer.Seek(positionBeforeWrite);
|
writer.Seek(positionBeforeWrite);
|
||||||
|
// Truncate back to the size before
|
||||||
|
writer.Truncate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
@@ -15,6 +16,9 @@ namespace Unity.Netcode
|
|||||||
[AddComponentMenu("Netcode/Network Manager", -100)]
|
[AddComponentMenu("Netcode/Network Manager", -100)]
|
||||||
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
|
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
|
||||||
{
|
{
|
||||||
|
// TODO: Deprecate...
|
||||||
|
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
|
||||||
|
// be removed thanks to our semver validation.
|
||||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||||
|
|
||||||
// RuntimeAccessModifiersILPP will make this `public`
|
// RuntimeAccessModifiersILPP will make this `public`
|
||||||
@@ -39,6 +43,8 @@ namespace Unity.Netcode
|
|||||||
ConnectionManager.ProcessPendingApprovals();
|
ConnectionManager.ProcessPendingApprovals();
|
||||||
ConnectionManager.PollAndHandleNetworkEvents();
|
ConnectionManager.PollAndHandleNetworkEvents();
|
||||||
|
|
||||||
|
DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0);
|
||||||
|
|
||||||
MessageManager.ProcessIncomingMessageQueue();
|
MessageManager.ProcessIncomingMessageQueue();
|
||||||
MessageManager.CleanupDisconnectedClients();
|
MessageManager.CleanupDisconnectedClients();
|
||||||
}
|
}
|
||||||
@@ -67,13 +73,89 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (m_ShuttingDown)
|
if (m_ShuttingDown)
|
||||||
{
|
{
|
||||||
ShutdownInternal();
|
// Host-server will disconnect any connected clients prior to finalizing its shutdown
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
ProcessServerShutdown();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clients just disconnect immediately
|
||||||
|
ShutdownInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to provide a graceful shutdown sequence
|
||||||
|
/// </summary>
|
||||||
|
internal enum ServerShutdownStates
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
WaitForClientDisconnects,
|
||||||
|
InternalShutdown,
|
||||||
|
ShuttingDown,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal ServerShutdownStates ServerShutdownState;
|
||||||
|
private float m_ShutdownTimeout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a "soft shutdown" where the host or server will disconnect
|
||||||
|
/// all clients, with a provided reasons, prior to invoking its final
|
||||||
|
/// internal shutdown.
|
||||||
|
/// </summary>
|
||||||
|
internal void ProcessServerShutdown()
|
||||||
|
{
|
||||||
|
var minClientCount = IsHost ? 2 : 1;
|
||||||
|
switch (ServerShutdownState)
|
||||||
|
{
|
||||||
|
case ServerShutdownStates.None:
|
||||||
|
{
|
||||||
|
if (ConnectedClients.Count >= minClientCount)
|
||||||
|
{
|
||||||
|
var hostServer = IsHost ? "host" : "server";
|
||||||
|
var disconnectReason = $"Disconnected due to {hostServer} shutting down.";
|
||||||
|
for (int i = ConnectedClientsIds.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var clientId = ConnectedClientsIds[i];
|
||||||
|
if (clientId == ServerClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ConnectionManager.DisconnectClient(clientId, disconnectReason);
|
||||||
|
}
|
||||||
|
ServerShutdownState = ServerShutdownStates.WaitForClientDisconnects;
|
||||||
|
m_ShutdownTimeout = Time.realtimeSinceStartup + 5.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ServerShutdownState = ServerShutdownStates.InternalShutdown;
|
||||||
|
ProcessServerShutdown();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ServerShutdownStates.WaitForClientDisconnects:
|
||||||
|
{
|
||||||
|
if (ConnectedClients.Count < minClientCount || m_ShutdownTimeout < Time.realtimeSinceStartup)
|
||||||
|
{
|
||||||
|
ServerShutdownState = ServerShutdownStates.InternalShutdown;
|
||||||
|
ProcessServerShutdown();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ServerShutdownStates.InternalShutdown:
|
||||||
|
{
|
||||||
|
ServerShutdownState = ServerShutdownStates.ShuttingDown;
|
||||||
|
ShutdownInternal();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The client id used to represent the server
|
/// The client id used to represent the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -101,7 +183,7 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
|
/// Gets a list of just the IDs of all connected clients. This is only accessible on the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<ulong> ConnectedClientsIds => IsServer ? ConnectionManager.ConnectedClientIds : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientIds)} should only be accessed on server.");
|
public IReadOnlyList<ulong> ConnectedClientsIds => ConnectionManager.ConnectedClientIds;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the local <see cref="NetworkClient"/> for this client.
|
/// Gets the local <see cref="NetworkClient"/> for this client.
|
||||||
@@ -119,6 +201,11 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsServer => ConnectionManager.LocalClient.IsServer;
|
public bool IsServer => ConnectionManager.LocalClient.IsServer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether or not the current server (local or remote) is a host - i.e., also a client
|
||||||
|
/// </summary>
|
||||||
|
public bool ServerIsHost => ConnectionManager.ConnectedClientIds.Contains(ServerClientId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets Whether or not a client is running
|
/// Gets Whether or not a client is running
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -206,6 +293,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
|
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
|
||||||
|
///
|
||||||
|
/// It is recommended to use OnConnectionEvent instead, as this will eventually be deprecated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<ulong> OnClientConnectedCallback
|
public event Action<ulong> OnClientConnectedCallback
|
||||||
{
|
{
|
||||||
@@ -215,6 +304,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
|
/// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects.
|
||||||
|
///
|
||||||
|
/// It is recommended to use OnConnectionEvent instead, as this will eventually be deprecated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<ulong> OnClientDisconnectCallback
|
public event Action<ulong> OnClientDisconnectCallback
|
||||||
{
|
{
|
||||||
@@ -222,6 +313,16 @@ namespace Unity.Netcode
|
|||||||
remove => ConnectionManager.OnClientDisconnectCallback -= value;
|
remove => ConnectionManager.OnClientDisconnectCallback -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The callback to invoke on any connection event. See <see cref="ConnectionEvent"/> and <see cref="ConnectionEventData"/>
|
||||||
|
/// for more info.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<NetworkManager, ConnectionEventData> OnConnectionEvent
|
||||||
|
{
|
||||||
|
add => ConnectionManager.OnConnectionEvent += value;
|
||||||
|
remove => ConnectionManager.OnConnectionEvent -= value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current host name we are connected to, used to validate certificate
|
/// The current host name we are connected to, used to validate certificate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -376,6 +477,24 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal IDeferredNetworkMessageManager DeferredMessageManager { get; private set; }
|
internal IDeferredNetworkMessageManager DeferredMessageManager { get; private set; }
|
||||||
|
|
||||||
|
// This erroneously tries to simplify these method references but the docs do not pick it up correctly
|
||||||
|
// because they try to resolve it on the field rather than the class of the same name.
|
||||||
|
#pragma warning disable IDE0001
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the various <see cref="SendTo"/> targets at runtime, as well as
|
||||||
|
/// runtime-bound targets like <see cref="Unity.Netcode.RpcTarget.Single"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group(NativeArray{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group(NativeList{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group(ulong[])"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Group{T}(T)"/>, <see cref="Unity.Netcode.RpcTarget.Not(ulong)"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeArray{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not(NativeList{ulong})"/>,
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not(ulong[])"/>, and
|
||||||
|
/// <see cref="Unity.Netcode.RpcTarget.Not{T}(T)"/>
|
||||||
|
/// </summary>
|
||||||
|
#pragma warning restore IDE0001
|
||||||
|
public RpcTarget RpcTarget;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the CustomMessagingManager for this NetworkManager
|
/// Gets the CustomMessagingManager for this NetworkManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -409,6 +528,19 @@ namespace Unity.Netcode
|
|||||||
internal NetworkConnectionManager ConnectionManager = new NetworkConnectionManager();
|
internal NetworkConnectionManager ConnectionManager = new NetworkConnectionManager();
|
||||||
internal NetworkMessageManager MessageManager = null;
|
internal NetworkMessageManager MessageManager = null;
|
||||||
|
|
||||||
|
internal struct Override<T>
|
||||||
|
{
|
||||||
|
private T m_Value;
|
||||||
|
public bool Overidden { get; private set; }
|
||||||
|
internal T Value
|
||||||
|
{
|
||||||
|
get { return Overidden ? m_Value : default(T); }
|
||||||
|
set { Overidden = true; m_Value = value; }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
internal Override<ushort> PortOverride;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
internal static INetworkManagerHelper NetworkManagerHelper;
|
internal static INetworkManagerHelper NetworkManagerHelper;
|
||||||
|
|
||||||
@@ -491,6 +623,15 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ModeChanged(PlayModeStateChange change)
|
||||||
|
{
|
||||||
|
if (IsListening && change == PlayModeStateChange.ExitingPlayMode)
|
||||||
|
{
|
||||||
|
// Make sure we are not holding onto anything in case domain reload is disabled
|
||||||
|
ShutdownInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -539,6 +680,9 @@ namespace Unity.Netcode
|
|||||||
NetworkConfig?.InitializePrefabs();
|
NetworkConfig?.InitializePrefabs();
|
||||||
|
|
||||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EditorApplication.playModeStateChanged += ModeChanged;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -581,15 +725,48 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport.
|
/// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport.
|
||||||
/// This should represent the transport's MTU size, minus any transport-level overhead.
|
/// This should represent the transport's default MTU size, minus any transport-level overhead.
|
||||||
|
/// This value will be used for any remote endpoints that haven't had per-endpoint MTUs set.
|
||||||
|
/// This value is also used as the size of the temporary buffer used when serializing
|
||||||
|
/// a single message (to avoid serializing multiple times when sending to multiple endpoints),
|
||||||
|
/// and thus should be large enough to ensure it can hold each message type.
|
||||||
|
/// This value defaults to 1296.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="size"></param>
|
/// <param name="size"></param>
|
||||||
public int MaximumTransmissionUnitSize
|
public int MaximumTransmissionUnitSize
|
||||||
{
|
{
|
||||||
set => MessageManager.NonFragmentedMessageMaxSize = value;
|
set => MessageManager.NonFragmentedMessageMaxSize = value & ~7; // Round down to nearest word aligned size
|
||||||
get => MessageManager.NonFragmentedMessageMaxSize;
|
get => MessageManager.NonFragmentedMessageMaxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the maximum transmission unit for a specific peer.
|
||||||
|
/// This determines the maximum size of a message batch that can be sent to that client.
|
||||||
|
/// If not set for any given client, <see cref="MaximumTransmissionUnitSize"/> will be used instead.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId"></param>
|
||||||
|
/// <param name="size"></param>
|
||||||
|
public void SetPeerMTU(ulong clientId, int size)
|
||||||
|
{
|
||||||
|
MessageManager.PeerMTUSizes[clientId] = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries the current MTU size for a client.
|
||||||
|
/// If no MTU has been set for that client, will return <see cref="MaximumTransmissionUnitSize"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int GetPeerMTU(ulong clientId)
|
||||||
|
{
|
||||||
|
if (MessageManager.PeerMTUSizes.TryGetValue(clientId, out var ret))
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageManager.NonFragmentedMessageMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery.
|
/// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery.
|
||||||
/// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size.
|
/// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size.
|
||||||
@@ -603,6 +780,12 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal void Initialize(bool server)
|
internal void Initialize(bool server)
|
||||||
{
|
{
|
||||||
|
// Make sure the ServerShutdownState is reset when initializing
|
||||||
|
if (server)
|
||||||
|
{
|
||||||
|
ServerShutdownState = ServerShutdownStates.None;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't allow the user to start a network session if the NetworkManager is
|
// Don't allow the user to start a network session if the NetworkManager is
|
||||||
// still parented under another GameObject
|
// still parented under another GameObject
|
||||||
if (NetworkManagerCheckForParent(true))
|
if (NetworkManagerCheckForParent(true))
|
||||||
@@ -610,6 +793,8 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ParseCommandLineOptions();
|
||||||
|
|
||||||
if (NetworkConfig.NetworkTransport == null)
|
if (NetworkConfig.NetworkTransport == null)
|
||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||||
@@ -666,6 +851,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
DeferredMessageManager = ComponentFactory.Create<IDeferredNetworkMessageManager>(this);
|
DeferredMessageManager = ComponentFactory.Create<IDeferredNetworkMessageManager>(this);
|
||||||
|
|
||||||
|
RpcTarget = new RpcTarget(this);
|
||||||
|
|
||||||
CustomMessagingManager = new CustomMessagingManager(this);
|
CustomMessagingManager = new CustomMessagingManager(this);
|
||||||
|
|
||||||
SceneManager = new NetworkSceneManager(this);
|
SceneManager = new NetworkSceneManager(this);
|
||||||
@@ -866,6 +1053,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
LocalClientId = ServerClientId;
|
LocalClientId = ServerClientId;
|
||||||
NetworkMetrics.SetConnectionId(LocalClientId);
|
NetworkMetrics.SetConnectionId(LocalClientId);
|
||||||
|
MessageManager.SetLocalClientId(LocalClientId);
|
||||||
|
|
||||||
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
|
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
|
||||||
{
|
{
|
||||||
@@ -944,11 +1132,6 @@ namespace Unity.Netcode
|
|||||||
MessageManager.StopProcessing = discardMessageQueue;
|
MessageManager.StopProcessing = discardMessageQueue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkConfig != null && NetworkConfig.NetworkTransport != null)
|
|
||||||
{
|
|
||||||
NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
|
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
|
||||||
@@ -973,6 +1156,9 @@ namespace Unity.Netcode
|
|||||||
DeferredMessageManager?.CleanupAllTriggers();
|
DeferredMessageManager?.CleanupAllTriggers();
|
||||||
CustomMessagingManager = null;
|
CustomMessagingManager = null;
|
||||||
|
|
||||||
|
RpcTarget?.Dispose();
|
||||||
|
RpcTarget = null;
|
||||||
|
|
||||||
BehaviourUpdater?.Shutdown();
|
BehaviourUpdater?.Shutdown();
|
||||||
BehaviourUpdater = null;
|
BehaviourUpdater = null;
|
||||||
|
|
||||||
@@ -1013,6 +1199,9 @@ namespace Unity.Netcode
|
|||||||
OnServerStopped?.Invoke(ConnectionManager.LocalClient.IsClient);
|
OnServerStopped?.Invoke(ConnectionManager.LocalClient.IsClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In the event shutdown is invoked within OnClientStopped or OnServerStopped, set it to false again
|
||||||
|
m_ShuttingDown = false;
|
||||||
|
|
||||||
// Reset the client's roles
|
// Reset the client's roles
|
||||||
ConnectionManager.LocalClient.SetRole(false, false);
|
ConnectionManager.LocalClient.SetRole(false, false);
|
||||||
|
|
||||||
@@ -1049,5 +1238,39 @@ namespace Unity.Netcode
|
|||||||
Singleton = null;
|
Singleton = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Command line options
|
||||||
|
private const string k_OverridePortArg = "-port";
|
||||||
|
|
||||||
|
private string GetArg(string[] commandLineArgs, string arg)
|
||||||
|
{
|
||||||
|
var argIndex = Array.IndexOf(commandLineArgs, arg);
|
||||||
|
if (argIndex >= 0 && argIndex < commandLineArgs.Length - 1)
|
||||||
|
{
|
||||||
|
return commandLineArgs[argIndex + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseArg<T>(string arg, ref Override<T> value)
|
||||||
|
{
|
||||||
|
if (GetArg(Environment.GetCommandLineArgs(), arg) is string argValue)
|
||||||
|
{
|
||||||
|
value.Value = (T)Convert.ChangeType(argValue, typeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseCommandLineOptions()
|
||||||
|
{
|
||||||
|
#if UNITY_SERVER && UNITY_DEDICATED_SERVER_ARGUMENTS_PRESENT
|
||||||
|
if ( UnityEngine.DedicatedServer.Arguments.Port != null)
|
||||||
|
{
|
||||||
|
PortOverride.Value = (ushort)UnityEngine.DedicatedServer.Arguments.Port;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ParseArg(k_OverridePortArg, ref PortOverride);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
#if UNITY_2021_2_OR_NEWER
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
#else
|
||||||
|
using UnityEditor.Experimental.SceneManagement;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -17,6 +26,22 @@ namespace Unity.Netcode
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
internal uint GlobalObjectIdHash;
|
internal uint GlobalObjectIdHash;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to track the source GlobalObjectIdHash value of the associated network prefab.
|
||||||
|
/// When an override exists or it is in-scene placed, GlobalObjectIdHash and PrefabGlobalObjectIdHash
|
||||||
|
/// will be different. The PrefabGlobalObjectIdHash value is what is used when sending a <see cref="CreateObjectMessage"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal uint PrefabGlobalObjectIdHash;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is the source prefab of an in-scene placed NetworkObject. This is not set for in-scene
|
||||||
|
/// placd NetworkObjects that are not prefab instances, dynamically spawned prefab instances,
|
||||||
|
/// or for network prefab assets.
|
||||||
|
/// </summary>
|
||||||
|
[HideInInspector]
|
||||||
|
[SerializeField]
|
||||||
|
internal uint InScenePlacedSourceGlobalObjectIdHash;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0
|
/// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -25,42 +50,205 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
foreach (var prefab in NetworkManager.NetworkConfig.Prefabs.Prefabs)
|
return GlobalObjectIdHash;
|
||||||
{
|
|
||||||
if (prefab.Prefab == gameObject)
|
|
||||||
{
|
|
||||||
return GlobalObjectIdHash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool m_IsPrefab;
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void OnValidate()
|
private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Object Types <see href="https://docs.unity3d.com/ScriptReference/GlobalObjectId.html"/>
|
||||||
|
/// Parameter 0 of <see cref="k_GlobalIdTemplate"/>
|
||||||
|
/// </summary>
|
||||||
|
// 0 = Null (when considered a null object type we can ignore)
|
||||||
|
// 1 = Imported Asset
|
||||||
|
// 2 = Scene Object
|
||||||
|
// 3 = Source Asset.
|
||||||
|
private const int k_NullObjectType = 0;
|
||||||
|
private const int k_ImportedAssetObjectType = 1;
|
||||||
|
private const int k_SceneObjectType = 2;
|
||||||
|
private const int k_SourceAssetObjectType = 3;
|
||||||
|
|
||||||
|
[ContextMenu("Refresh In-Scene Prefab Instances")]
|
||||||
|
internal void RefreshAllPrefabInstances()
|
||||||
{
|
{
|
||||||
GenerateGlobalObjectIdHash();
|
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
|
||||||
|
if (!PrefabUtility.IsPartOfAnyPrefab(this) || instanceGlobalId.identifierType != k_ImportedAssetObjectType)
|
||||||
|
{
|
||||||
|
EditorUtility.DisplayDialog("Network Prefab Assets Only", "This action can only be performed on a network prefab asset.", "Ok");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle updating the currently active scene
|
||||||
|
var networkObjects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||||
|
foreach (var networkObject in networkObjects)
|
||||||
|
{
|
||||||
|
networkObject.OnValidate();
|
||||||
|
}
|
||||||
|
NetworkObjectRefreshTool.ProcessActiveScene();
|
||||||
|
|
||||||
|
// Refresh all build settings scenes
|
||||||
|
var activeScene = SceneManager.GetActiveScene();
|
||||||
|
foreach (var editorScene in EditorBuildSettings.scenes)
|
||||||
|
{
|
||||||
|
// skip disabled scenes and the currently active scene
|
||||||
|
if (!editorScene.enabled || activeScene.path == editorScene.path)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Add the scene to be processed
|
||||||
|
NetworkObjectRefreshTool.ProcessScene(editorScene.path, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process all added scenes
|
||||||
|
NetworkObjectRefreshTool.ProcessScenes();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void GenerateGlobalObjectIdHash()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
|
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
|
||||||
if (UnityEditor.EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
|
if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode
|
// do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode
|
||||||
if (!UnityEditor.EditorApplication.isPlaying && UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
|
if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalObjectIdString = UnityEditor.GlobalObjectId.GetGlobalObjectIdSlow(this).ToString();
|
// Get a global object identifier for this network prefab
|
||||||
GlobalObjectIdHash = XXHash.Hash32(globalObjectIdString);
|
var globalId = GetGlobalId();
|
||||||
|
|
||||||
|
|
||||||
|
// if the identifier type is 0, then don't update the GlobalObjectIdHash
|
||||||
|
if (globalId.identifierType == k_NullObjectType)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldValue = GlobalObjectIdHash;
|
||||||
|
GlobalObjectIdHash = globalId.ToString().Hash32();
|
||||||
|
|
||||||
|
// If the GlobalObjectIdHash value changed, then mark the asset dirty
|
||||||
|
if (GlobalObjectIdHash != oldValue)
|
||||||
|
{
|
||||||
|
// Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed)
|
||||||
|
if (!IsEditingPrefab() && gameObject.scene.name != null && gameObject.scene.name != gameObject.name)
|
||||||
|
{
|
||||||
|
// Sanity check to make sure this is a scene placed object
|
||||||
|
if (globalId.identifierType != k_SceneObjectType)
|
||||||
|
{
|
||||||
|
// This should never happen, but in the event it does throw and error
|
||||||
|
Debug.LogError($"[{gameObject.name}] is detected as an in-scene placed object but its identifier is of type {globalId.identifierType}! **Report this error**");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a prefab instance
|
||||||
|
if (PrefabUtility.IsPartOfAnyPrefab(this))
|
||||||
|
{
|
||||||
|
// We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty)
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always check for in-scene placed to assure any previous version scene assets with in-scene place NetworkObjects gets updated
|
||||||
|
CheckForInScenePlaced();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsEditingPrefab()
|
||||||
|
{
|
||||||
|
// Check if we are directly editing the prefab
|
||||||
|
var stage = PrefabStageUtility.GetPrefabStage(gameObject);
|
||||||
|
|
||||||
|
// if we are not editing the prefab directly (or a sub-prefab), then return the object identifier
|
||||||
|
if (stage == null || stage.assetPath == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This checks to see if this NetworkObject is an in-scene placed prefab instance. If so it will
|
||||||
|
/// automatically find the source prefab asset's GlobalObjectIdHash value, assign it to
|
||||||
|
/// InScenePlacedSourceGlobalObjectIdHash and mark this as being in-scene placed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This NetworkObject is considered an in-scene placed prefab asset instance if it is:
|
||||||
|
/// - Part of a prefab
|
||||||
|
/// - Not being directly edited
|
||||||
|
/// - Within a valid scene that is part of the scenes in build list
|
||||||
|
/// (In-scene defined NetworkObjects that are not part of a prefab instance are excluded.)
|
||||||
|
/// </remarks>
|
||||||
|
private void CheckForInScenePlaced()
|
||||||
|
{
|
||||||
|
if (PrefabUtility.IsPartOfAnyPrefab(this) && !IsEditingPrefab() && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
|
||||||
|
{
|
||||||
|
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
|
||||||
|
var assetPath = AssetDatabase.GetAssetPath(prefab);
|
||||||
|
var sourceAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(assetPath);
|
||||||
|
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
|
||||||
|
{
|
||||||
|
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
IsSceneObject = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GlobalObjectId GetGlobalId()
|
||||||
|
{
|
||||||
|
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
|
||||||
|
|
||||||
|
// If not editing a prefab, then just use the generated id
|
||||||
|
if (!IsEditingPrefab())
|
||||||
|
{
|
||||||
|
return instanceGlobalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the asset doesn't exist at the given path, then return the object identifier
|
||||||
|
var prefabStageAssetPath = PrefabStageUtility.GetPrefabStage(gameObject).assetPath;
|
||||||
|
// If (for some reason) the asset path is null return the generated id
|
||||||
|
if (prefabStageAssetPath == null)
|
||||||
|
{
|
||||||
|
return instanceGlobalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var theAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(prefabStageAssetPath);
|
||||||
|
// If there is no asset at that path (for some odd/edge case reason), return the generated id
|
||||||
|
if (theAsset == null)
|
||||||
|
{
|
||||||
|
return instanceGlobalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't get the asset GUID and/or the file identifier, then return the object identifier
|
||||||
|
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(theAsset, out var guid, out long localFileId))
|
||||||
|
{
|
||||||
|
return instanceGlobalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: If we reached this point, then we are most likely opening a prefab to edit.
|
||||||
|
// The instanceGlobalId will be constructed as if it is a scene object, however when it
|
||||||
|
// is serialized its value will be treated as a file asset (the "why" to the below code).
|
||||||
|
|
||||||
|
// Construct an imported asset identifier with the type being a source asset object type
|
||||||
|
var prefabGlobalIdText = string.Format(k_GlobalIdTemplate, k_SourceAssetObjectType, guid, (ulong)localFileId, 0);
|
||||||
|
|
||||||
|
// If we can't parse the result log an error and return the instanceGlobalId
|
||||||
|
if (!GlobalObjectId.TryParse(prefabGlobalIdText, out var prefabGlobalId))
|
||||||
|
{
|
||||||
|
Debug.LogError($"[GlobalObjectId Gen] Failed to parse ({prefabGlobalIdText}) returning default ({instanceGlobalId})! ** Please Report This Error **");
|
||||||
|
return instanceGlobalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, return the constructed identifier for the source prefab asset
|
||||||
|
return prefabGlobalId;
|
||||||
}
|
}
|
||||||
#endif // UNITY_EDITOR
|
#endif // UNITY_EDITOR
|
||||||
|
|
||||||
@@ -553,7 +741,7 @@ namespace Unity.Netcode
|
|||||||
// Since we still have a session connection, log locally and on the server to inform user of this issue.
|
// Since we still have a session connection, log locally and on the server to inform user of this issue.
|
||||||
if (NetworkManager.LogLevel <= LogLevel.Error)
|
if (NetworkManager.LogLevel <= LogLevel.Error)
|
||||||
{
|
{
|
||||||
NetworkLog.LogErrorServer($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
|
NetworkLog.LogErrorServer($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -570,7 +758,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
|
internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
|
||||||
{
|
{
|
||||||
if (!NetworkManager.IsListening)
|
if (!NetworkManager.IsListening)
|
||||||
{
|
{
|
||||||
@@ -593,6 +781,78 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This invokes <see cref="NetworkSpawnManager.InstantiateAndSpawn(NetworkObject, ulong, bool, bool, bool, Vector3, Quaternion)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkPrefab">The NetworkPrefab to instantiate and spawn.</param>
|
||||||
|
/// <param name="networkManager">The local instance of the NetworkManager connected to an session in progress.</param>
|
||||||
|
/// <param name="ownerClientId">The owner of the <see cref="NetworkObject"/> instance (defaults to server).</param>
|
||||||
|
/// <param name="destroyWithScene">Whether the <see cref="NetworkObject"/> instance will be destroyed when the scene it is located within is unloaded (default is false).</param>
|
||||||
|
/// <param name="isPlayerObject">Whether the <see cref="NetworkObject"/> instance is a player object or not (default is false).</param>
|
||||||
|
/// <param name="forceOverride">Whether you want to force spawning the override when running as a host or server or if you want it to spawn the override for host mode and
|
||||||
|
/// the source prefab for server. If there is an override, clients always spawn that as opposed to the source prefab (defaults to false). </param>
|
||||||
|
/// <param name="position">The starting poisiton of the <see cref="NetworkObject"/> instance.</param>
|
||||||
|
/// <param name="rotation">The starting rotation of the <see cref="NetworkObject"/> instance.</param>
|
||||||
|
/// <returns>The newly instantiated and spawned <see cref="NetworkObject"/> prefab instance.</returns>
|
||||||
|
public static NetworkObject InstantiateAndSpawn(GameObject networkPrefab, NetworkManager networkManager, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default)
|
||||||
|
{
|
||||||
|
var networkObject = networkPrefab.GetComponent<NetworkObject>();
|
||||||
|
if (networkObject == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"The {nameof(NetworkPrefab)} {networkPrefab.name} does not have a {nameof(NetworkObject)} component!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return networkObject.InstantiateAndSpawn(networkManager, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This invokes <see cref="NetworkSpawnManager.InstantiateAndSpawn(NetworkObject, ulong, bool, bool, bool, Vector3, Quaternion)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkManager">The local instance of the NetworkManager connected to an session in progress.</param>
|
||||||
|
/// <param name="ownerClientId">The owner of the <see cref="NetworkObject"/> instance (defaults to server).</param>
|
||||||
|
/// <param name="destroyWithScene">Whether the <see cref="NetworkObject"/> instance will be destroyed when the scene it is located within is unloaded (default is false).</param>
|
||||||
|
/// <param name="isPlayerObject">Whether the <see cref="NetworkObject"/> instance is a player object or not (default is false).</param>
|
||||||
|
/// <param name="forceOverride">Whether you want to force spawning the override when running as a host or server or if you want it to spawn the override for host mode and
|
||||||
|
/// the source prefab for server. If there is an override, clients always spawn that as opposed to the source prefab (defaults to false). </param>
|
||||||
|
/// <param name="position">The starting poisiton of the <see cref="NetworkObject"/> instance.</param>
|
||||||
|
/// <param name="rotation">The starting rotation of the <see cref="NetworkObject"/> instance.</param>
|
||||||
|
/// <returns>The newly instantiated and spawned <see cref="NetworkObject"/> prefab instance.</returns>
|
||||||
|
public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default)
|
||||||
|
{
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NetworkManagerNull]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkManager.IsListening)
|
||||||
|
{
|
||||||
|
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NoActiveSession]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkManager.IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotAuthority]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it is actually a valid prefab
|
||||||
|
if (!NetworkManager.NetworkConfig.Prefabs.Contains(gameObject))
|
||||||
|
{
|
||||||
|
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetworkManager.SpawnManager.InstantiateAndSpawnNoParameterChecks(this, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spawns this <see cref="NetworkObject"/> across the network. Can only be called from the Server
|
/// Spawns this <see cref="NetworkObject"/> across the network. Can only be called from the Server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -684,6 +944,21 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void InvokeOwnershipChanged(ulong previous, ulong next)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||||
|
{
|
||||||
|
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
ChildNetworkBehaviours[i].InternalOnOwnershipChanged(previous, next);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
|
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||||
@@ -804,20 +1079,21 @@ namespace Unity.Netcode
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NetworkManager.IsServer)
|
if (!NetworkManager.IsServer && !NetworkManager.ShutdownInProgress)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsSpawned)
|
// If the parent is not null fail only if either of the two is true:
|
||||||
|
// - This instance is spawned and the parent is not.
|
||||||
|
// - This instance is not spawned and the parent is.
|
||||||
|
// Basically, don't allow parenting when either the child or parent is not spawned.
|
||||||
|
// Caveat: if the parent is null then we can allow parenting whether the instance is or is not spawned.
|
||||||
|
if (parent != null && (IsSpawned ^ parent.IsSpawned))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent != null && !parent.IsSpawned)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m_CachedWorldPositionStays = worldPositionStays;
|
m_CachedWorldPositionStays = worldPositionStays;
|
||||||
|
|
||||||
if (parent == null)
|
if (parent == null)
|
||||||
@@ -853,15 +1129,36 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (!NetworkManager.IsServer)
|
if (!NetworkManager.IsServer)
|
||||||
{
|
{
|
||||||
transform.parent = m_CachedParent;
|
// Log exception if we are a client and not shutting down.
|
||||||
Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s"));
|
if (!NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
transform.parent = m_CachedParent;
|
||||||
|
Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s"));
|
||||||
|
}
|
||||||
|
else // Otherwise, if we are removing a parent then go ahead and allow parenting to occur
|
||||||
|
if (transform.parent == null)
|
||||||
|
{
|
||||||
|
m_LatestParent = null;
|
||||||
|
m_CachedParent = null;
|
||||||
|
InvokeBehaviourOnNetworkObjectParentChanged(null);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else // Otherwise, on the serer side if this instance is not spawned...
|
||||||
if (!IsSpawned)
|
if (!IsSpawned)
|
||||||
{
|
{
|
||||||
transform.parent = m_CachedParent;
|
// ,,,and we are removing the parent, then go ahead and allow parenting to occur
|
||||||
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
|
if (transform.parent == null)
|
||||||
|
{
|
||||||
|
m_LatestParent = null;
|
||||||
|
m_CachedParent = null;
|
||||||
|
InvokeBehaviourOnNetworkObjectParentChanged(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transform.parent = m_CachedParent;
|
||||||
|
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var removeParent = false;
|
var removeParent = false;
|
||||||
@@ -1190,7 +1487,7 @@ namespace Unity.Netcode
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
|
public NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
|
||||||
{
|
{
|
||||||
if (index >= ChildNetworkBehaviours.Count)
|
if (index >= ChildNetworkBehaviours.Count)
|
||||||
{
|
{
|
||||||
@@ -1704,16 +2001,39 @@ namespace Unity.Netcode
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal uint HostCheckForGlobalObjectIdHashOverride()
|
internal uint HostCheckForGlobalObjectIdHashOverride()
|
||||||
{
|
{
|
||||||
if (NetworkManager.IsHost)
|
if (NetworkManager.IsServer)
|
||||||
{
|
{
|
||||||
if (NetworkManager.PrefabHandler.ContainsHandler(this))
|
if (NetworkManager.PrefabHandler.ContainsHandler(this))
|
||||||
{
|
{
|
||||||
var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash);
|
var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash);
|
||||||
return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash;
|
return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash;
|
||||||
}
|
}
|
||||||
if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.TryGetValue(GlobalObjectIdHash, out uint hash))
|
|
||||||
|
// If scene management is disabled and this is an in-scene placed NetworkObject then go ahead
|
||||||
|
// and send the InScenePlacedSourcePrefab's GlobalObjectIdHash value (i.e. what to dynamically spawn)
|
||||||
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0)
|
||||||
{
|
{
|
||||||
return hash;
|
return InScenePlacedSourceGlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the PrefabGlobalObjectIdHash is a non-zero value and the GlobalObjectIdHash value is
|
||||||
|
// different from the PrefabGlobalObjectIdHash value, then the NetworkObject instance is
|
||||||
|
// an override for the original network prefab (i.e. PrefabGlobalObjectIdHash)
|
||||||
|
if (!IsSceneObject.Value && GlobalObjectIdHash != PrefabGlobalObjectIdHash)
|
||||||
|
{
|
||||||
|
// If the PrefabGlobalObjectIdHash is already populated (i.e. InstantiateAndSpawn used), then return this
|
||||||
|
if (PrefabGlobalObjectIdHash != 0)
|
||||||
|
{
|
||||||
|
return PrefabGlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For legacy manual instantiation and spawning, check the OverrideToNetworkPrefab for a possible match
|
||||||
|
if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash))
|
||||||
|
{
|
||||||
|
return NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab[GlobalObjectIdHash];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
118
Runtime/Core/NetworkObjectRefreshTool.cs
Normal file
118
Runtime/Core/NetworkObjectRefreshTool.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a helper tool to update all in-scene placed instances of a prefab that
|
||||||
|
/// originally did not have a NetworkObject component but one was added to the prefab
|
||||||
|
/// later.
|
||||||
|
/// </summary>
|
||||||
|
internal class NetworkObjectRefreshTool
|
||||||
|
{
|
||||||
|
private static List<string> s_ScenesToUpdate = new List<string>();
|
||||||
|
private static bool s_ProcessScenes;
|
||||||
|
private static bool s_CloseScenes;
|
||||||
|
|
||||||
|
internal static Action AllScenesProcessed;
|
||||||
|
|
||||||
|
internal static void ProcessScene(string scenePath, bool processScenes = true)
|
||||||
|
{
|
||||||
|
if (!s_ScenesToUpdate.Contains(scenePath))
|
||||||
|
{
|
||||||
|
if (s_ScenesToUpdate.Count == 0)
|
||||||
|
{
|
||||||
|
EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
|
||||||
|
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
|
||||||
|
}
|
||||||
|
s_ScenesToUpdate.Add(scenePath);
|
||||||
|
}
|
||||||
|
s_ProcessScenes = processScenes;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ProcessActiveScene()
|
||||||
|
{
|
||||||
|
var activeScene = SceneManager.GetActiveScene();
|
||||||
|
if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes)
|
||||||
|
{
|
||||||
|
SceneOpened(activeScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ProcessScenes()
|
||||||
|
{
|
||||||
|
if (s_ScenesToUpdate.Count != 0)
|
||||||
|
{
|
||||||
|
s_CloseScenes = true;
|
||||||
|
var scenePath = s_ScenesToUpdate.First();
|
||||||
|
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_CloseScenes = false;
|
||||||
|
EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved;
|
||||||
|
EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened;
|
||||||
|
AllScenesProcessed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FinishedProcessingScene(Scene scene, bool refreshed = false)
|
||||||
|
{
|
||||||
|
if (s_ScenesToUpdate.Contains(scene.path))
|
||||||
|
{
|
||||||
|
// Provide a log of all scenes that were modified to the user
|
||||||
|
if (refreshed)
|
||||||
|
{
|
||||||
|
Debug.Log($"Refreshed and saved updates to scene: {scene.name}");
|
||||||
|
}
|
||||||
|
s_ProcessScenes = false;
|
||||||
|
s_ScenesToUpdate.Remove(scene.path);
|
||||||
|
|
||||||
|
if (scene != SceneManager.GetActiveScene())
|
||||||
|
{
|
||||||
|
EditorSceneManager.CloseScene(scene, s_CloseScenes);
|
||||||
|
}
|
||||||
|
ProcessScenes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EditorSceneManager_sceneSaved(Scene scene)
|
||||||
|
{
|
||||||
|
FinishedProcessingScene(scene, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SceneOpened(Scene scene)
|
||||||
|
{
|
||||||
|
if (s_ScenesToUpdate.Contains(scene.path))
|
||||||
|
{
|
||||||
|
if (s_ProcessScenes)
|
||||||
|
{
|
||||||
|
if (!EditorSceneManager.MarkSceneDirty(scene))
|
||||||
|
{
|
||||||
|
Debug.Log($"Scene {scene.name} did not get marked as dirty!");
|
||||||
|
FinishedProcessingScene(scene);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorSceneManager.SaveScene(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FinishedProcessingScene(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EditorSceneManager_sceneOpened(Scene scene, OpenSceneMode mode)
|
||||||
|
{
|
||||||
|
SceneOpened(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // UNITY_EDITOR
|
||||||
11
Runtime/Core/NetworkObjectRefreshTool.cs.meta
Normal file
11
Runtime/Core/NetworkObjectRefreshTool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d24d5e8371c3cca4890e2713bdeda288
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -113,6 +113,7 @@ namespace Unity.Netcode
|
|||||||
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
||||||
if (triggers.TryGetValue(key, out var triggerInfo))
|
if (triggers.TryGetValue(key, out var triggerInfo))
|
||||||
{
|
{
|
||||||
|
triggers.Remove(key);
|
||||||
foreach (var deferredMessage in triggerInfo.TriggerData)
|
foreach (var deferredMessage in triggerInfo.TriggerData)
|
||||||
{
|
{
|
||||||
// Reader will be disposed within HandleMessage
|
// Reader will be disposed within HandleMessage
|
||||||
@@ -120,7 +121,6 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
triggerInfo.TriggerData.Dispose();
|
triggerInfo.TriggerData.Dispose();
|
||||||
triggers.Remove(key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a generic parameter in this class as a type that should be serialized through
|
||||||
|
/// <see cref="NetworkVariableSerialization{T}"/>. This enables the use of the following methods to support
|
||||||
|
/// serialization within a Network Variable type:
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Read"/>
|
||||||
|
/// <br/>
|
||||||
|
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Write"/>
|
||||||
|
/// <br/>
|
||||||
|
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.AreEqual"/>
|
||||||
|
/// <br/>
|
||||||
|
/// <see cref="NetworkVariableSerialization{T}"/>.<see cref="NetworkVariableSerialization{T}.Duplicate"/>
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// The parameter is indicated by index (and is 0-indexed); for example:
|
||||||
|
/// <br/>
|
||||||
|
/// <code>
|
||||||
|
/// [SerializesGenericParameter(1)]
|
||||||
|
/// public class MyClass<TTypeOne, TTypeTwo>
|
||||||
|
/// {
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// <br/>
|
||||||
|
/// This tells the code generation for <see cref="NetworkVariableSerialization{T}"/> to generate
|
||||||
|
/// serialized code for <b>TTypeTwo</b> (generic parameter 1).
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Note that this is primarily intended to support subtypes of <see cref="NetworkVariableBase"/>,
|
||||||
|
/// and as such, the type resolution is done by examining fields of <see cref="NetworkBehaviour"/>
|
||||||
|
/// subclasses. If your type is not used in a <see cref="NetworkBehaviour"/>, the codegen will
|
||||||
|
/// not find the types, even with this attribute.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// This attribute is properly inherited by subclasses. For example:
|
||||||
|
/// <br/>
|
||||||
|
/// <code>
|
||||||
|
/// [SerializesGenericParameter(0)]
|
||||||
|
/// public class MyClass<T>
|
||||||
|
/// {
|
||||||
|
/// }
|
||||||
|
/// <br/>
|
||||||
|
/// public class MySubclass1 : MyClass<Foo>
|
||||||
|
/// {
|
||||||
|
/// }
|
||||||
|
/// <br/>
|
||||||
|
/// public class MySubclass2<T> : MyClass<T>
|
||||||
|
/// {
|
||||||
|
/// }
|
||||||
|
/// <br/>
|
||||||
|
/// [SerializesGenericParameter(1)]
|
||||||
|
/// public class MySubclass3<TTypeOne, TTypeTwo> : MyClass<TTypeOne>
|
||||||
|
/// {
|
||||||
|
/// }
|
||||||
|
/// <br/>
|
||||||
|
/// public class MyBehaviour : NetworkBehaviour
|
||||||
|
/// {
|
||||||
|
/// public MySubclass1 TheValue;
|
||||||
|
/// public MySubclass2<Bar> TheValue;
|
||||||
|
/// public MySubclass3<Baz, Qux> TheValue;
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// <br/>
|
||||||
|
/// The above code will trigger generation of serialization code for <b>Foo</b> (passed directly to the
|
||||||
|
/// base class), <b>Bar</b> (passed indirectly to the base class), <b>Baz</b> (passed indirectly to the base class),
|
||||||
|
/// and <b>Qux</b> (marked as serializable in the subclass).
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
|
||||||
|
public class GenerateSerializationForGenericParameterAttribute : Attribute
|
||||||
|
{
|
||||||
|
internal int ParameterIndex;
|
||||||
|
|
||||||
|
public GenerateSerializationForGenericParameterAttribute(int parameterIndex)
|
||||||
|
{
|
||||||
|
ParameterIndex = parameterIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 18cdaa9c2f6446279b0c5948fcd34eec
|
||||||
|
timeCreated: 1694029524
|
||||||
26
Runtime/Messaging/GenerateSerializationForTypeAttribute.cs
Normal file
26
Runtime/Messaging/GenerateSerializationForTypeAttribute.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a specific type that needs serialization to be generated by codegen.
|
||||||
|
/// This is only needed in special circumstances where manual serialization is being done.
|
||||||
|
/// If you are making a generic network variable-style class, use <see cref="GenerateSerializationForGenericParameterAttribute"/>.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// This attribute can be attached to any class or method anywhere in the codebase and
|
||||||
|
/// will trigger codegen to generate serialization code for the provided type. It only needs
|
||||||
|
/// to be included once type per codebase, but including it multiple times for the same type
|
||||||
|
/// is safe.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method, AllowMultiple = true)]
|
||||||
|
public class GenerateSerializationForTypeAttribute : Attribute
|
||||||
|
{
|
||||||
|
internal Type Type;
|
||||||
|
|
||||||
|
public GenerateSerializationForTypeAttribute(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1bd80306706f4054b9ba514a72076df5
|
||||||
|
timeCreated: 1694103021
|
||||||
@@ -6,6 +6,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
OnSpawn,
|
OnSpawn,
|
||||||
OnAddPrefab,
|
OnAddPrefab,
|
||||||
|
OnNextFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -13,5 +16,24 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
return __network_message_types;
|
return __network_message_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
[InitializeOnLoadMethod]
|
||||||
|
public static void NotifyOnPlayStateChange()
|
||||||
|
{
|
||||||
|
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnPlayModeStateChanged(PlayModeStateChange change)
|
||||||
|
{
|
||||||
|
if (change == PlayModeStateChange.ExitingPlayMode)
|
||||||
|
{
|
||||||
|
// Clear out the network message types, because ILPP-generated RuntimeInitializeOnLoad code will
|
||||||
|
// run again and add more messages to it.
|
||||||
|
__network_message_types.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
|
||||||
|
|
||||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
Runtime/Messaging/Messages/ClientConnectedMessage.cs
Normal file
35
Runtime/Messaging/Messages/ClientConnectedMessage.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal struct ClientConnectedMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
|
{
|
||||||
|
public int Version => 0;
|
||||||
|
|
||||||
|
public ulong ClientId;
|
||||||
|
|
||||||
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
|
{
|
||||||
|
BytePacker.WriteValueBitPacked(writer, ClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||||
|
{
|
||||||
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
|
if (!networkManager.IsClient)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ByteUnpacker.ReadValueBitPacked(reader, out ClientId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ref NetworkContext context)
|
||||||
|
{
|
||||||
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
|
networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId);
|
||||||
|
if (networkManager.IsConnectedClient)
|
||||||
|
{
|
||||||
|
networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 158454105806474cba54a4ea5a0bfb12
|
||||||
|
timeCreated: 1697836112
|
||||||
35
Runtime/Messaging/Messages/ClientDisconnectedMessage.cs
Normal file
35
Runtime/Messaging/Messages/ClientDisconnectedMessage.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal struct ClientDisconnectedMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||||
|
{
|
||||||
|
public int Version => 0;
|
||||||
|
|
||||||
|
public ulong ClientId;
|
||||||
|
|
||||||
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
|
{
|
||||||
|
BytePacker.WriteValueBitPacked(writer, ClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||||
|
{
|
||||||
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
|
if (!networkManager.IsClient)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ByteUnpacker.ReadValueBitPacked(reader, out ClientId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ref NetworkContext context)
|
||||||
|
{
|
||||||
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
|
networkManager.ConnectionManager.ConnectedClientIds.Remove(ClientId);
|
||||||
|
if (networkManager.IsConnectedClient)
|
||||||
|
{
|
||||||
|
networkManager.ConnectionManager.InvokeOnPeerDisconnectedCallback(ClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8f91296c8e5f40b1a2a03d74a31526b6
|
||||||
|
timeCreated: 1697836161
|
||||||
@@ -5,7 +5,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
internal struct ConnectionApprovedMessage : INetworkMessage
|
internal struct ConnectionApprovedMessage : INetworkMessage
|
||||||
{
|
{
|
||||||
public int Version => 0;
|
private const int k_VersionAddClientIds = 1;
|
||||||
|
public int Version => k_VersionAddClientIds;
|
||||||
|
|
||||||
public ulong OwnerClientId;
|
public ulong OwnerClientId;
|
||||||
public int NetworkTick;
|
public int NetworkTick;
|
||||||
@@ -17,6 +18,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
public NativeArray<MessageVersionData> MessageVersions;
|
public NativeArray<MessageVersionData> MessageVersions;
|
||||||
|
|
||||||
|
public NativeArray<ulong> ConnectedClientIds;
|
||||||
|
|
||||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
{
|
{
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -36,7 +39,15 @@ namespace Unity.Netcode
|
|||||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||||
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
||||||
|
|
||||||
|
if (targetVersion >= k_VersionAddClientIds)
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(ConnectedClientIds);
|
||||||
|
}
|
||||||
|
|
||||||
uint sceneObjectCount = 0;
|
uint sceneObjectCount = 0;
|
||||||
|
|
||||||
|
// When SpawnedObjectsList is not null then scene management is disabled. Provide a list of
|
||||||
|
// all observed and spawned NetworkObjects that the approved client needs to synchronize.
|
||||||
if (SpawnedObjectsList != null)
|
if (SpawnedObjectsList != null)
|
||||||
{
|
{
|
||||||
var pos = writer.Position;
|
var pos = writer.Position;
|
||||||
@@ -45,7 +56,7 @@ namespace Unity.Netcode
|
|||||||
// Serialize NetworkVariable data
|
// Serialize NetworkVariable data
|
||||||
foreach (var sobj in SpawnedObjectsList)
|
foreach (var sobj in SpawnedObjectsList)
|
||||||
{
|
{
|
||||||
if (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))
|
if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId)))
|
||||||
{
|
{
|
||||||
sobj.Observers.Add(OwnerClientId);
|
sobj.Observers.Add(OwnerClientId);
|
||||||
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);
|
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);
|
||||||
@@ -103,6 +114,16 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
||||||
|
|
||||||
|
if (receivedMessageVersion >= k_VersionAddClientIds)
|
||||||
|
{
|
||||||
|
reader.ReadValueSafe(out ConnectedClientIds, Allocator.TempJob);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConnectedClientIds = new NativeArray<ulong>(0, Allocator.TempJob);
|
||||||
|
}
|
||||||
|
|
||||||
m_ReceivedSceneObjectData = reader;
|
m_ReceivedSceneObjectData = reader;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -111,6 +132,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
var networkManager = (NetworkManager)context.SystemOwner;
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
networkManager.LocalClientId = OwnerClientId;
|
networkManager.LocalClientId = OwnerClientId;
|
||||||
|
networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId);
|
||||||
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
|
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
|
||||||
|
|
||||||
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
|
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
|
||||||
@@ -123,6 +145,12 @@ namespace Unity.Netcode
|
|||||||
// Stop the client-side approval timeout coroutine since we are approved.
|
// Stop the client-side approval timeout coroutine since we are approved.
|
||||||
networkManager.ConnectionManager.StopClientApprovalCoroutine();
|
networkManager.ConnectionManager.StopClientApprovalCoroutine();
|
||||||
|
|
||||||
|
networkManager.ConnectionManager.ConnectedClientIds.Clear();
|
||||||
|
foreach (var clientId in ConnectedClientIds)
|
||||||
|
{
|
||||||
|
networkManager.ConnectionManager.ConnectedClientIds.Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
|
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
|
||||||
if (!networkManager.NetworkConfig.EnableSceneManagement)
|
if (!networkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
@@ -143,6 +171,8 @@ namespace Unity.Netcode
|
|||||||
// When scene management is disabled we notify after everything is synchronized
|
// When scene management is disabled we notify after everything is synchronized
|
||||||
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
|
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectedClientIds.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
internal struct CreateObjectMessage : INetworkMessage
|
internal struct CreateObjectMessage : INetworkMessage
|
||||||
@@ -34,9 +35,29 @@ namespace Unity.Netcode
|
|||||||
public void Handle(ref NetworkContext context)
|
public void Handle(ref NetworkContext context)
|
||||||
{
|
{
|
||||||
var networkManager = (NetworkManager)context.SystemOwner;
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, m_ReceivedNetworkVariableData, networkManager);
|
// If a client receives a create object message and it is still synchronizing, then defer the object creation until it has finished synchronizing
|
||||||
|
if (networkManager.SceneManager.ShouldDeferCreateObject())
|
||||||
|
{
|
||||||
|
networkManager.SceneManager.DeferCreateObject(context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CreateObject(ref networkManager, context.SenderId, context.MessageSize, ObjectInfo, m_ReceivedNetworkVariableData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
networkManager.NetworkMetrics.TrackObjectSpawnReceived(context.SenderId, networkObject, context.MessageSize);
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static void CreateObject(ref NetworkManager networkManager, ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader networkVariableData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var networkObject = NetworkObject.AddSceneObject(sceneObject, networkVariableData, networkManager);
|
||||||
|
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
public void Handle(ref NetworkContext context)
|
public void Handle(ref NetworkContext context)
|
||||||
{
|
{
|
||||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize);
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
|
if (!networkManager.ShutdownInProgress && networkManager.CustomMessagingManager != null)
|
||||||
|
{
|
||||||
|
networkManager.CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
Runtime/Messaging/Messages/ProxyMessage.cs
Normal file
70
Runtime/Messaging/Messages/ProxyMessage.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Collections;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal struct ProxyMessage : INetworkMessage
|
||||||
|
{
|
||||||
|
public NativeArray<ulong> TargetClientIds;
|
||||||
|
public NetworkDelivery Delivery;
|
||||||
|
public RpcMessage WrappedMessage;
|
||||||
|
|
||||||
|
// Version of ProxyMessage and RpcMessage must always match.
|
||||||
|
// If ProxyMessage needs to change, increment RpcMessage's version
|
||||||
|
public int Version => new RpcMessage().Version;
|
||||||
|
|
||||||
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(TargetClientIds);
|
||||||
|
BytePacker.WriteValuePacked(writer, Delivery);
|
||||||
|
WrappedMessage.Serialize(writer, targetVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||||
|
{
|
||||||
|
reader.ReadValueSafe(out TargetClientIds, Allocator.Temp);
|
||||||
|
ByteUnpacker.ReadValuePacked(reader, out Delivery);
|
||||||
|
WrappedMessage = new RpcMessage();
|
||||||
|
WrappedMessage.Deserialize(reader, ref context, receivedMessageVersion);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Handle(ref NetworkContext context)
|
||||||
|
{
|
||||||
|
|
||||||
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
|
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var observers = networkObject.Observers;
|
||||||
|
|
||||||
|
var nonServerIds = new NativeList<ulong>(Allocator.Temp);
|
||||||
|
for (var i = 0; i < TargetClientIds.Length; ++i)
|
||||||
|
{
|
||||||
|
if (!observers.Contains(TargetClientIds[i]))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TargetClientIds[i] == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
WrappedMessage.Handle(ref context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nonServerIds.Add(TargetClientIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedMessage.WriteBuffer = new FastBufferWriter(WrappedMessage.ReadBuffer.Length, Allocator.Temp);
|
||||||
|
|
||||||
|
using (WrappedMessage.WriteBuffer)
|
||||||
|
{
|
||||||
|
WrappedMessage.WriteBuffer.WriteBytesSafe(WrappedMessage.ReadBuffer.GetUnsafePtr(), WrappedMessage.ReadBuffer.Length);
|
||||||
|
networkManager.MessageManager.SendMessage(ref WrappedMessage, Delivery, nonServerIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/Messages/ProxyMessage.cs.meta
Normal file
3
Runtime/Messaging/Messages/ProxyMessage.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e9ee0457d5b740b38dfe6542658fb522
|
||||||
|
timeCreated: 1697825043
|
||||||
@@ -34,7 +34,7 @@ namespace Unity.Netcode
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId))
|
if (!NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()].ContainsKey(metadata.NetworkRpcMethodId))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ namespace Unity.Netcode
|
|||||||
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
|
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
|
||||||
|
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
|
if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||||
{
|
{
|
||||||
networkManager.NetworkMetrics.TrackRpcReceived(
|
networkManager.NetworkMetrics.TrackRpcReceived(
|
||||||
context.SenderId,
|
context.SenderId,
|
||||||
@@ -67,7 +67,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
|
NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -75,7 +75,7 @@ namespace Unity.Netcode
|
|||||||
if (networkManager.LogLevel == LogLevel.Developer)
|
if (networkManager.LogLevel == LogLevel.Developer)
|
||||||
{
|
{
|
||||||
Debug.Log($"RPC Table Contents");
|
Debug.Log($"RPC Table Contents");
|
||||||
foreach (var entry in NetworkManager.__rpc_func_table)
|
foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()])
|
||||||
{
|
{
|
||||||
Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
|
Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
|
||||||
}
|
}
|
||||||
@@ -159,4 +159,42 @@ namespace Unity.Netcode
|
|||||||
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
|
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal struct RpcMessage : INetworkMessage
|
||||||
|
{
|
||||||
|
public int Version => 0;
|
||||||
|
|
||||||
|
public RpcMetadata Metadata;
|
||||||
|
public ulong SenderClientId;
|
||||||
|
|
||||||
|
public FastBufferWriter WriteBuffer;
|
||||||
|
public FastBufferReader ReadBuffer;
|
||||||
|
|
||||||
|
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
|
{
|
||||||
|
BytePacker.WriteValuePacked(writer, SenderClientId);
|
||||||
|
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||||
|
{
|
||||||
|
ByteUnpacker.ReadValuePacked(reader, out SenderClientId);
|
||||||
|
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ref NetworkContext context)
|
||||||
|
{
|
||||||
|
var rpcParams = new __RpcParams
|
||||||
|
{
|
||||||
|
Ext = new RpcParams
|
||||||
|
{
|
||||||
|
Receive = new RpcReceiveParams
|
||||||
|
{
|
||||||
|
SenderClientId = SenderClientId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Magic;
|
public ushort Magic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of messages in the batch.
|
||||||
|
/// </summary>
|
||||||
|
public ushort BatchCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of bytes in the batch.
|
/// Total number of bytes in the batch.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -22,9 +27,5 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong BatchHash;
|
public ulong BatchHash;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Total number of messages in the batch.
|
|
||||||
/// </summary>
|
|
||||||
public ushort BatchCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Unity.Netcode.Transports.UTP;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -56,7 +57,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
{
|
{
|
||||||
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
|
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
|
||||||
|
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. {transportErrorMsg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -66,7 +68,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
{
|
{
|
||||||
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
|
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -76,7 +78,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
{
|
{
|
||||||
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
|
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
|
||||||
|
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. {transportErrorMsg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -88,7 +91,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
{
|
{
|
||||||
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
|
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
|
||||||
|
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. {transportErrorMsg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -98,7 +102,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||||
{
|
{
|
||||||
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
|
var transportErrorMsg = GetTransportErrorMessage(messageContent, m_NetworkManager);
|
||||||
|
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. {transportErrorMsg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -108,6 +113,28 @@ namespace Unity.Netcode
|
|||||||
return !m_NetworkManager.MessageManager.StopProcessing;
|
return !m_NetworkManager.MessageManager.StopProcessing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetTransportErrorMessage(FastBufferReader messageContent, NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
if (networkManager.NetworkConfig.NetworkTransport is not UnityTransport)
|
||||||
|
{
|
||||||
|
return $"NetworkTransport: {networkManager.NetworkConfig.NetworkTransport.GetType()}. Please report this to the maintainer of transport layer.";
|
||||||
|
}
|
||||||
|
|
||||||
|
var transportVersion = GetTransportVersion(networkManager);
|
||||||
|
return $"{transportVersion}. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {NetworkMessageManager.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetTransportVersion(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
var transportVersion = "NetworkTransport: " + networkManager.NetworkConfig.NetworkTransport.GetType();
|
||||||
|
if (networkManager.NetworkConfig.NetworkTransport is UnityTransport unityTransport)
|
||||||
|
{
|
||||||
|
transportVersion += " UnityTransportProtocol: " + unityTransport.Protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transportVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ namespace Unity.Netcode
|
|||||||
private INetworkMessageSender m_Sender;
|
private INetworkMessageSender m_Sender;
|
||||||
private bool m_Disposed;
|
private bool m_Disposed;
|
||||||
|
|
||||||
|
private ulong m_LocalClientId;
|
||||||
|
|
||||||
internal Type[] MessageTypes => m_ReverseTypeMap;
|
internal Type[] MessageTypes => m_ReverseTypeMap;
|
||||||
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
|
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
|
||||||
|
|
||||||
@@ -95,10 +97,22 @@ namespace Unity.Netcode
|
|||||||
return m_MessageTypes[t];
|
return m_MessageTypes[t];
|
||||||
}
|
}
|
||||||
|
|
||||||
public const int DefaultNonFragmentedMessageMaxSize = 1300;
|
internal object GetOwner()
|
||||||
|
{
|
||||||
|
return m_Owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetLocalClientId(ulong id)
|
||||||
|
{
|
||||||
|
m_LocalClientId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int DefaultNonFragmentedMessageMaxSize = 1300 & ~7; // Round down to nearest word aligned size (1296)
|
||||||
public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize;
|
public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize;
|
||||||
public int FragmentedMessageMaxSize = int.MaxValue;
|
public int FragmentedMessageMaxSize = int.MaxValue;
|
||||||
|
|
||||||
|
public Dictionary<ulong, int> PeerMTUSizes = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
internal struct MessageWithHandler
|
internal struct MessageWithHandler
|
||||||
{
|
{
|
||||||
public Type MessageType;
|
public Type MessageType;
|
||||||
@@ -497,6 +511,7 @@ namespace Unity.Netcode
|
|||||||
m_SendQueues.Remove(clientId);
|
m_SendQueues.Remove(clientId);
|
||||||
|
|
||||||
m_PerClientMessageVersions.Remove(clientId);
|
m_PerClientMessageVersions.Remove(clientId);
|
||||||
|
PeerMTUSizes.Remove(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CleanupDisconnectedClients()
|
internal void CleanupDisconnectedClients()
|
||||||
@@ -518,15 +533,18 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap))
|
if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap))
|
||||||
{
|
{
|
||||||
if (forReceive)
|
var networkManager = NetworkManager.Singleton;
|
||||||
|
if (networkManager != null && networkManager.LogLevel == LogLevel.Developer)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state.");
|
if (forReceive)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +563,7 @@ namespace Unity.Netcode
|
|||||||
// Special cases because these are the messages that carry the version info - thus the version info isn't
|
// Special cases because these are the messages that carry the version info - thus the version info isn't
|
||||||
// populated yet when we get these. The first part of these messages always has to be the version data
|
// populated yet when we get these. The first part of these messages always has to be the version data
|
||||||
// and can't change.
|
// and can't change.
|
||||||
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage))
|
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage) && context.SenderId != manager.m_LocalClientId)
|
||||||
{
|
{
|
||||||
messageVersion = manager.GetMessageVersion(typeof(T), context.SenderId, true);
|
messageVersion = manager.GetMessageVersion(typeof(T), context.SenderId, true);
|
||||||
if (messageVersion < 0)
|
if (messageVersion < 0)
|
||||||
@@ -675,6 +693,21 @@ namespace Unity.Netcode
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var startSize = NonFragmentedMessageMaxSize;
|
||||||
|
if (delivery != NetworkDelivery.ReliableFragmentedSequenced)
|
||||||
|
{
|
||||||
|
if (PeerMTUSizes.TryGetValue(clientId, out var clientMaxSize))
|
||||||
|
{
|
||||||
|
maxSize = clientMaxSize;
|
||||||
|
}
|
||||||
|
startSize = maxSize;
|
||||||
|
if (tmpSerializer.Position >= maxSize)
|
||||||
|
{
|
||||||
|
Debug.LogError($"MTU size for {clientId} is too small to contain a message of type {typeof(TMessageType).FullName}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||||
{
|
{
|
||||||
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery);
|
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery);
|
||||||
@@ -683,7 +716,7 @@ namespace Unity.Netcode
|
|||||||
var sendQueueItem = m_SendQueues[clientId];
|
var sendQueueItem = m_SendQueues[clientId];
|
||||||
if (sendQueueItem.Length == 0)
|
if (sendQueueItem.Length == 0)
|
||||||
{
|
{
|
||||||
sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize));
|
sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize));
|
||||||
sendQueueItem.ElementAt(0).Writer.Seek(sizeof(NetworkBatchHeader));
|
sendQueueItem.ElementAt(0).Writer.Seek(sizeof(NetworkBatchHeader));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -691,7 +724,7 @@ namespace Unity.Netcode
|
|||||||
ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
|
ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
|
||||||
if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length + headerSerializer.Length)
|
if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length + headerSerializer.Length)
|
||||||
{
|
{
|
||||||
sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize));
|
sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize));
|
||||||
sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(NetworkBatchHeader));
|
sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(NetworkBatchHeader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -787,6 +820,12 @@ namespace Unity.Netcode
|
|||||||
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
|
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, in NativeList<ulong> clientIds)
|
||||||
|
where T : INetworkMessage
|
||||||
|
{
|
||||||
|
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
|
||||||
|
}
|
||||||
|
|
||||||
internal unsafe void ProcessSendQueues()
|
internal unsafe void ProcessSendQueues()
|
||||||
{
|
{
|
||||||
if (StopProcessing)
|
if (StopProcessing)
|
||||||
@@ -826,11 +865,17 @@ namespace Unity.Netcode
|
|||||||
// Skipping the Verify and sneaking the write mark in because we know it's fine.
|
// Skipping the Verify and sneaking the write mark in because we know it's fine.
|
||||||
queueItem.Writer.Handle->AllowedWriteMark = sizeof(NetworkBatchHeader);
|
queueItem.Writer.Handle->AllowedWriteMark = sizeof(NetworkBatchHeader);
|
||||||
#endif
|
#endif
|
||||||
queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(NetworkBatchHeader), queueItem.Writer.Length - sizeof(NetworkBatchHeader));
|
|
||||||
|
|
||||||
queueItem.BatchHeader.BatchSize = queueItem.Writer.Length;
|
|
||||||
|
var alignedLength = (queueItem.Writer.Length + 7) & ~7;
|
||||||
|
queueItem.Writer.TryBeginWrite(alignedLength);
|
||||||
|
|
||||||
|
queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(NetworkBatchHeader), alignedLength - sizeof(NetworkBatchHeader));
|
||||||
|
|
||||||
|
queueItem.BatchHeader.BatchSize = alignedLength;
|
||||||
|
|
||||||
queueItem.Writer.WriteValue(queueItem.BatchHeader);
|
queueItem.Writer.WriteValue(queueItem.BatchHeader);
|
||||||
|
queueItem.Writer.Seek(alignedLength);
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -21,12 +21,36 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>Represents the common base class for Rpc attributes.</para>
|
/// <para>Represents the common base class for Rpc attributes.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RpcAttribute : Attribute
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class RpcAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
// Must match the set of parameters below
|
||||||
|
public struct RpcAttributeParams
|
||||||
|
{
|
||||||
|
public RpcDelivery Delivery;
|
||||||
|
public bool RequireOwnership;
|
||||||
|
public bool DeferLocal;
|
||||||
|
public bool AllowTargetOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must match the fields in RemoteAttributeParams
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Type of RPC delivery method
|
/// Type of RPC delivery method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RpcDelivery Delivery = RpcDelivery.Reliable;
|
public RpcDelivery Delivery = RpcDelivery.Reliable;
|
||||||
|
public bool RequireOwnership;
|
||||||
|
public bool DeferLocal;
|
||||||
|
public bool AllowTargetOverride;
|
||||||
|
|
||||||
|
public RpcAttribute(SendTo target)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// To get around an issue with the release validator, RuntimeAccessModifiersILPP will make this 'public'
|
||||||
|
private RpcAttribute()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,10 +60,12 @@ namespace Unity.Netcode
|
|||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class ServerRpcAttribute : RpcAttribute
|
public class ServerRpcAttribute : RpcAttribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
public new bool RequireOwnership;
|
||||||
/// Whether or not the ServerRpc should only be run if executed by the owner of the object
|
|
||||||
/// </summary>
|
public ServerRpcAttribute() : base(SendTo.Server)
|
||||||
public bool RequireOwnership = true;
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -47,5 +73,11 @@ namespace Unity.Netcode
|
|||||||
/// <para>A ClientRpc marked method will be fired by the server but executed on clients.</para>
|
/// <para>A ClientRpc marked method will be fired by the server but executed on clients.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class ClientRpcAttribute : RpcAttribute { }
|
public class ClientRpcAttribute : RpcAttribute
|
||||||
|
{
|
||||||
|
public ClientRpcAttribute() : base(SendTo.NotServer)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,60 @@ using Unity.Collections;
|
|||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
public enum LocalDeferMode
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Defer,
|
||||||
|
SendImmediate
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Generic RPC
|
||||||
|
/// </summary>
|
||||||
|
public struct RpcSendParams
|
||||||
|
{
|
||||||
|
public BaseRpcTarget Target;
|
||||||
|
|
||||||
|
public LocalDeferMode LocalDeferMode;
|
||||||
|
|
||||||
|
public static implicit operator RpcSendParams(BaseRpcTarget target) => new RpcSendParams { Target = target };
|
||||||
|
public static implicit operator RpcSendParams(LocalDeferMode deferMode) => new RpcSendParams { LocalDeferMode = deferMode };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The receive parameters for server-side remote procedure calls
|
||||||
|
/// </summary>
|
||||||
|
public struct RpcReceiveParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Server-Side RPC
|
||||||
|
/// The client identifier of the sender
|
||||||
|
/// </summary>
|
||||||
|
public ulong SenderClientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-Side RPC
|
||||||
|
/// Can be used with any sever-side remote procedure call
|
||||||
|
/// Note: typically this is use primarily for the <see cref="ServerRpcReceiveParams"/>
|
||||||
|
/// </summary>
|
||||||
|
public struct RpcParams
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The server RPC send parameters (currently a place holder)
|
||||||
|
/// </summary>
|
||||||
|
public RpcSendParams Send;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The client RPC receive parameters provides you with the sender's identifier
|
||||||
|
/// </summary>
|
||||||
|
public RpcReceiveParams Receive;
|
||||||
|
|
||||||
|
public static implicit operator RpcParams(RpcSendParams send) => new RpcParams { Send = send };
|
||||||
|
public static implicit operator RpcParams(BaseRpcTarget target) => new RpcParams { Send = new RpcSendParams { Target = target } };
|
||||||
|
public static implicit operator RpcParams(LocalDeferMode deferMode) => new RpcParams { Send = new RpcSendParams { LocalDeferMode = deferMode } };
|
||||||
|
public static implicit operator RpcParams(RpcReceiveParams receive) => new RpcParams { Receive = receive };
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server-Side RPC
|
/// Server-Side RPC
|
||||||
/// Place holder. <see cref="ServerRpcParams"/>
|
/// Place holder. <see cref="ServerRpcParams"/>
|
||||||
@@ -99,6 +153,7 @@ namespace Unity.Netcode
|
|||||||
internal struct __RpcParams
|
internal struct __RpcParams
|
||||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||||
{
|
{
|
||||||
|
public RpcParams Ext;
|
||||||
public ServerRpcParams Server;
|
public ServerRpcParams Server;
|
||||||
public ClientRpcParams Client;
|
public ClientRpcParams Client;
|
||||||
}
|
}
|
||||||
|
|||||||
3
Runtime/Messaging/RpcTargets.meta
Normal file
3
Runtime/Messaging/RpcTargets.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b02186acd1144e20acbd0dcb69b14938
|
||||||
|
timeCreated: 1697824888
|
||||||
57
Runtime/Messaging/RpcTargets/BaseRpcTarget.cs
Normal file
57
Runtime/Messaging/RpcTargets/BaseRpcTarget.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
public abstract class BaseRpcTarget : IDisposable
|
||||||
|
{
|
||||||
|
protected NetworkManager m_NetworkManager;
|
||||||
|
private bool m_Locked;
|
||||||
|
|
||||||
|
internal void Lock()
|
||||||
|
{
|
||||||
|
m_Locked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Unlock()
|
||||||
|
{
|
||||||
|
m_Locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal BaseRpcTarget(NetworkManager manager)
|
||||||
|
{
|
||||||
|
m_NetworkManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CheckLockBeforeDispose()
|
||||||
|
{
|
||||||
|
if (m_Locked)
|
||||||
|
{
|
||||||
|
throw new Exception($"RPC targets obtained through {nameof(RpcTargetUse)}.{RpcTargetUse.Temp} may not be disposed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Dispose();
|
||||||
|
|
||||||
|
internal abstract void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams);
|
||||||
|
|
||||||
|
private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery)
|
||||||
|
{
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
var size =
|
||||||
|
#endif
|
||||||
|
behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId);
|
||||||
|
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||||
|
{
|
||||||
|
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||||
|
clientId,
|
||||||
|
behaviour.NetworkObject,
|
||||||
|
rpcMethodName,
|
||||||
|
behaviour.__getTypeName(),
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/Messaging/RpcTargets/BaseRpcTarget.cs.meta
Normal file
11
Runtime/Messaging/RpcTargets/BaseRpcTarget.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 07c2620262e24eb5a426b521c09b3091
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
37
Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs
Normal file
37
Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class ClientsAndHostRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private BaseRpcTarget m_UnderlyingTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
if (m_UnderlyingTarget == null)
|
||||||
|
{
|
||||||
|
// NotServer treats a host as being a server and will not send to it
|
||||||
|
// ClientsAndHost sends to everyone who runs any client logic
|
||||||
|
// So if the server is a host, this target includes it (as hosts run client logic)
|
||||||
|
// If the server is not a host, this target leaves it out, ergo the selection of NotServer.
|
||||||
|
if (behaviour.NetworkManager.ServerIsHost)
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = behaviour.RpcTarget.Everyone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = behaviour.RpcTarget.NotServer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ClientsAndHostRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c9f883d678ec4715b160dd9497d5f42d
|
||||||
|
timeCreated: 1699481382
|
||||||
34
Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs
Normal file
34
Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class DirectSendRpcTarget : BaseRpcTarget, IIndividualRpcTarget
|
||||||
|
{
|
||||||
|
public BaseRpcTarget Target => this;
|
||||||
|
|
||||||
|
internal ulong ClientId;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
CheckLockBeforeDispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
SendMessageToClient(behaviour, ClientId, ref message, delivery);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetClientId(ulong clientId)
|
||||||
|
{
|
||||||
|
ClientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DirectSendRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DirectSendRpcTarget(ulong clientId, NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
ClientId = clientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 077544cfd0b94cfc8a2a55d3828b74bb
|
||||||
|
timeCreated: 1697824873
|
||||||
26
Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs
Normal file
26
Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class EveryoneRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private NotServerRpcTarget m_NotServerRpcTarget;
|
||||||
|
private ServerRpcTarget m_ServerRpcTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
m_NotServerRpcTarget.Dispose();
|
||||||
|
m_ServerRpcTarget.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
m_NotServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal EveryoneRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
m_NotServerRpcTarget = new NotServerRpcTarget(manager);
|
||||||
|
m_ServerRpcTarget = new ServerRpcTarget(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 675d4a5c79fc47078092ac15d255745d
|
||||||
|
timeCreated: 1697824941
|
||||||
9
Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs
Normal file
9
Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal interface IGroupRpcTarget
|
||||||
|
{
|
||||||
|
void Add(ulong clientId);
|
||||||
|
void Clear();
|
||||||
|
BaseRpcTarget Target { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: beb19a6bb1334252a89b21c8490f7cbe
|
||||||
|
timeCreated: 1697825109
|
||||||
8
Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs
Normal file
8
Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal interface IIndividualRpcTarget
|
||||||
|
{
|
||||||
|
void SetClientId(ulong clientId);
|
||||||
|
BaseRpcTarget Target { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c658d9641f564d9890bef4f558f1cea6
|
||||||
|
timeCreated: 1697825115
|
||||||
67
Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs
Normal file
67
Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using Unity.Collections;
|
||||||
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class LocalSendRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
var networkManager = behaviour.NetworkManager;
|
||||||
|
var context = new NetworkContext
|
||||||
|
{
|
||||||
|
SenderId = m_NetworkManager.LocalClientId,
|
||||||
|
Timestamp = networkManager.RealTimeProvider.RealTimeSinceStartup,
|
||||||
|
SystemOwner = networkManager,
|
||||||
|
// header information isn't valid since it's not a real message.
|
||||||
|
// RpcMessage doesn't access this stuff so it's just left empty.
|
||||||
|
Header = new NetworkMessageHeader(),
|
||||||
|
SerializedHeaderSize = 0,
|
||||||
|
MessageSize = 0
|
||||||
|
};
|
||||||
|
int length;
|
||||||
|
if (rpcParams.Send.LocalDeferMode == LocalDeferMode.Defer)
|
||||||
|
{
|
||||||
|
using var serializedWriter = new FastBufferWriter(message.WriteBuffer.Length + UnsafeUtility.SizeOf<RpcMetadata>(), Allocator.Temp, int.MaxValue);
|
||||||
|
message.Serialize(serializedWriter, message.Version);
|
||||||
|
using var reader = new FastBufferReader(serializedWriter, Allocator.None);
|
||||||
|
context.Header = new NetworkMessageHeader
|
||||||
|
{
|
||||||
|
MessageSize = (uint)reader.Length,
|
||||||
|
MessageType = m_NetworkManager.MessageManager.GetMessageType(typeof(RpcMessage))
|
||||||
|
};
|
||||||
|
|
||||||
|
behaviour.NetworkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context);
|
||||||
|
length = reader.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None);
|
||||||
|
message.ReadBuffer = tempBuffer;
|
||||||
|
message.Handle(ref context);
|
||||||
|
length = tempBuffer.Length;
|
||||||
|
}
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||||
|
{
|
||||||
|
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||||
|
behaviour.NetworkManager.LocalClientId,
|
||||||
|
behaviour.NetworkObject,
|
||||||
|
rpcMethodName,
|
||||||
|
behaviour.__getTypeName(),
|
||||||
|
length);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LocalSendRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c3b290cdc20d4d2293652ec79652962a
|
||||||
|
timeCreated: 1697824985
|
||||||
67
Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs
Normal file
67
Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class NotMeRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private IGroupRpcTarget m_GroupSendTarget;
|
||||||
|
private ServerRpcTarget m_ServerRpcTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget.Dispose();
|
||||||
|
if (m_GroupSendTarget != null)
|
||||||
|
{
|
||||||
|
m_GroupSendTarget.Target.Dispose();
|
||||||
|
m_GroupSendTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
if (m_GroupSendTarget == null)
|
||||||
|
{
|
||||||
|
if (behaviour.IsServer)
|
||||||
|
{
|
||||||
|
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Clear();
|
||||||
|
if (behaviour.IsServer)
|
||||||
|
{
|
||||||
|
foreach (var clientId in behaviour.NetworkObject.Observers)
|
||||||
|
{
|
||||||
|
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_GroupSendTarget.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_GroupSendTarget.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
if (!behaviour.IsServer)
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal NotMeRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget = new ServerRpcTarget(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 99cd5e8be7bd454bab700ee08b8dad7b
|
||||||
|
timeCreated: 1697824966
|
||||||
83
Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs
Normal file
83
Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class NotOwnerRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private IGroupRpcTarget m_GroupSendTarget;
|
||||||
|
private ServerRpcTarget m_ServerRpcTarget;
|
||||||
|
private LocalSendRpcTarget m_LocalSendRpcTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget.Dispose();
|
||||||
|
m_LocalSendRpcTarget.Dispose();
|
||||||
|
if (m_GroupSendTarget != null)
|
||||||
|
{
|
||||||
|
m_GroupSendTarget.Target.Dispose();
|
||||||
|
m_GroupSendTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
if (m_GroupSendTarget == null)
|
||||||
|
{
|
||||||
|
if (behaviour.IsServer)
|
||||||
|
{
|
||||||
|
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_GroupSendTarget.Clear();
|
||||||
|
|
||||||
|
if (behaviour.IsServer)
|
||||||
|
{
|
||||||
|
foreach (var clientId in behaviour.NetworkObject.Observers)
|
||||||
|
{
|
||||||
|
if (clientId == behaviour.OwnerClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (clientId == behaviour.OwnerClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
if (behaviour.OwnerClientId != NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal NotOwnerRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget = new ServerRpcTarget(manager);
|
||||||
|
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d7bc66c5253b44d09ad978ea9e51c96f
|
||||||
|
timeCreated: 1698789420
|
||||||
72
Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs
Normal file
72
Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class NotServerRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private IGroupRpcTarget m_GroupSendTarget;
|
||||||
|
private LocalSendRpcTarget m_LocalSendRpcTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget.Dispose();
|
||||||
|
if (m_GroupSendTarget != null)
|
||||||
|
{
|
||||||
|
m_GroupSendTarget.Target.Dispose();
|
||||||
|
m_GroupSendTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
if (m_GroupSendTarget == null)
|
||||||
|
{
|
||||||
|
if (behaviour.IsServer)
|
||||||
|
{
|
||||||
|
m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_GroupSendTarget.Clear();
|
||||||
|
|
||||||
|
if (behaviour.IsServer)
|
||||||
|
{
|
||||||
|
foreach (var clientId in behaviour.NetworkObject.Observers)
|
||||||
|
{
|
||||||
|
if (clientId == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (clientId == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientId == behaviour.NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal NotServerRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c63787afe52f45ffbd5d801f78e7c0d6
|
||||||
|
timeCreated: 1697824954
|
||||||
54
Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs
Normal file
54
Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class OwnerRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private IIndividualRpcTarget m_UnderlyingTarget;
|
||||||
|
private LocalSendRpcTarget m_LocalRpcTarget;
|
||||||
|
private ServerRpcTarget m_ServerRpcTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
m_LocalRpcTarget.Dispose();
|
||||||
|
if (m_UnderlyingTarget != null)
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget.Target.Dispose();
|
||||||
|
m_UnderlyingTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
if (behaviour.OwnerClientId == behaviour.NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
m_LocalRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (behaviour.OwnerClientId == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_UnderlyingTarget == null)
|
||||||
|
{
|
||||||
|
if (behaviour.NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = new DirectSendRpcTarget(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = new ProxyRpcTarget(behaviour.OwnerClientId, m_NetworkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_UnderlyingTarget.SetClientId(behaviour.OwnerClientId);
|
||||||
|
m_UnderlyingTarget.Target.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal OwnerRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
m_LocalRpcTarget = new LocalSendRpcTarget(manager);
|
||||||
|
m_ServerRpcTarget = new ServerRpcTarget(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23c4d52455fc419aaf03094617894257
|
||||||
|
timeCreated: 1697824972
|
||||||
16
Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs
Normal file
16
Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class ProxyRpcTarget : ProxyRpcTargetGroup, IIndividualRpcTarget
|
||||||
|
{
|
||||||
|
internal ProxyRpcTarget(ulong clientId, NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetClientId(ulong clientId)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 86002805bb9e422e8b71581d1325357f
|
||||||
|
timeCreated: 1697825007
|
||||||
100
Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs
Normal file
100
Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Collections;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class ProxyRpcTargetGroup : BaseRpcTarget, IDisposable, IGroupRpcTarget
|
||||||
|
{
|
||||||
|
public BaseRpcTarget Target => this;
|
||||||
|
|
||||||
|
private ServerRpcTarget m_ServerRpcTarget;
|
||||||
|
private LocalSendRpcTarget m_LocalSendRpcTarget;
|
||||||
|
|
||||||
|
private bool m_Disposed;
|
||||||
|
public NativeList<ulong> TargetClientIds;
|
||||||
|
internal HashSet<ulong> Ids = new HashSet<ulong>();
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message };
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
var size =
|
||||||
|
#endif
|
||||||
|
behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId);
|
||||||
|
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||||
|
{
|
||||||
|
foreach (var clientId in TargetClientIds)
|
||||||
|
{
|
||||||
|
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||||
|
clientId,
|
||||||
|
behaviour.NetworkObject,
|
||||||
|
rpcMethodName,
|
||||||
|
behaviour.__getTypeName(),
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (Ids.Contains(NetworkManager.ServerClientId))
|
||||||
|
{
|
||||||
|
m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
if (Ids.Contains(m_NetworkManager.LocalClientId))
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ProxyRpcTargetGroup(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
TargetClientIds = new NativeList<ulong>(Allocator.Persistent);
|
||||||
|
m_ServerRpcTarget = new ServerRpcTarget(manager);
|
||||||
|
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
CheckLockBeforeDispose();
|
||||||
|
if (!m_Disposed)
|
||||||
|
{
|
||||||
|
TargetClientIds.Dispose();
|
||||||
|
m_Disposed = true;
|
||||||
|
m_ServerRpcTarget.Dispose();
|
||||||
|
m_LocalSendRpcTarget.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ulong clientId)
|
||||||
|
{
|
||||||
|
if (!Ids.Contains(clientId))
|
||||||
|
{
|
||||||
|
Ids.Add(clientId);
|
||||||
|
if (clientId != NetworkManager.ServerClientId && clientId != m_NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
TargetClientIds.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(ulong clientId)
|
||||||
|
{
|
||||||
|
Ids.Remove(clientId);
|
||||||
|
for (var i = 0; i < TargetClientIds.Length; ++i)
|
||||||
|
{
|
||||||
|
if (TargetClientIds[i] == clientId)
|
||||||
|
{
|
||||||
|
TargetClientIds.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Ids.Clear();
|
||||||
|
TargetClientIds.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5728dbab532e46a88127510b4ec75af9
|
||||||
|
timeCreated: 1697825000
|
||||||
564
Runtime/Messaging/RpcTargets/RpcTarget.cs
Normal file
564
Runtime/Messaging/RpcTargets/RpcTarget.cs
Normal file
@@ -0,0 +1,564 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Collections;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for the default method by which an RPC is communicated across the network
|
||||||
|
/// </summary>
|
||||||
|
public enum SendTo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send to the NetworkObject's current owner.
|
||||||
|
/// Will execute locally if the local process is the owner.
|
||||||
|
/// </summary>
|
||||||
|
Owner,
|
||||||
|
/// <summary>
|
||||||
|
/// Send to everyone but the current owner, filtered to the current observer list.
|
||||||
|
/// Will execute locally if the local process is not the owner.
|
||||||
|
/// </summary>
|
||||||
|
NotOwner,
|
||||||
|
/// <summary>
|
||||||
|
/// Send to the server, regardless of ownership.
|
||||||
|
/// Will execute locally if invoked on the server.
|
||||||
|
/// </summary>
|
||||||
|
Server,
|
||||||
|
/// <summary>
|
||||||
|
/// Send to everyone but the server, filtered to the current observer list.
|
||||||
|
/// Will NOT send to a server running in host mode - it is still treated as a server.
|
||||||
|
/// If you want to send to servers when they are host, but not when they are dedicated server, use
|
||||||
|
/// <see cref="ClientsAndHost"/>.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// Will execute locally if invoked on a client.
|
||||||
|
/// Will NOT execute locally if invoked on a server running in host mode.
|
||||||
|
/// </summary>
|
||||||
|
NotServer,
|
||||||
|
/// <summary>
|
||||||
|
/// Execute this RPC locally.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// Normally this is no different from a standard function call.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams,
|
||||||
|
/// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over
|
||||||
|
/// the network.
|
||||||
|
/// </summary>
|
||||||
|
Me,
|
||||||
|
/// <summary>
|
||||||
|
/// Send this RPC to everyone but the local machine, filtered to the current observer list.
|
||||||
|
/// </summary>
|
||||||
|
NotMe,
|
||||||
|
/// <summary>
|
||||||
|
/// Send this RPC to everone, filtered to the current observer list.
|
||||||
|
/// Will execute locally.
|
||||||
|
/// </summary>
|
||||||
|
Everyone,
|
||||||
|
/// <summary>
|
||||||
|
/// Send this RPC to all clients, including the host, if a host exists.
|
||||||
|
/// If the server is running in host mode, this is the same as <see cref="Everyone" />.
|
||||||
|
/// If the server is running in dedicated server mode, this is the same as <see cref="NotServer" />.
|
||||||
|
/// </summary>
|
||||||
|
ClientsAndHost,
|
||||||
|
/// <summary>
|
||||||
|
/// This RPC cannot be sent without passing in a target in RpcSendParams.
|
||||||
|
/// </summary>
|
||||||
|
SpecifiedInParams
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RpcTargetUse
|
||||||
|
{
|
||||||
|
Temp,
|
||||||
|
Persistent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementations of the various <see cref="SendTo"/> options, as well as additional runtime-only options
|
||||||
|
/// <see cref="Single"/>,
|
||||||
|
/// <see cref="Group(NativeArray{ulong})"/>,
|
||||||
|
/// <see cref="Group(NativeList{ulong})"/>,
|
||||||
|
/// <see cref="Group(ulong[])"/>,
|
||||||
|
/// <see cref="Group{T}(T)"/>, <see cref="Not(ulong)"/>,
|
||||||
|
/// <see cref="Not(NativeArray{ulong})"/>,
|
||||||
|
/// <see cref="Not(NativeList{ulong})"/>,
|
||||||
|
/// <see cref="Not(ulong[])"/>, and
|
||||||
|
/// <see cref="Not{T}(T)"/>
|
||||||
|
/// </summary>
|
||||||
|
public class RpcTarget
|
||||||
|
{
|
||||||
|
private NetworkManager m_NetworkManager;
|
||||||
|
internal RpcTarget(NetworkManager manager)
|
||||||
|
{
|
||||||
|
m_NetworkManager = manager;
|
||||||
|
|
||||||
|
Everyone = new EveryoneRpcTarget(manager);
|
||||||
|
Owner = new OwnerRpcTarget(manager);
|
||||||
|
NotOwner = new NotOwnerRpcTarget(manager);
|
||||||
|
Server = new ServerRpcTarget(manager);
|
||||||
|
NotServer = new NotServerRpcTarget(manager);
|
||||||
|
NotMe = new NotMeRpcTarget(manager);
|
||||||
|
Me = new LocalSendRpcTarget(manager);
|
||||||
|
ClientsAndHost = new ClientsAndHostRpcTarget(manager);
|
||||||
|
|
||||||
|
m_CachedProxyRpcTargetGroup = new ProxyRpcTargetGroup(manager);
|
||||||
|
m_CachedTargetGroup = new RpcTargetGroup(manager);
|
||||||
|
m_CachedDirectSendTarget = new DirectSendRpcTarget(manager);
|
||||||
|
m_CachedProxyRpcTarget = new ProxyRpcTarget(0, manager);
|
||||||
|
|
||||||
|
m_CachedProxyRpcTargetGroup.Lock();
|
||||||
|
m_CachedTargetGroup.Lock();
|
||||||
|
m_CachedDirectSendTarget.Lock();
|
||||||
|
m_CachedProxyRpcTarget.Lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Everyone.Dispose();
|
||||||
|
Owner.Dispose();
|
||||||
|
NotOwner.Dispose();
|
||||||
|
Server.Dispose();
|
||||||
|
NotServer.Dispose();
|
||||||
|
NotMe.Dispose();
|
||||||
|
Me.Dispose();
|
||||||
|
ClientsAndHost.Dispose();
|
||||||
|
|
||||||
|
m_CachedProxyRpcTargetGroup.Unlock();
|
||||||
|
m_CachedTargetGroup.Unlock();
|
||||||
|
m_CachedDirectSendTarget.Unlock();
|
||||||
|
m_CachedProxyRpcTarget.Unlock();
|
||||||
|
|
||||||
|
m_CachedProxyRpcTargetGroup.Dispose();
|
||||||
|
m_CachedTargetGroup.Dispose();
|
||||||
|
m_CachedDirectSendTarget.Dispose();
|
||||||
|
m_CachedProxyRpcTarget.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to the NetworkObject's current owner.
|
||||||
|
/// Will execute locally if the local process is the owner.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget Owner;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to everyone but the current owner, filtered to the current observer list.
|
||||||
|
/// Will execute locally if the local process is not the owner.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget NotOwner;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to the server, regardless of ownership.
|
||||||
|
/// Will execute locally if invoked on the server.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to everyone but the server, filtered to the current observer list.
|
||||||
|
/// Will NOT send to a server running in host mode - it is still treated as a server.
|
||||||
|
/// If you want to send to servers when they are host, but not when they are dedicated server, use
|
||||||
|
/// <see cref="SendTo.ClientsAndHost"/>.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// Will execute locally if invoked on a client.
|
||||||
|
/// Will NOT execute locally if invoked on a server running in host mode.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget NotServer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute this RPC locally.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// Normally this is no different from a standard function call.
|
||||||
|
/// <br />
|
||||||
|
/// <br />
|
||||||
|
/// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams,
|
||||||
|
/// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over
|
||||||
|
/// the network.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget Me;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send this RPC to everyone but the local machine, filtered to the current observer list.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget NotMe;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send this RPC to everone, filtered to the current observer list.
|
||||||
|
/// Will execute locally.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget Everyone;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send this RPC to all clients, including the host, if a host exists.
|
||||||
|
/// If the server is running in host mode, this is the same as <see cref="Everyone" />.
|
||||||
|
/// If the server is running in dedicated server mode, this is the same as <see cref="NotServer" />.
|
||||||
|
/// </summary>
|
||||||
|
public BaseRpcTarget ClientsAndHost;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to a specific single client ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Single(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Single(ulong clientId, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
if (clientId == m_NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
return Me;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_NetworkManager.IsServer || clientId == NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
return new DirectSendRpcTarget(clientId, m_NetworkManager);
|
||||||
|
}
|
||||||
|
m_CachedDirectSendTarget.SetClientId(clientId);
|
||||||
|
return m_CachedDirectSendTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
return new ProxyRpcTarget(clientId, m_NetworkManager);
|
||||||
|
}
|
||||||
|
m_CachedProxyRpcTarget.SetClientId(clientId);
|
||||||
|
return m_CachedProxyRpcTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send to everyone EXCEPT a specific single client ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedClientId"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Not(ulong excludedClientId, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
IGroupRpcTarget target;
|
||||||
|
if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedProxyRpcTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.Clear();
|
||||||
|
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (clientId != excludedClientId)
|
||||||
|
{
|
||||||
|
target.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it.
|
||||||
|
if (!m_NetworkManager.ServerIsHost && excludedClientId != NetworkManager.ServerClientId)
|
||||||
|
{
|
||||||
|
target.Add(NetworkManager.ServerClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to a group of client IDs.
|
||||||
|
/// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient
|
||||||
|
/// Group method if the group list is dynamically constructed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Group(NativeArray<ulong> clientIds, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
IGroupRpcTarget target;
|
||||||
|
if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedProxyRpcTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.Clear();
|
||||||
|
foreach (var clientId in clientIds)
|
||||||
|
{
|
||||||
|
target.Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to a group of client IDs.
|
||||||
|
/// NativeList can be trivially constructed using Allocator.Temp, making this an efficient
|
||||||
|
/// Group method if the group list is dynamically constructed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Group(NativeList<ulong> clientIds, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
var asArray = clientIds.AsArray();
|
||||||
|
return Group(asArray, use);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to a group of client IDs.
|
||||||
|
/// Constructing arrays requires garbage collected allocations. This override is only recommended
|
||||||
|
/// if you either have no strict performance requirements, or have the group of client IDs cached so
|
||||||
|
/// it is not created each time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Group(ulong[] clientIds, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
return Group(new NativeArray<ulong>(clientIds, Allocator.Temp), use);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to a group of client IDs.
|
||||||
|
/// This accepts any IEnumerable type, such as List<ulong>, but cannot be called without
|
||||||
|
/// a garbage collected allocation (even if the type itself is a struct type, due to boxing).
|
||||||
|
/// This override is only recommended if you either have no strict performance requirements,
|
||||||
|
/// or have the group of client IDs cached so it is not created each time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Group<T>(T clientIds, RpcTargetUse use) where T : IEnumerable<ulong>
|
||||||
|
{
|
||||||
|
IGroupRpcTarget target;
|
||||||
|
if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedProxyRpcTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.Clear();
|
||||||
|
foreach (var clientId in clientIds)
|
||||||
|
{
|
||||||
|
target.Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to everyone EXCEPT a group of client IDs.
|
||||||
|
/// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient
|
||||||
|
/// Group method if the group list is dynamically constructed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedClientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Not(NativeArray<ulong> excludedClientIds, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
IGroupRpcTarget target;
|
||||||
|
if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedProxyRpcTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.Clear();
|
||||||
|
|
||||||
|
using var asASet = new NativeHashSet<ulong>(excludedClientIds.Length, Allocator.Temp);
|
||||||
|
foreach (var clientId in excludedClientIds)
|
||||||
|
{
|
||||||
|
asASet.Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (!asASet.Contains(clientId))
|
||||||
|
{
|
||||||
|
target.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it.
|
||||||
|
if (!m_NetworkManager.ServerIsHost && !asASet.Contains(NetworkManager.ServerClientId))
|
||||||
|
{
|
||||||
|
target.Add(NetworkManager.ServerClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to everyone EXCEPT a group of client IDs.
|
||||||
|
/// NativeList can be trivially constructed using Allocator.Temp, making this an efficient
|
||||||
|
/// Group method if the group list is dynamically constructed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedClientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Not(NativeList<ulong> excludedClientIds, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
var asArray = excludedClientIds.AsArray();
|
||||||
|
return Not(asArray, use);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to everyone EXCEPT a group of client IDs.
|
||||||
|
/// Constructing arrays requires garbage collected allocations. This override is only recommended
|
||||||
|
/// if you either have no strict performance requirements, or have the group of client IDs cached so
|
||||||
|
/// it is not created each time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedClientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Not(ulong[] excludedClientIds, RpcTargetUse use)
|
||||||
|
{
|
||||||
|
return Not(new NativeArray<ulong>(excludedClientIds, Allocator.Temp), use);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends to everyone EXCEPT a group of client IDs.
|
||||||
|
/// This accepts any IEnumerable type, such as List<ulong>, but cannot be called without
|
||||||
|
/// a garbage collected allocation (even if the type itself is a struct type, due to boxing).
|
||||||
|
/// This override is only recommended if you either have no strict performance requirements,
|
||||||
|
/// or have the group of client IDs cached so it is not created each time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="excludedClientIds"></param>
|
||||||
|
/// <param name="use"><see cref="RpcTargetUse.Temp"/> will return a cached target, which should not be stored as it will
|
||||||
|
/// be overwritten in future calls to Not() or Group(). Do not call Dispose() on Temp targets.<br /><br /><see cref="RpcTargetUse.Persistent"/> will
|
||||||
|
/// return a new target, which can be stored, but should not be done frequently because it results in a GC allocation. You must call Dispose() on Persistent targets when you are done with them.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public BaseRpcTarget Not<T>(T excludedClientIds, RpcTargetUse use) where T : IEnumerable<ulong>
|
||||||
|
{
|
||||||
|
IGroupRpcTarget target;
|
||||||
|
if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new RpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (use == RpcTargetUse.Persistent)
|
||||||
|
{
|
||||||
|
target = new ProxyRpcTargetGroup(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target = m_CachedProxyRpcTargetGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.Clear();
|
||||||
|
|
||||||
|
using var asASet = new NativeHashSet<ulong>(m_NetworkManager.ConnectedClientsIds.Count, Allocator.Temp);
|
||||||
|
foreach (var clientId in excludedClientIds)
|
||||||
|
{
|
||||||
|
asASet.Add(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var clientId in m_NetworkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (!asASet.Contains(clientId))
|
||||||
|
{
|
||||||
|
target.Add(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it.
|
||||||
|
if (!m_NetworkManager.ServerIsHost && !asASet.Contains(NetworkManager.ServerClientId))
|
||||||
|
{
|
||||||
|
target.Add(NetworkManager.ServerClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProxyRpcTargetGroup m_CachedProxyRpcTargetGroup;
|
||||||
|
private RpcTargetGroup m_CachedTargetGroup;
|
||||||
|
private DirectSendRpcTarget m_CachedDirectSendTarget;
|
||||||
|
private ProxyRpcTarget m_CachedProxyRpcTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/RpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/RpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1b26d0227e71408b918ae25ca2a0179b
|
||||||
|
timeCreated: 1699555535
|
||||||
80
Runtime/Messaging/RpcTargets/RpcTargetGroup.cs
Normal file
80
Runtime/Messaging/RpcTargets/RpcTargetGroup.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class RpcTargetGroup : BaseRpcTarget, IGroupRpcTarget
|
||||||
|
{
|
||||||
|
public BaseRpcTarget Target => this;
|
||||||
|
|
||||||
|
internal List<BaseRpcTarget> Targets = new List<BaseRpcTarget>();
|
||||||
|
|
||||||
|
private LocalSendRpcTarget m_LocalSendRpcTarget;
|
||||||
|
private HashSet<ulong> m_Ids = new HashSet<ulong>();
|
||||||
|
private Stack<DirectSendRpcTarget> m_TargetCache = new Stack<DirectSendRpcTarget>();
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
CheckLockBeforeDispose();
|
||||||
|
foreach (var target in Targets)
|
||||||
|
{
|
||||||
|
target.Dispose();
|
||||||
|
}
|
||||||
|
foreach (var target in m_TargetCache)
|
||||||
|
{
|
||||||
|
target.Dispose();
|
||||||
|
}
|
||||||
|
m_LocalSendRpcTarget.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
foreach (var target in Targets)
|
||||||
|
{
|
||||||
|
target.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ulong clientId)
|
||||||
|
{
|
||||||
|
if (!m_Ids.Contains(clientId))
|
||||||
|
{
|
||||||
|
m_Ids.Add(clientId);
|
||||||
|
if (clientId == m_NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
Targets.Add(m_LocalSendRpcTarget);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_TargetCache.Count == 0)
|
||||||
|
{
|
||||||
|
Targets.Add(new DirectSendRpcTarget(m_NetworkManager) { ClientId = clientId });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var target = m_TargetCache.Pop();
|
||||||
|
target.ClientId = clientId;
|
||||||
|
Targets.Add(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_Ids.Clear();
|
||||||
|
foreach (var target in Targets)
|
||||||
|
{
|
||||||
|
if (target is DirectSendRpcTarget directSendRpcTarget)
|
||||||
|
{
|
||||||
|
m_TargetCache.Push(directSendRpcTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Targets.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RpcTargetGroup(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
m_LocalSendRpcTarget = new LocalSendRpcTarget(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/RpcTargetGroup.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/RpcTargetGroup.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f8c0fc053b64a588c99dd7d706d9f0a
|
||||||
|
timeCreated: 1697824991
|
||||||
36
Runtime/Messaging/RpcTargets/ServerRpcTarget.cs
Normal file
36
Runtime/Messaging/RpcTargets/ServerRpcTarget.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace Unity.Netcode
|
||||||
|
{
|
||||||
|
internal class ServerRpcTarget : BaseRpcTarget
|
||||||
|
{
|
||||||
|
private BaseRpcTarget m_UnderlyingTarget;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (m_UnderlyingTarget != null)
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget.Dispose();
|
||||||
|
m_UnderlyingTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
|
||||||
|
{
|
||||||
|
if (m_UnderlyingTarget == null)
|
||||||
|
{
|
||||||
|
if (behaviour.NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = new LocalSendRpcTarget(m_NetworkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_UnderlyingTarget = new DirectSendRpcTarget(m_NetworkManager) { ClientId = NetworkManager.ServerClientId };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ServerRpcTarget(NetworkManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Runtime/Messaging/RpcTargets/ServerRpcTarget.cs.meta
Normal file
3
Runtime/Messaging/RpcTargets/ServerRpcTarget.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c911725afb6d44f3bb1a1d567d9dee0f
|
||||||
|
timeCreated: 1697824979
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -9,6 +8,7 @@ namespace Unity.Netcode
|
|||||||
/// Event based NetworkVariable container for syncing Lists
|
/// Event based NetworkVariable container for syncing Lists
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type for the list</typeparam>
|
/// <typeparam name="T">The type for the list</typeparam>
|
||||||
|
[GenerateSerializationForGenericParameter(0)]
|
||||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||||
{
|
{
|
||||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||||
@@ -68,14 +68,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal void MarkNetworkObjectDirty()
|
internal void MarkNetworkObjectDirty()
|
||||||
{
|
{
|
||||||
if (m_NetworkBehaviour == null)
|
MarkNetworkBehaviourDirty();
|
||||||
{
|
|
||||||
Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " +
|
|
||||||
"Are you modifying a NetworkList before the NetworkObject is spawned?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
|
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
|
[GenerateSerializationForGenericParameter(0)]
|
||||||
public class NetworkVariable<T> : NetworkVariableBase
|
public class NetworkVariable<T> : NetworkVariableBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -146,8 +147,11 @@ namespace Unity.Netcode
|
|||||||
// Therefore, we set the m_PreviousValue field to a duplicate of the current
|
// Therefore, we set the m_PreviousValue field to a duplicate of the current
|
||||||
// field, so that our next dirty check is made against the current "not dirty"
|
// field, so that our next dirty check is made against the current "not dirty"
|
||||||
// value.
|
// value.
|
||||||
m_HasPreviousValue = true;
|
if (!m_HasPreviousValue || !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_PreviousValue))
|
||||||
NetworkVariableSerialization<T>.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue);
|
{
|
||||||
|
m_HasPreviousValue = true;
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -88,17 +88,30 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (m_IsDirty)
|
if (m_IsDirty)
|
||||||
{
|
{
|
||||||
if (m_NetworkBehaviour == null)
|
MarkNetworkBehaviourDirty();
|
||||||
{
|
|
||||||
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
|
|
||||||
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void MarkNetworkBehaviourDirty()
|
||||||
|
{
|
||||||
|
if (m_NetworkBehaviour == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
|
||||||
|
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
if (m_NetworkBehaviour.NetworkManager.LogLevel <= LogLevel.Developer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"NetworkVariable is written to during the NetworkManager shutdown! " +
|
||||||
|
"Are you modifying a NetworkVariable within a NetworkBehaviour.OnDestroy or NetworkBehaviour.OnDespawn method?");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the dirty state and marks the variable as synced / clean
|
/// Resets the dirty state and marks the variable as synced / clean
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
public void Duplicate(in T value, ref T duplicatedValue)
|
public void Duplicate(in T value, ref T duplicatedValue)
|
||||||
{
|
{
|
||||||
using var writer = new FastBufferWriter(256, Allocator.Temp);
|
using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue);
|
||||||
var refValue = value;
|
var refValue = value;
|
||||||
Write(writer, ref refValue);
|
Write(writer, ref refValue);
|
||||||
|
|
||||||
@@ -580,11 +580,16 @@ namespace Unity.Netcode
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
|
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
|
||||||
{
|
{
|
||||||
|
private void ThrowArgumentError()
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable<T>)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
public void Write(FastBufferWriter writer, ref T value)
|
public void Write(FastBufferWriter writer, ref T value)
|
||||||
{
|
{
|
||||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
ThrowArgumentError();
|
||||||
}
|
}
|
||||||
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
||||||
}
|
}
|
||||||
@@ -592,7 +597,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
ThrowArgumentError();
|
||||||
}
|
}
|
||||||
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
||||||
}
|
}
|
||||||
@@ -606,7 +611,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null || UserNetworkVariableSerialization<T>.DuplicateValue == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)}, {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
ThrowArgumentError();
|
||||||
}
|
}
|
||||||
UserNetworkVariableSerialization<T>.DuplicateValue(value, ref duplicatedValue);
|
UserNetworkVariableSerialization<T>.DuplicateValue(value, ref duplicatedValue);
|
||||||
}
|
}
|
||||||
@@ -841,8 +846,95 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
|
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
|
||||||
|
|
||||||
internal delegate bool EqualsDelegate(ref T a, ref T b);
|
/// <summary>
|
||||||
internal static EqualsDelegate AreEqual;
|
/// A callback to check if two values are equal.
|
||||||
|
/// </summary>
|
||||||
|
public delegate bool EqualsDelegate(ref T a, ref T b);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the most efficient mechanism for a given type to determine if two values are equal.
|
||||||
|
/// For types that implement <see cref="IEquatable{T}"/>, it will call the Equals() method.
|
||||||
|
/// For unmanaged types, it will do a bytewise memory comparison.
|
||||||
|
/// For other types, it will call the == operator.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
||||||
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
||||||
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
||||||
|
/// generic, it is better to check their equality yourself.
|
||||||
|
/// </summary>
|
||||||
|
public static EqualsDelegate AreEqual { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize a value using the best-known serialization method for a generic value.
|
||||||
|
/// Will reliably serialize any value that is passed to it correctly with no boxing.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
||||||
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
||||||
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
||||||
|
/// generic, it is better to use FastBufferWriter directly.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// If the codegen is unable to determine a serializer for a type,
|
||||||
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.WriteValue"/> is called, which, by default,
|
||||||
|
/// will throw an exception, unless you have assigned a user serialization callback to it at runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void Write(FastBufferWriter writer, ref T value)
|
||||||
|
{
|
||||||
|
Serializer.Write(writer, ref value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserialize a value using the best-known serialization method for a generic value.
|
||||||
|
/// Will reliably deserialize any value that is passed to it correctly with no boxing.
|
||||||
|
/// For types whose deserialization can be determined by codegen (which is most types),
|
||||||
|
/// GC will only be incurred if the type is a managed type and the ref value passed in is `null`,
|
||||||
|
/// in which case a new value is created; otherwise, it will be deserialized in-place.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
||||||
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
||||||
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
||||||
|
/// generic, it is better to use FastBufferReader directly.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// If the codegen is unable to determine a serializer for a type,
|
||||||
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.ReadValue"/> is called, which, by default,
|
||||||
|
/// will throw an exception, unless you have assigned a user deserialization callback to it at runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public static void Read(FastBufferReader reader, ref T value)
|
||||||
|
{
|
||||||
|
Serializer.Read(reader, ref value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duplicates a value using the most efficient means of creating a complete copy.
|
||||||
|
/// For most types this is a simple assignment or memcpy.
|
||||||
|
/// For managed types, this is will serialize and then deserialize the value to ensure
|
||||||
|
/// a correct copy.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Note: If you are using this in a custom generic class, please make sure your class is
|
||||||
|
/// decorated with <see cref="GenerateSerializationForGenericParameterAttribute"/> so that codegen can
|
||||||
|
/// initialize the serialization mechanisms correctly. If your class is NOT
|
||||||
|
/// generic, it is better to duplicate it directly.
|
||||||
|
/// <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// If the codegen is unable to determine a serializer for a type,
|
||||||
|
/// <see cref="UserNetworkVariableSerialization{T}"/>.<see cref="UserNetworkVariableSerialization{T}.DuplicateValue"/> is called, which, by default,
|
||||||
|
/// will throw an exception, unless you have assigned a user duplication callback to it at runtime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="duplicatedValue"></param>
|
||||||
|
public static void Duplicate(in T value, ref T duplicatedValue)
|
||||||
|
{
|
||||||
|
Serializer.Duplicate(value, ref duplicatedValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Compares two values of the same unmanaged type by underlying memory
|
// Compares two values of the same unmanaged type by underlying memory
|
||||||
// Ignoring any overridden value checks
|
// Ignoring any overridden value checks
|
||||||
@@ -1001,15 +1093,21 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
return a == b;
|
return a == b;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static void Write(FastBufferWriter writer, ref T value)
|
// RuntimeAccessModifiersILPP will make this `public`
|
||||||
|
// This is just pass-through to NetworkVariableSerialization<T> but is here becaues I could not get ILPP
|
||||||
|
// to generate code that would successfully call Type<T>.Method(T), but it has no problem calling Type.Method<T>(T)
|
||||||
|
internal class RpcFallbackSerialization
|
||||||
|
{
|
||||||
|
public static void Write<T>(FastBufferWriter writer, ref T value)
|
||||||
{
|
{
|
||||||
Serializer.Write(writer, ref value);
|
NetworkVariableSerialization<T>.Write(writer, ref value);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Read(FastBufferReader reader, ref T value)
|
public static void Read<T>(FastBufferReader reader, ref T value)
|
||||||
{
|
{
|
||||||
Serializer.Read(reader, ref value);
|
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
@@ -597,6 +598,47 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for integration tests, normal runtime mode this will always be LoadSceneMode.Single
|
||||||
|
/// </summary>
|
||||||
|
internal LoadSceneMode DeferLoadingFilter = LoadSceneMode.Single;
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a remote client should defer object creation initiated by CreateObjectMessage
|
||||||
|
/// until a scene event is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Deferring object creation should only occur when there is a possibility the objects could be
|
||||||
|
/// instantiated in a currently active scene that will be unloaded during single mode scene loading
|
||||||
|
/// to prevent the newly created objects from being destroyed when the scene is unloaded.
|
||||||
|
/// </remarks>
|
||||||
|
internal bool ShouldDeferCreateObject()
|
||||||
|
{
|
||||||
|
// This applies only to remote clients and when scene management is enabled
|
||||||
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement || NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var synchronizeEventDetected = false;
|
||||||
|
var loadingEventDetected = false;
|
||||||
|
foreach (var entry in SceneEventDataStore)
|
||||||
|
{
|
||||||
|
if (entry.Value.SceneEventType == SceneEventType.Synchronize)
|
||||||
|
{
|
||||||
|
synchronizeEventDetected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When loading a scene and the load scene mode is single we should defer object creation
|
||||||
|
if (entry.Value.SceneEventType == SceneEventType.Load && entry.Value.LoadSceneMode == DeferLoadingFilter)
|
||||||
|
{
|
||||||
|
loadingEventDetected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronizing while in client synchronization mode single --> Defer
|
||||||
|
// When not synchronizing but loading a scene in single mode --> Defer
|
||||||
|
return (synchronizeEventDetected && ClientSynchronizationMode == LoadSceneMode.Single) || (!synchronizeEventDetected && loadingEventDetected);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the scene name from full path to the scene
|
/// Gets the scene name from full path to the scene
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -740,6 +782,19 @@ namespace Unity.Netcode
|
|||||||
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
|
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
|
||||||
DontDestroyOnLoadScene = networkManager.gameObject.scene;
|
DontDestroyOnLoadScene = networkManager.gameObject.scene;
|
||||||
|
|
||||||
|
// Since the server tracks loaded scenes, we need to add any currently loaded scenes on the
|
||||||
|
// server side when the NetworkManager is started and NetworkSceneManager instantiated when
|
||||||
|
// scene management is enabled.
|
||||||
|
if (networkManager.IsServer && networkManager.NetworkConfig.EnableSceneManagement)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
|
{
|
||||||
|
var loadedScene = SceneManager.GetSceneAt(i);
|
||||||
|
ScenesLoaded.Add(loadedScene.handle, loadedScene);
|
||||||
|
}
|
||||||
|
SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, NetworkManager);
|
||||||
|
}
|
||||||
|
|
||||||
// Add to the server to client scene handle table
|
// Add to the server to client scene handle table
|
||||||
UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
|
UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
|
||||||
}
|
}
|
||||||
@@ -961,17 +1016,16 @@ namespace Unity.Netcode
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private SceneEventProgress ValidateSceneEventUnloading(Scene scene)
|
private SceneEventProgress ValidateSceneEventUnloading(Scene scene)
|
||||||
{
|
{
|
||||||
if (!NetworkManager.IsServer)
|
|
||||||
{
|
|
||||||
throw new NotServerException("Only server can start a scene event!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
//Log message about enabling SceneManagement
|
Debug.LogWarning($"{nameof(LoadScene)} was called, but {nameof(NetworkConfig.EnableSceneManagement)} was not enabled! Enable {nameof(NetworkConfig.EnableSceneManagement)} prior to starting a client, host, or server prior to using {nameof(NetworkSceneManager)}!");
|
||||||
throw new Exception(
|
return new SceneEventProgress(null, SceneEventProgressStatus.SceneManagementNotEnabled);
|
||||||
$"{nameof(NetworkConfig.EnableSceneManagement)} flag is not enabled in the {nameof(Netcode.NetworkManager)}'s {nameof(NetworkConfig)}. " +
|
}
|
||||||
$"Please set {nameof(NetworkConfig.EnableSceneManagement)} flag to true before calling {nameof(LoadScene)} or {nameof(UnloadScene)}.");
|
|
||||||
|
if (!NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Unload] Clients cannot invoke the {nameof(UnloadScene)} method!");
|
||||||
|
return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scene.isLoaded)
|
if (!scene.isLoaded)
|
||||||
@@ -990,16 +1044,16 @@ namespace Unity.Netcode
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private SceneEventProgress ValidateSceneEventLoading(string sceneName)
|
private SceneEventProgress ValidateSceneEventLoading(string sceneName)
|
||||||
{
|
{
|
||||||
if (!NetworkManager.IsServer)
|
|
||||||
{
|
|
||||||
throw new NotServerException("Only server can start a scene event!");
|
|
||||||
}
|
|
||||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
//Log message about enabling SceneManagement
|
Debug.LogWarning($"{nameof(LoadScene)} was called, but {nameof(NetworkConfig.EnableSceneManagement)} was not enabled! Enable {nameof(NetworkConfig.EnableSceneManagement)} prior to starting a client, host, or server prior to using {nameof(NetworkSceneManager)}!");
|
||||||
throw new Exception(
|
return new SceneEventProgress(null, SceneEventProgressStatus.SceneManagementNotEnabled);
|
||||||
$"{nameof(NetworkConfig.EnableSceneManagement)} flag is not enabled in the {nameof(Netcode.NetworkManager)}'s {nameof(NetworkConfig)}. " +
|
}
|
||||||
$"Please set {nameof(NetworkConfig.EnableSceneManagement)} flag to true before calling {nameof(LoadScene)} or {nameof(UnloadScene)}.");
|
|
||||||
|
if (!NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[{nameof(SceneEventProgressStatus.ServerOnlyAction)}][Load] Clients cannot invoke the {nameof(LoadScene)} method!");
|
||||||
|
return new SceneEventProgress(null, SceneEventProgressStatus.ServerOnlyAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValidateSceneEvent(sceneName);
|
return ValidateSceneEvent(sceneName);
|
||||||
@@ -1104,6 +1158,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
var sceneName = scene.name;
|
var sceneName = scene.name;
|
||||||
var sceneHandle = scene.handle;
|
var sceneHandle = scene.handle;
|
||||||
|
|
||||||
if (!scene.isLoaded)
|
if (!scene.isLoaded)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"{nameof(UnloadScene)} was called, but the scene {scene.name} is not currently loaded!");
|
Debug.LogWarning($"{nameof(UnloadScene)} was called, but the scene {scene.name} is not currently loaded!");
|
||||||
@@ -1689,6 +1744,9 @@ namespace Unity.Netcode
|
|||||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||||
m_IsSceneEventActive = false;
|
m_IsSceneEventActive = false;
|
||||||
|
|
||||||
|
// Process any pending create object messages that the client received while loading a scene
|
||||||
|
ProcessDeferredCreateObjectMessages();
|
||||||
|
|
||||||
// Notify local client that the scene was loaded
|
// Notify local client that the scene was loaded
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
{
|
{
|
||||||
@@ -2050,6 +2108,9 @@ namespace Unity.Netcode
|
|||||||
// If needed, migrate dynamically spawned NetworkObjects to the same scene as they are on the server
|
// If needed, migrate dynamically spawned NetworkObjects to the same scene as they are on the server
|
||||||
SynchronizeNetworkObjectScene();
|
SynchronizeNetworkObjectScene();
|
||||||
|
|
||||||
|
// Process any pending create object messages that the client received during synchronization
|
||||||
|
ProcessDeferredCreateObjectMessages();
|
||||||
|
|
||||||
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
|
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
|
||||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||||
|
|
||||||
@@ -2205,6 +2266,11 @@ namespace Unity.Netcode
|
|||||||
// of the client was persisted since MLAPI)
|
// of the client was persisted since MLAPI)
|
||||||
NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(clientId);
|
NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(clientId);
|
||||||
|
|
||||||
|
if (NetworkManager.IsHost)
|
||||||
|
{
|
||||||
|
NetworkManager.ConnectionManager.InvokeOnPeerConnectedCallback(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
// Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
|
// Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
|
||||||
// a potential crash within the MessageSystem (i.e. sending to a client that no longer exists)
|
// a potential crash within the MessageSystem (i.e. sending to a client that no longer exists)
|
||||||
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization && NetworkManager.ConnectedClients.ContainsKey(clientId))
|
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization && NetworkManager.ConnectedClients.ContainsKey(clientId))
|
||||||
@@ -2541,5 +2607,50 @@ namespace Unity.Netcode
|
|||||||
internal Dictionary<int, List<ulong>> ObjectsMigratedTable;
|
internal Dictionary<int, List<ulong>> ObjectsMigratedTable;
|
||||||
}
|
}
|
||||||
internal List<DeferredObjectsMovedEvent> DeferredObjectsMovedEvents = new List<DeferredObjectsMovedEvent>();
|
internal List<DeferredObjectsMovedEvent> DeferredObjectsMovedEvents = new List<DeferredObjectsMovedEvent>();
|
||||||
|
|
||||||
|
internal struct DeferredObjectCreation
|
||||||
|
{
|
||||||
|
internal ulong SenderId;
|
||||||
|
internal uint MessageSize;
|
||||||
|
internal NetworkObject.SceneObject SceneObject;
|
||||||
|
internal FastBufferReader FastBufferReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal List<DeferredObjectCreation> DeferredObjectCreationList = new List<DeferredObjectCreation>();
|
||||||
|
internal int DeferredObjectCreationCount;
|
||||||
|
|
||||||
|
internal void DeferCreateObject(ulong senderId, uint messageSize, NetworkObject.SceneObject sceneObject, FastBufferReader fastBufferReader)
|
||||||
|
{
|
||||||
|
var deferredObjectCreationEntry = new DeferredObjectCreation()
|
||||||
|
{
|
||||||
|
SenderId = senderId,
|
||||||
|
MessageSize = messageSize,
|
||||||
|
SceneObject = sceneObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
deferredObjectCreationEntry.FastBufferReader = new FastBufferReader(fastBufferReader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, fastBufferReader.Length - fastBufferReader.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeferredObjectCreationList.Add(deferredObjectCreationEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessDeferredCreateObjectMessages()
|
||||||
|
{
|
||||||
|
// If no pending create object messages exit early
|
||||||
|
if (DeferredObjectCreationList.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var networkManager = NetworkManager;
|
||||||
|
// Process all deferred create object messages.
|
||||||
|
foreach (var deferredObjectCreation in DeferredObjectCreationList)
|
||||||
|
{
|
||||||
|
CreateObjectMessage.CreateObject(ref networkManager, deferredObjectCreation.SenderId, deferredObjectCreation.MessageSize, deferredObjectCreation.SceneObject, deferredObjectCreation.FastBufferReader);
|
||||||
|
}
|
||||||
|
DeferredObjectCreationCount = DeferredObjectCreationList.Count;
|
||||||
|
DeferredObjectCreationList.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ namespace Unity.Netcode
|
|||||||
/// If you receive this event then it is most likely due to a bug (<em>please open a GitHub issue with steps to replicate</em>).<br/>
|
/// If you receive this event then it is most likely due to a bug (<em>please open a GitHub issue with steps to replicate</em>).<br/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InternalNetcodeError,
|
InternalNetcodeError,
|
||||||
|
/// <summary>
|
||||||
|
/// This is returned when an unload or load action is attempted and scene management is disabled
|
||||||
|
/// </summary>
|
||||||
|
SceneManagementNotEnabled,
|
||||||
|
/// <summary>
|
||||||
|
/// This is returned when a client attempts to perform a server only action
|
||||||
|
/// </summary>
|
||||||
|
ServerOnlyAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ namespace Unity.Netcode
|
|||||||
throw new SpawnStateException("Object is not spawned");
|
throw new SpawnStateException("Object is not spawned");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var previous = networkObject.OwnerClientId;
|
||||||
// Assign the new owner
|
// Assign the new owner
|
||||||
networkObject.OwnerClientId = clientId;
|
networkObject.OwnerClientId = clientId;
|
||||||
|
|
||||||
@@ -286,6 +287,12 @@ namespace Unity.Netcode
|
|||||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After we have sent the change ownership message to all client observers, invoke the ownership changed notification.
|
||||||
|
/// !!Important!!
|
||||||
|
/// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership
|
||||||
|
/// change can be sent from NetworkBehaviours that override the <see cref="NetworkBehaviour.OnOwnershipChanged"></see>
|
||||||
|
networkObject.InvokeOwnershipChanged(previous, clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||||
@@ -315,6 +322,167 @@ namespace Unity.Netcode
|
|||||||
return networkObject != null;
|
return networkObject != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum InstantiateAndSpawnErrorTypes
|
||||||
|
{
|
||||||
|
NetworkPrefabNull,
|
||||||
|
NotAuthority,
|
||||||
|
InvokedWhenShuttingDown,
|
||||||
|
NotRegisteredNetworkPrefab,
|
||||||
|
NetworkManagerNull,
|
||||||
|
NoActiveSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static readonly Dictionary<InstantiateAndSpawnErrorTypes, string> InstantiateAndSpawnErrors = new Dictionary<InstantiateAndSpawnErrorTypes, string>(
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>[]{
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>(InstantiateAndSpawnErrorTypes.NetworkPrefabNull, $"The {nameof(NetworkObject)} prefab parameter was null!"),
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>(InstantiateAndSpawnErrorTypes.NotAuthority, $"Only the server has authority to {nameof(InstantiateAndSpawn)}!"),
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>(InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown, $"Invoking {nameof(InstantiateAndSpawn)} while shutting down! Calls to {nameof(InstantiateAndSpawn)} will be ignored."),
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>(InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab, $"The {nameof(NetworkObject)} parameter is not a registered network prefab. Did you forget to register it or are you trying to instantiate and spawn an instance of a network prefab?"),
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>(InstantiateAndSpawnErrorTypes.NetworkManagerNull, $"The {nameof(NetworkManager)} parameter was null!"),
|
||||||
|
new KeyValuePair<InstantiateAndSpawnErrorTypes, string>(InstantiateAndSpawnErrorTypes.NoActiveSession, "You can only invoke this method when you are connected to an existing/in-progress network session!")
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use this method to easily instantiate and spawn an instance of a network prefab.
|
||||||
|
/// InstantiateAndSpawn will:
|
||||||
|
/// - Find any override associated with the <see cref="NetworkObject"/> prefab
|
||||||
|
/// - If there is no override, then the current <see cref="NetworkObject"/> prefab type is used.
|
||||||
|
/// - Create an instance of the <see cref="NetworkObject"/> prefab (or its override).
|
||||||
|
/// - Spawn the <see cref="NetworkObject"/> prefab instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="networkPrefab">The <see cref="NetworkObject"/> of the pefab asset.</param>
|
||||||
|
/// <param name="ownerClientId">The owner of the <see cref="NetworkObject"/> instance (defaults to server).</param>
|
||||||
|
/// <param name="destroyWithScene">Whether the <see cref="NetworkObject"/> instance will be destroyed when the scene it is located within is unloaded (default is false).</param>
|
||||||
|
/// <param name="isPlayerObject">Whether the <see cref="NetworkObject"/> instance is a player object or not (default is false).</param>
|
||||||
|
/// <param name="forceOverride">Whether you want to force spawning the override when running as a host or server or if you want it to spawn the override for host mode and
|
||||||
|
/// the source prefab for server. If there is an override, clients always spawn that as opposed to the source prefab (defaults to false). </param>
|
||||||
|
/// <param name="position">The starting poisiton of the <see cref="NetworkObject"/> instance.</param>
|
||||||
|
/// <param name="rotation">The starting rotation of the <see cref="NetworkObject"/> instance.</param>
|
||||||
|
/// <returns>The newly instantiated and spawned <see cref="NetworkObject"/> prefab instance.</returns>
|
||||||
|
public NetworkObject InstantiateAndSpawn(NetworkObject networkPrefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default)
|
||||||
|
{
|
||||||
|
if (networkPrefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError(InstantiateAndSpawnErrors[InstantiateAndSpawnErrorTypes.NetworkPrefabNull]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogError(InstantiateAndSpawnErrors[InstantiateAndSpawnErrorTypes.NotAuthority]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(InstantiateAndSpawnErrors[InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it is actually a valid prefab
|
||||||
|
if (!NetworkManager.NetworkConfig.Prefabs.Contains(networkPrefab.gameObject))
|
||||||
|
{
|
||||||
|
Debug.LogError(InstantiateAndSpawnErrors[InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InstantiateAndSpawnNoParameterChecks(networkPrefab, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// !!! Does not perform any parameter checks prior to attempting to instantiate and spawn the NetworkObject !!!
|
||||||
|
/// </summary>
|
||||||
|
internal NetworkObject InstantiateAndSpawnNoParameterChecks(NetworkObject networkPrefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default)
|
||||||
|
{
|
||||||
|
|
||||||
|
var networkObject = networkPrefab;
|
||||||
|
// Host spawns the ovveride and server spawns the original prefab unless forceOverride is set to true where both server or host will spawn the override.
|
||||||
|
if (forceOverride || NetworkManager.IsHost)
|
||||||
|
{
|
||||||
|
networkObject = GetNetworkObjectToSpawn(networkPrefab.GlobalObjectIdHash, ownerClientId, position, rotation);
|
||||||
|
}
|
||||||
|
if (networkObject == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Failed to instantiate and spawn {networkPrefab.name}!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
networkObject.IsPlayerObject = isPlayerObject;
|
||||||
|
networkObject.transform.position = position;
|
||||||
|
networkObject.transform.rotation = rotation;
|
||||||
|
networkObject.SpawnWithOwnership(ownerClientId, destroyWithScene);
|
||||||
|
return networkObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the
|
||||||
|
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
|
||||||
|
/// </summary>
|
||||||
|
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3 position = default, Quaternion rotation = default, bool isScenePlaced = false)
|
||||||
|
{
|
||||||
|
NetworkObject networkObject = null;
|
||||||
|
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
||||||
|
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||||
|
{
|
||||||
|
// Let the handler spawn the NetworkObject
|
||||||
|
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position, rotation);
|
||||||
|
networkObject.NetworkManagerOwner = NetworkManager;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash
|
||||||
|
var networkPrefabReference = (GameObject)null;
|
||||||
|
var inScenePlacedWithNoSceneManagement = !NetworkManager.NetworkConfig.EnableSceneManagement && isScenePlaced;
|
||||||
|
|
||||||
|
if (NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
|
||||||
|
{
|
||||||
|
var networkPrefab = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash];
|
||||||
|
|
||||||
|
switch (networkPrefab.Override)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case NetworkPrefabOverride.None:
|
||||||
|
networkPrefabReference = networkPrefab.Prefab;
|
||||||
|
break;
|
||||||
|
case NetworkPrefabOverride.Hash:
|
||||||
|
case NetworkPrefabOverride.Prefab:
|
||||||
|
{
|
||||||
|
// When scene management is disabled and this is an in-scene placed NetworkObject, we want to always use the
|
||||||
|
// SourcePrefabToOverride and not any possible prefab override as a user might want to spawn overrides dynamically
|
||||||
|
// but might want to use the same source network prefab as an in-scene placed NetworkObject.
|
||||||
|
// (When scene management is enabled, clients don't delete their in-scene placed NetworkObjects prior to dynamically
|
||||||
|
// spawning them so the original prefab placed is preserved and this is not needed)
|
||||||
|
if (inScenePlacedWithNoSceneManagement)
|
||||||
|
{
|
||||||
|
networkPrefabReference = networkPrefab.SourcePrefabToOverride ? networkPrefab.SourcePrefabToOverride : networkPrefab.Prefab;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.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)}?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create prefab instance
|
||||||
|
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
|
||||||
|
networkObject.NetworkManagerOwner = NetworkManager;
|
||||||
|
networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return networkObject;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a local NetowrkObject to be spawned.
|
/// Creates a local NetowrkObject to be spawned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -336,48 +504,7 @@ namespace Unity.Netcode
|
|||||||
// If scene management is disabled or the NetworkObject was dynamically spawned
|
// If scene management is disabled or the NetworkObject was dynamically spawned
|
||||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
|
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
|
||||||
{
|
{
|
||||||
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
networkObject = GetNetworkObjectToSpawn(sceneObject.Hash, sceneObject.OwnerClientId, position, rotation, sceneObject.IsSceneObject);
|
||||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
|
||||||
{
|
|
||||||
// Let the handler spawn the NetworkObject
|
|
||||||
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation);
|
|
||||||
networkObject.NetworkManagerOwner = NetworkManager;
|
|
||||||
isSpawnedByPrefabHandler = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash
|
|
||||||
GameObject networkPrefabReference = null;
|
|
||||||
if (NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
|
|
||||||
{
|
|
||||||
switch (NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].Override)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
case NetworkPrefabOverride.None:
|
|
||||||
networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].Prefab;
|
|
||||||
break;
|
|
||||||
case NetworkPrefabOverride.Hash:
|
|
||||||
case NetworkPrefabOverride.Prefab:
|
|
||||||
networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.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)}?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Create prefab instance
|
|
||||||
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
|
|
||||||
networkObject.NetworkManagerOwner = NetworkManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else // Get the in-scene placed NetworkObject
|
else // Get the in-scene placed NetworkObject
|
||||||
{
|
{
|
||||||
@@ -617,6 +744,13 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
networkObject.SubscribeToActiveSceneForSynch();
|
networkObject.SubscribeToActiveSceneForSynch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set
|
||||||
|
// then assign this to the PrefabGlobalObjectIdHash
|
||||||
|
if (networkObject.IsSceneObject.Value && networkObject.InScenePlacedSourceGlobalObjectIdHash != 0)
|
||||||
|
{
|
||||||
|
networkObject.PrefabGlobalObjectIdHash = networkObject.InScenePlacedSourceGlobalObjectIdHash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||||
@@ -721,17 +855,43 @@ namespace Unity.Netcode
|
|||||||
// Leave destruction up to the handler
|
// Leave destruction up to the handler
|
||||||
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
|
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
|
||||||
}
|
}
|
||||||
else if (networkObjects[i].IsSpawned)
|
else
|
||||||
{
|
{
|
||||||
// If it is an in-scene placed NetworkObject then just despawn
|
// If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene
|
||||||
// and let it be destroyed when the scene is unloaded. Otherwise, despawn and destroy it.
|
// is unloaded. Otherwise, despawn and destroy it.
|
||||||
var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
|
var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
|
||||||
|
|
||||||
OnDespawnObject(networkObjects[i], shouldDestroy);
|
// If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed
|
||||||
}
|
if (shouldDestroy)
|
||||||
else if (networkObjects[i].IsSceneObject != null && !networkObjects[i].IsSceneObject.Value)
|
{
|
||||||
{
|
// Check to see if there are any in-scene placed children that are marked to be destroyed with the scene
|
||||||
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
|
var childrenObjects = networkObjects[i].GetComponentsInChildren<NetworkObject>();
|
||||||
|
foreach (var childObject in childrenObjects)
|
||||||
|
{
|
||||||
|
if (childObject == networkObjects[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the child is an in-scene placed NetworkObject then remove the child from the parent (which was dynamically spawned)
|
||||||
|
// and set its parent to root
|
||||||
|
if (childObject.IsSceneObject != null && childObject.IsSceneObject.Value)
|
||||||
|
{
|
||||||
|
childObject.TryRemoveParent(childObject.WorldPositionStays());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If spawned, then despawn and potentially destroy.
|
||||||
|
if (networkObjects[i].IsSpawned)
|
||||||
|
{
|
||||||
|
OnDespawnObject(networkObjects[i], shouldDestroy);
|
||||||
|
}
|
||||||
|
else // Otherwise, if we are not spawned and we should destroy...then destroy.
|
||||||
|
if (shouldDestroy)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -781,7 +941,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (networkObjects[i].NetworkManager == NetworkManager)
|
if (networkObjects[i].NetworkManager == NetworkManager)
|
||||||
{
|
{
|
||||||
if (networkObjects[i].IsSceneObject == null)
|
if (networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject.HasValue && networkObjects[i].IsSceneObject.Value))
|
||||||
{
|
{
|
||||||
networkObjectsToSpawn.Add(networkObjects[i]);
|
networkObjectsToSpawn.Add(networkObjects[i]);
|
||||||
}
|
}
|
||||||
@@ -930,7 +1090,7 @@ namespace Unity.Netcode
|
|||||||
if (sobj.CheckObjectVisibility == null)
|
if (sobj.CheckObjectVisibility == null)
|
||||||
{
|
{
|
||||||
// If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server
|
// If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server
|
||||||
if (!sobj.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId))
|
if (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId)
|
||||||
{
|
{
|
||||||
sobj.Observers.Add(clientId);
|
sobj.Observers.Add(clientId);
|
||||||
}
|
}
|
||||||
@@ -943,7 +1103,6 @@ namespace Unity.Netcode
|
|||||||
sobj.Observers.Add(clientId);
|
sobj.Observers.Add(clientId);
|
||||||
}
|
}
|
||||||
else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false
|
else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false
|
||||||
if (sobj.Observers.Contains(clientId))
|
|
||||||
{
|
{
|
||||||
sobj.Observers.Remove(clientId);
|
sobj.Observers.Remove(clientId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ namespace Unity.Netcode.Transports.UNET
|
|||||||
public override bool StartClient()
|
public override bool StartClient()
|
||||||
{
|
{
|
||||||
m_ServerHostId = UnityEngine.Networking.NetworkTransport.AddHost(new HostTopology(GetConfig(), 1), 0, null);
|
m_ServerHostId = UnityEngine.Networking.NetworkTransport.AddHost(new HostTopology(GetConfig(), 1), 0, null);
|
||||||
m_ServerConnectionId = UnityEngine.Networking.NetworkTransport.Connect(m_ServerHostId, ConnectAddress, ConnectPort, 0, out byte error);
|
m_ServerConnectionId = UnityEngine.Networking.NetworkTransport.Connect(m_ServerHostId, ConnectAddress, GetConnectPort(), 0, out byte error);
|
||||||
return (NetworkError)error == NetworkError.Ok;
|
return (NetworkError)error == NetworkError.Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ namespace Unity.Netcode.Transports.UNET
|
|||||||
{
|
{
|
||||||
var topology = new HostTopology(GetConfig(), MaxConnections);
|
var topology = new HostTopology(GetConfig(), MaxConnections);
|
||||||
// Undocumented, but AddHost returns -1 in case of any type of failure. See UNET::NetLibraryManager::AddHost
|
// Undocumented, but AddHost returns -1 in case of any type of failure. See UNET::NetLibraryManager::AddHost
|
||||||
return -1 != UnityEngine.Networking.NetworkTransport.AddHost(topology, ServerListenPort, null);
|
return -1 != UnityEngine.Networking.NetworkTransport.AddHost(topology, GetServerListenPort(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DisconnectRemoteClient(ulong clientId)
|
public override void DisconnectRemoteClient(ulong clientId)
|
||||||
@@ -281,6 +281,26 @@ namespace Unity.Netcode.Transports.UNET
|
|||||||
|
|
||||||
return connectionConfig;
|
return connectionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int GetConnectPort()
|
||||||
|
{
|
||||||
|
if (NetworkManager && NetworkManager.PortOverride.Overidden)
|
||||||
|
{
|
||||||
|
return NetworkManager.PortOverride.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConnectPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetServerListenPort()
|
||||||
|
{
|
||||||
|
if (NetworkManager && NetworkManager.PortOverride.Overidden)
|
||||||
|
{
|
||||||
|
return NetworkManager.PortOverride.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerListenPort;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
||||||
|
|||||||
@@ -198,43 +198,69 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
/// could lead to a corrupted queue.
|
/// could lead to a corrupted queue.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
|
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
|
||||||
|
/// <param name="softMaxBytes">
|
||||||
|
/// Maximum number of bytes to copy (0 means writer capacity). This is a soft limit only.
|
||||||
|
/// If a message is larger than that but fits in the writer, it will be written. In effect,
|
||||||
|
/// this parameter is the maximum size that small messages can be coalesced together.
|
||||||
|
/// </param>
|
||||||
/// <returns>How many bytes were written to the writer.</returns>
|
/// <returns>How many bytes were written to the writer.</returns>
|
||||||
public int FillWriterWithMessages(ref DataStreamWriter writer)
|
public int FillWriterWithMessages(ref DataStreamWriter writer, int softMaxBytes = 0)
|
||||||
{
|
{
|
||||||
if (!IsCreated || Length == 0)
|
if (!IsCreated || Length == 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
softMaxBytes = softMaxBytes == 0 ? writer.Capacity : Math.Min(softMaxBytes, writer.Capacity);
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
var reader = new DataStreamReader(m_Data.AsArray());
|
var reader = new DataStreamReader(m_Data.AsArray());
|
||||||
|
|
||||||
var writerAvailable = writer.Capacity;
|
|
||||||
var readerOffset = HeadIndex;
|
var readerOffset = HeadIndex;
|
||||||
|
|
||||||
while (readerOffset < TailIndex)
|
reader.SeekSet(readerOffset);
|
||||||
|
var messageLength = reader.ReadInt();
|
||||||
|
var bytesToWrite = messageLength + sizeof(int);
|
||||||
|
|
||||||
|
// Our behavior here depends on the size of the first message in the queue. If it's
|
||||||
|
// larger than the soft limit, then add only that message to the writer (we want
|
||||||
|
// large payloads to be fragmented on their own). Otherwise coalesce all small
|
||||||
|
// messages until we hit the soft limit (which presumably means they won't be
|
||||||
|
// fragmented, which is the desired behavior for smaller messages).
|
||||||
|
|
||||||
|
if (bytesToWrite > softMaxBytes && bytesToWrite <= writer.Capacity)
|
||||||
{
|
{
|
||||||
reader.SeekSet(readerOffset);
|
writer.WriteInt(messageLength);
|
||||||
var messageLength = reader.ReadInt();
|
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength);
|
||||||
|
|
||||||
if (writerAvailable < sizeof(int) + messageLength)
|
return bytesToWrite;
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
writer.WriteInt(messageLength);
|
|
||||||
|
|
||||||
var messageOffset = reader.GetBytesRead();
|
|
||||||
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
|
|
||||||
|
|
||||||
writerAvailable -= sizeof(int) + messageLength;
|
|
||||||
readerOffset += sizeof(int) + messageLength;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var bytesWritten = 0;
|
||||||
|
|
||||||
return writer.Capacity - writerAvailable;
|
while (readerOffset < TailIndex)
|
||||||
|
{
|
||||||
|
reader.SeekSet(readerOffset);
|
||||||
|
messageLength = reader.ReadInt();
|
||||||
|
bytesToWrite = messageLength + sizeof(int);
|
||||||
|
|
||||||
|
if (bytesWritten + bytesToWrite <= softMaxBytes)
|
||||||
|
{
|
||||||
|
writer.WriteInt(messageLength);
|
||||||
|
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + reader.GetBytesRead(), messageLength);
|
||||||
|
|
||||||
|
readerOffset += bytesToWrite;
|
||||||
|
bytesWritten += bytesToWrite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +268,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
/// Fill the given <see cref="DataStreamWriter"/> with as many bytes from the queue as
|
/// Fill the given <see cref="DataStreamWriter"/> with as many bytes from the queue as
|
||||||
/// possible, disregarding message boundaries.
|
/// possible, disregarding message boundaries.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
///<remarks>
|
/// <remarks>
|
||||||
/// This does NOT actually consume anything from the queue. That is, calling this method
|
/// This does NOT actually consume anything from the queue. That is, calling this method
|
||||||
/// does not reduce the length of the queue. Callers are expected to call
|
/// does not reduce the length of the queue. Callers are expected to call
|
||||||
/// <see cref="Consume"/> with the value returned by this method afterwards if the data can
|
/// <see cref="Consume"/> with the value returned by this method afterwards if the data can
|
||||||
@@ -252,15 +278,17 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
/// this could lead to reading messages from a corrupted queue.
|
/// this could lead to reading messages from a corrupted queue.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
|
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
|
||||||
|
/// <param name="maxBytes">Max number of bytes to copy (0 means writer capacity).</param>
|
||||||
/// <returns>How many bytes were written to the writer.</returns>
|
/// <returns>How many bytes were written to the writer.</returns>
|
||||||
public int FillWriterWithBytes(ref DataStreamWriter writer)
|
public int FillWriterWithBytes(ref DataStreamWriter writer, int maxBytes = 0)
|
||||||
{
|
{
|
||||||
if (!IsCreated || Length == 0)
|
if (!IsCreated || Length == 0)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var copyLength = Math.Min(writer.Capacity, Length);
|
var maxLength = maxBytes == 0 ? writer.Capacity : Math.Min(maxBytes, writer.Capacity);
|
||||||
|
var copyLength = Math.Min(maxLength, Length);
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -402,6 +402,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
/// - packet jitter (variances in latency, see: https://en.wikipedia.org/wiki/Jitter)
|
/// - packet jitter (variances in latency, see: https://en.wikipedia.org/wiki/Jitter)
|
||||||
/// - packet drop rate (packet loss)
|
/// - packet drop rate (packet loss)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
#if UTP_TRANSPORT_2_0_ABOVE
|
#if UTP_TRANSPORT_2_0_ABOVE
|
||||||
[Obsolete("DebugSimulator is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)]
|
[Obsolete("DebugSimulator is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)]
|
||||||
#endif
|
#endif
|
||||||
@@ -685,9 +686,11 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
/// <param name="packetDelay">Packet delay in milliseconds.</param>
|
/// <param name="packetDelay">Packet delay in milliseconds.</param>
|
||||||
/// <param name="packetJitter">Packet jitter in milliseconds.</param>
|
/// <param name="packetJitter">Packet jitter in milliseconds.</param>
|
||||||
/// <param name="dropRate">Packet drop percentage.</param>
|
/// <param name="dropRate">Packet drop percentage.</param>
|
||||||
|
|
||||||
#if UTP_TRANSPORT_2_0_ABOVE
|
#if UTP_TRANSPORT_2_0_ABOVE
|
||||||
[Obsolete("SetDebugSimulatorParameters is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)]
|
[Obsolete("SetDebugSimulatorParameters is no longer supported and has no effect. Use Network Simulator from the Multiplayer Tools package.", false)]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public void SetDebugSimulatorParameters(int packetDelay, int packetJitter, int dropRate)
|
public void SetDebugSimulatorParameters(int packetDelay, int packetJitter, int dropRate)
|
||||||
{
|
{
|
||||||
if (m_Driver.IsCreated)
|
if (m_Driver.IsCreated)
|
||||||
@@ -727,6 +730,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
public SendTarget Target;
|
public SendTarget Target;
|
||||||
public BatchedSendQueue Queue;
|
public BatchedSendQueue Queue;
|
||||||
public NetworkPipeline ReliablePipeline;
|
public NetworkPipeline ReliablePipeline;
|
||||||
|
public int MTU;
|
||||||
|
|
||||||
public void Execute()
|
public void Execute()
|
||||||
{
|
{
|
||||||
@@ -749,7 +753,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
// in the stream (the send queue does that automatically) we are sure they'll be
|
// in the stream (the send queue does that automatically) we are sure they'll be
|
||||||
// reassembled properly at the other end. This allows us to lift the limit of ~44KB
|
// reassembled properly at the other end. This allows us to lift the limit of ~44KB
|
||||||
// on reliable payloads (because of the reliable window size).
|
// on reliable payloads (because of the reliable window size).
|
||||||
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer) : Queue.FillWriterWithMessages(ref writer);
|
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer, MTU) : Queue.FillWriterWithMessages(ref writer, MTU);
|
||||||
|
|
||||||
result = Driver.EndSend(writer);
|
result = Driver.EndSend(writer);
|
||||||
if (result == written)
|
if (result == written)
|
||||||
@@ -783,12 +787,21 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mtu = 0;
|
||||||
|
if (NetworkManager)
|
||||||
|
{
|
||||||
|
var ngoClientId = NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId);
|
||||||
|
mtu = NetworkManager.GetPeerMTU(ngoClientId);
|
||||||
|
}
|
||||||
|
|
||||||
new SendBatchedMessagesJob
|
new SendBatchedMessagesJob
|
||||||
{
|
{
|
||||||
Driver = m_Driver.ToConcurrent(),
|
Driver = m_Driver.ToConcurrent(),
|
||||||
Target = sendTarget,
|
Target = sendTarget,
|
||||||
Queue = queue,
|
Queue = queue,
|
||||||
ReliablePipeline = m_ReliableSequencedPipeline
|
ReliablePipeline = m_ReliableSequencedPipeline,
|
||||||
|
MTU = mtu,
|
||||||
}.Run();
|
}.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -982,6 +995,11 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
private void ExtractNetworkMetricsFromPipeline(NetworkPipeline pipeline, NetworkConnection networkConnection)
|
private void ExtractNetworkMetricsFromPipeline(NetworkPipeline pipeline, NetworkConnection networkConnection)
|
||||||
{
|
{
|
||||||
|
if (m_Driver.GetConnectionState(networkConnection) != NetworkConnection.State.Connected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Don't need to dispose of the buffers, they are filled with data pointers.
|
//Don't need to dispose of the buffers, they are filled with data pointers.
|
||||||
m_Driver.GetPipelineBuffers(pipeline,
|
m_Driver.GetPipelineBuffers(pipeline,
|
||||||
#if UTP_TRANSPORT_2_0_ABOVE
|
#if UTP_TRANSPORT_2_0_ABOVE
|
||||||
@@ -1199,6 +1217,11 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
|
|
||||||
|
if (NetworkManager && NetworkManager.PortOverride.Overidden)
|
||||||
|
{
|
||||||
|
ConnectionData.Port = NetworkManager.PortOverride.Value;
|
||||||
|
}
|
||||||
|
|
||||||
m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider();
|
m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider();
|
||||||
|
|
||||||
m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
|
m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
|
||||||
@@ -1211,7 +1234,18 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
// Bump the reliable window size to its maximum size of 64. Since NGO makes heavy use of
|
// Bump the reliable window size to its maximum size of 64. Since NGO makes heavy use of
|
||||||
// reliable delivery, we're better off with the increased window size compared to the
|
// reliable delivery, we're better off with the increased window size compared to the
|
||||||
// extra 4 bytes of header that this costs us.
|
// extra 4 bytes of header that this costs us.
|
||||||
m_NetworkSettings.WithReliableStageParameters(windowSize: 64);
|
//
|
||||||
|
// We also increase the maximum resend timeout since the default one in UTP is very
|
||||||
|
// aggressive (optimized for latency and low bandwidth). With NGO, it's too low and
|
||||||
|
// we sometimes notice a lot of useless resends, especially if using Relay. (We can
|
||||||
|
// only do this with UTP 2.0 because 1.X doesn't support that parameter.)
|
||||||
|
m_NetworkSettings.WithReliableStageParameters(
|
||||||
|
windowSize: 64
|
||||||
|
#if UTP_TRANSPORT_2_0_ABOVE
|
||||||
|
,
|
||||||
|
maximumResendTime: m_ProtocolType == ProtocolType.RelayUnityTransport ? 750 : 500
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
#if !UTP_TRANSPORT_2_0_ABOVE && !UNITY_WEBGL
|
#if !UTP_TRANSPORT_2_0_ABOVE && !UNITY_WEBGL
|
||||||
m_NetworkSettings.WithBaselibNetworkInterfaceParameters(
|
m_NetworkSettings.WithBaselibNetworkInterfaceParameters(
|
||||||
@@ -1560,6 +1594,21 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if UTP_TRANSPORT_2_1_ABOVE
|
||||||
|
if (m_ProtocolType == ProtocolType.RelayUnityTransport)
|
||||||
|
{
|
||||||
|
if (m_UseWebSockets && m_RelayServerData.IsWebSocket == 0)
|
||||||
|
{
|
||||||
|
Debug.LogError("Transport is configured to use WebSockets, but Relay server data isn't. Be sure to use \"wss\" as the connection type when creating the server data (instead of \"dtls\" or \"udp\").");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_UseWebSockets && m_RelayServerData.IsWebSocket != 0)
|
||||||
|
{
|
||||||
|
Debug.LogError("Relay server data indicates usage of WebSockets, but \"Use WebSockets\" checkbox isn't checked under \"Unity Transport\" component.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if UTP_TRANSPORT_2_0_ABOVE
|
#if UTP_TRANSPORT_2_0_ABOVE
|
||||||
if (m_UseWebSockets)
|
if (m_UseWebSockets)
|
||||||
{
|
{
|
||||||
@@ -1567,7 +1616,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if UNITY_WEBGL
|
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||||
Debug.LogWarning($"WebSockets were used even though they're not selected in NetworkManager. You should check {nameof(UseWebSockets)}', on the Unity Transport component, to silence this warning.");
|
Debug.LogWarning($"WebSockets were used even though they're not selected in NetworkManager. You should check {nameof(UseWebSockets)}', on the Unity Transport component, to silence this warning.");
|
||||||
driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings);
|
driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings);
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -36,6 +36,16 @@
|
|||||||
"name": "com.unity.transport",
|
"name": "com.unity.transport",
|
||||||
"expression": "2.0.0-exp",
|
"expression": "2.0.0-exp",
|
||||||
"define": "UTP_TRANSPORT_2_0_ABOVE"
|
"define": "UTP_TRANSPORT_2_0_ABOVE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "com.unity.transport",
|
||||||
|
"expression": "2.1.0",
|
||||||
|
"define": "UTP_TRANSPORT_2_1_ABOVE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unity",
|
||||||
|
"expression": "2023",
|
||||||
|
"define": "UNITY_DEDICATED_SERVER_ARGUMENTS_PRESENT"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -913,6 +913,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
if (CoroutineRunner == null)
|
if (CoroutineRunner == null)
|
||||||
{
|
{
|
||||||
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
|
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
|
||||||
|
// Move the CoroutineRunner into the DDOL in case we unload the scene it was instantiated in.
|
||||||
|
// (which if that gets destroyed then it basically stops all integration test queue processing)
|
||||||
|
Object.DontDestroyOnLoad(CoroutineRunner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -305,8 +305,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
public IEnumerator SetUp()
|
public IEnumerator SetUp()
|
||||||
{
|
{
|
||||||
VerboseDebug($"Entering {nameof(SetUp)}");
|
VerboseDebug($"Entering {nameof(SetUp)}");
|
||||||
|
|
||||||
NetcodeLogAssert = new NetcodeLogAssert();
|
NetcodeLogAssert = new NetcodeLogAssert();
|
||||||
|
if (m_EnableTimeTravel)
|
||||||
|
{
|
||||||
|
// Setup the frames per tick for time travel advance to next tick
|
||||||
|
ConfigureFramesPerTick();
|
||||||
|
}
|
||||||
if (m_SetupIsACoroutine)
|
if (m_SetupIsACoroutine)
|
||||||
{
|
{
|
||||||
yield return OnSetup();
|
yield return OnSetup();
|
||||||
@@ -732,6 +736,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
Assert.Fail("Failed to start instances");
|
Assert.Fail("Failed to start instances");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When scene management is enabled, we need to re-apply the scenes populated list since we have overriden the ISceneManagerHandler
|
||||||
|
// imeplementation at this point. This assures any pre-loaded scenes will be automatically assigned to the server and force clients
|
||||||
|
// to load their own scenes.
|
||||||
|
if (m_ServerNetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
|
{
|
||||||
|
var scenesLoaded = m_ServerNetworkManager.SceneManager.ScenesLoaded;
|
||||||
|
m_ServerNetworkManager.SceneManager.SceneManagerHandler.PopulateLoadedScenes(ref scenesLoaded, m_ServerNetworkManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (LogAllMessages)
|
if (LogAllMessages)
|
||||||
{
|
{
|
||||||
EnableMessageLogging();
|
EnableMessageLogging();
|
||||||
@@ -799,6 +813,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
Assert.Fail("Failed to start instances");
|
Assert.Fail("Failed to start instances");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Time travel does not play nice with scene loading, clear out server side pre-loaded scenes.
|
||||||
|
if (m_ServerNetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
|
{
|
||||||
|
m_ServerNetworkManager.SceneManager.ScenesLoaded.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (LogAllMessages)
|
if (LogAllMessages)
|
||||||
{
|
{
|
||||||
EnableMessageLogging();
|
EnableMessageLogging();
|
||||||
@@ -886,7 +906,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
|
IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
|
||||||
IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
|
IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
|
||||||
NetcodeIntegrationTestHelpers.RegisterSceneManagerHandler(m_ServerNetworkManager, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ClientSceneHandler_CanClientsUnload()
|
private bool ClientSceneHandler_CanClientsUnload()
|
||||||
@@ -1545,8 +1564,42 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual uint GetTickRate()
|
||||||
|
{
|
||||||
|
return k_DefaultTickRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual int GetFrameRate()
|
||||||
|
{
|
||||||
|
return Application.targetFrameRate == 0 ? 60 : Application.targetFrameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int m_FramesPerTick = 0;
|
||||||
|
private float m_TickFrequency = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculates the <see cref="m_TickFrequency"/> and <see cref="m_FramesPerTick"/> that is
|
||||||
|
/// used in <see cref="TimeTravelAdvanceTick"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected void ConfigureFramesPerTick()
|
||||||
|
{
|
||||||
|
m_TickFrequency = 1.0f / GetTickRate();
|
||||||
|
m_FramesPerTick = Math.Max((int)(m_TickFrequency / GetFrameRate()), 1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper function to time travel exactly one tick's worth of time at the current frame and tick rates.
|
/// Helper function to time travel exactly one tick's worth of time at the current frame and tick rates.
|
||||||
|
/// This is NetcodeIntegrationTest instance relative and will automatically adjust based on <see cref="GetFrameRate"/>
|
||||||
|
/// and <see cref="GetTickRate"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected void TimeTravelAdvanceTick()
|
||||||
|
{
|
||||||
|
TimeTravel(m_TickFrequency, m_FramesPerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper function to time travel exactly one tick's worth of time at the current frame and tick rates.
|
||||||
|
/// ** Is based on the global k_DefaultTickRate and is not local to each NetcodeIntegrationTest instance **
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void TimeTravelToNextTick()
|
public static void TimeTravelToNextTick()
|
||||||
{
|
{
|
||||||
@@ -1556,7 +1609,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
frameRate = 60;
|
frameRate = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
var frames = Math.Max((int)(timePassed / frameRate), 1);
|
var frames = Math.Max((int)(timePassed / frameRate), 1);
|
||||||
TimeTravel(timePassed, frames);
|
TimeTravel(timePassed, frames);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
if (!networkManager.IsServer || networkManager.IsServer && serverSideSceneManager)
|
if (!networkManager.IsServer || networkManager.IsServer && serverSideSceneManager)
|
||||||
{
|
{
|
||||||
RegisterSceneManagerHandler(networkManager);
|
// Pass along the serverSideSceneManager property (otherwise the server won't register properly)
|
||||||
|
RegisterSceneManagerHandler(networkManager, serverSideSceneManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +406,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
|
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
|
||||||
// loaded and register the server to client scene handle since host-server shares the test runner scene
|
// loaded and register the server to client scene handle since host-server shares the test runner scene
|
||||||
// with the clients.
|
// with the clients.
|
||||||
networkManager.SceneManager.GetAndAddNewlyLoadedSceneByName(scene.name);
|
if (!networkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle))
|
||||||
|
{
|
||||||
|
networkManager.SceneManager.ScenesLoaded.Add(scene.handle, scene);
|
||||||
|
}
|
||||||
networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle);
|
networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,8 +447,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
server.ConnectionManager.MessageManager.Hook(hooks);
|
server.ConnectionManager.MessageManager.Hook(hooks);
|
||||||
s_Hooks[server] = hooks;
|
s_Hooks[server] = hooks;
|
||||||
|
|
||||||
// if set, then invoke this for the server
|
// Register the server side handler (always pass true for server)
|
||||||
RegisterHandlers(server);
|
RegisterHandlers(server, true);
|
||||||
|
|
||||||
callback?.Invoke();
|
callback?.Invoke();
|
||||||
|
|
||||||
@@ -809,7 +813,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
if (minFrames > 0)
|
if (minFrames > 0)
|
||||||
{
|
{
|
||||||
yield return new WaitUntil(() => Time.frameCount >= minFrames);
|
var waitForFrameCount = Time.frameCount + minFrames;
|
||||||
|
yield return new WaitUntil(() => Time.frameCount >= waitForFrameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (Time.realtimeSinceStartup - startTime < timeout && !predicate())
|
while (Time.realtimeSinceStartup - startTime < timeout && !predicate())
|
||||||
@@ -919,6 +924,24 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
var res = check.Result;
|
var res = check.Result;
|
||||||
result.Result = res;
|
result.Result = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static uint GetGlobalObjectIdHash(NetworkObject networkObject)
|
||||||
|
{
|
||||||
|
return networkObject.GlobalObjectIdHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
public static void SetRefreshAllPrefabsCallback(Action scenesProcessed)
|
||||||
|
{
|
||||||
|
NetworkObjectRefreshTool.AllScenesProcessed = scenesProcessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RefreshAllPrefabInstances(NetworkObject networkObject, Action scenesProcessed)
|
||||||
|
{
|
||||||
|
NetworkObjectRefreshTool.AllScenesProcessed = scenesProcessed;
|
||||||
|
networkObject.RefreshAllPrefabInstances();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty MonoBehaviour that is a holder of coroutine
|
// Empty MonoBehaviour that is a holder of coroutine
|
||||||
|
|||||||
@@ -181,6 +181,64 @@ namespace Unity.Netcode.EditorTests
|
|||||||
Assert.AreEqual(2, m_MessageSender.MessageQueue.Count);
|
Assert.AreEqual(2, m_MessageSender.MessageQueue.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void WhenExceedingPerClientBatchSizeLessThanDefault_NewBatchesAreCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize)
|
||||||
|
{
|
||||||
|
var message = GetMessage();
|
||||||
|
m_MessageManager.NonFragmentedMessageMaxSize = maxMessageSize * 5;
|
||||||
|
var clients = new ulong[] { 0, 1, 2 };
|
||||||
|
m_MessageManager.ClientConnected(1);
|
||||||
|
m_MessageManager.ClientConnected(2);
|
||||||
|
m_MessageManager.SetVersion(1, XXHash.Hash32(typeof(TestMessage).FullName), 0);
|
||||||
|
m_MessageManager.SetVersion(2, XXHash.Hash32(typeof(TestMessage).FullName), 0);
|
||||||
|
|
||||||
|
for (var i = 0; i < clients.Length; ++i)
|
||||||
|
{
|
||||||
|
m_MessageManager.PeerMTUSizes[clients[i]] = maxMessageSize * (i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
|
||||||
|
for (var i = 0; i < clients.Length; ++i)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < ((m_MessageManager.PeerMTUSizes[clients[i]] - UnsafeUtility.SizeOf<NetworkBatchHeader>()) / size) + 1; ++j)
|
||||||
|
{
|
||||||
|
m_MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, clients[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_MessageManager.ProcessSendQueues();
|
||||||
|
Assert.AreEqual(2 * clients.Length, m_MessageSender.MessageQueue.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void WhenExceedingPerClientBatchSizeGreaterThanDefault_OnlyOneNewBatcheIsCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize)
|
||||||
|
{
|
||||||
|
var message = GetMessage();
|
||||||
|
m_MessageManager.NonFragmentedMessageMaxSize = 128;
|
||||||
|
var clients = new ulong[] { 0, 1, 2 };
|
||||||
|
m_MessageManager.ClientConnected(1);
|
||||||
|
m_MessageManager.ClientConnected(2);
|
||||||
|
m_MessageManager.SetVersion(1, XXHash.Hash32(typeof(TestMessage).FullName), 0);
|
||||||
|
m_MessageManager.SetVersion(2, XXHash.Hash32(typeof(TestMessage).FullName), 0);
|
||||||
|
|
||||||
|
for (var i = 0; i < clients.Length; ++i)
|
||||||
|
{
|
||||||
|
m_MessageManager.PeerMTUSizes[clients[i]] = maxMessageSize * (i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = UnsafeUtility.SizeOf<TestMessage>() + 2; // MessageHeader packed with this message will be 2 bytes
|
||||||
|
for (var i = 0; i < clients.Length; ++i)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < ((m_MessageManager.PeerMTUSizes[clients[i]] - UnsafeUtility.SizeOf<NetworkBatchHeader>()) / size) + 1; ++j)
|
||||||
|
{
|
||||||
|
m_MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, clients[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_MessageManager.ProcessSendQueues();
|
||||||
|
Assert.AreEqual(2 * clients.Length, m_MessageSender.MessageQueue.Count);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize)
|
public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -173,37 +173,50 @@ namespace Unity.Netcode.EditorTests
|
|||||||
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
try
|
||||||
|
{
|
||||||
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
object1.GlobalObjectIdHash = 1;
|
object1.GlobalObjectIdHash = 1;
|
||||||
object2.GlobalObjectIdHash = 2;
|
object2.GlobalObjectIdHash = 2;
|
||||||
object3.GlobalObjectIdHash = 3;
|
object3.GlobalObjectIdHash = 3;
|
||||||
|
|
||||||
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||||
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
networkManager.AddNetworkPrefab(object2.gameObject);
|
networkManager.AddNetworkPrefab(object2.gameObject);
|
||||||
networkManager2.AddNetworkPrefab(object3.gameObject);
|
networkManager2.AddNetworkPrefab(object3.gameObject);
|
||||||
|
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
|
|
||||||
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
networkManager.ShutdownInternal();
|
||||||
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -224,36 +237,49 @@ namespace Unity.Netcode.EditorTests
|
|||||||
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
try
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
{
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
object1.GlobalObjectIdHash = 1;
|
object1.GlobalObjectIdHash = 1;
|
||||||
object2.GlobalObjectIdHash = 2;
|
object2.GlobalObjectIdHash = 2;
|
||||||
object3.GlobalObjectIdHash = 3;
|
object3.GlobalObjectIdHash = 3;
|
||||||
|
|
||||||
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||||
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
||||||
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
||||||
|
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
|
|
||||||
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
networkManager.ShutdownInternal();
|
||||||
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -274,36 +300,49 @@ namespace Unity.Netcode.EditorTests
|
|||||||
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
try
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
{
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
object1.GlobalObjectIdHash = 1;
|
object1.GlobalObjectIdHash = 1;
|
||||||
object2.GlobalObjectIdHash = 2;
|
object2.GlobalObjectIdHash = 2;
|
||||||
object3.GlobalObjectIdHash = 3;
|
object3.GlobalObjectIdHash = 3;
|
||||||
|
|
||||||
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||||
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object2.gameObject });
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object2.gameObject });
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object3.gameObject });
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object3.gameObject });
|
||||||
|
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
|
|
||||||
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(sharedList.Contains(object2.gameObject));
|
Assert.IsTrue(sharedList.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(sharedList.Contains(object3.gameObject));
|
Assert.IsTrue(sharedList.Contains(object3.gameObject));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
networkManager.ShutdownInternal();
|
||||||
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -324,36 +363,49 @@ namespace Unity.Netcode.EditorTests
|
|||||||
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
try
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
{
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
object1.GlobalObjectIdHash = 1;
|
object1.GlobalObjectIdHash = 1;
|
||||||
object2.GlobalObjectIdHash = 2;
|
object2.GlobalObjectIdHash = 2;
|
||||||
object3.GlobalObjectIdHash = 3;
|
object3.GlobalObjectIdHash = 3;
|
||||||
|
|
||||||
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||||
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
||||||
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
|
|
||||||
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
networkManager.ShutdownInternal();
|
||||||
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -374,42 +426,60 @@ namespace Unity.Netcode.EditorTests
|
|||||||
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
try
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
{
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
object1.GlobalObjectIdHash = 1;
|
object1.GlobalObjectIdHash = 1;
|
||||||
object2.GlobalObjectIdHash = 2;
|
object2.GlobalObjectIdHash = 2;
|
||||||
object3.GlobalObjectIdHash = 3;
|
object3.GlobalObjectIdHash = 3;
|
||||||
|
|
||||||
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||||
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
||||||
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
||||||
|
|
||||||
networkManager.ShutdownInternal();
|
networkManager.ShutdownInternal();
|
||||||
networkManager2.ShutdownInternal();
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
|
|
||||||
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
networkManager.ShutdownInternal();
|
||||||
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -430,39 +500,52 @@ namespace Unity.Netcode.EditorTests
|
|||||||
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
try
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
{
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
object1.GlobalObjectIdHash = 1;
|
object1.GlobalObjectIdHash = 1;
|
||||||
object2.GlobalObjectIdHash = 2;
|
object2.GlobalObjectIdHash = 2;
|
||||||
object3.GlobalObjectIdHash = 3;
|
object3.GlobalObjectIdHash = 3;
|
||||||
|
|
||||||
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
var sharedList = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||||
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { sharedList };
|
||||||
|
|
||||||
networkManager.Initialize(true);
|
networkManager.Initialize(true);
|
||||||
networkManager2.Initialize(false);
|
networkManager2.Initialize(false);
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject });
|
||||||
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject });
|
||||||
|
|
||||||
networkManager.NetworkConfig.Prefabs.Initialize();
|
networkManager.NetworkConfig.Prefabs.Initialize();
|
||||||
networkManager2.NetworkConfig.Prefabs.Initialize();
|
networkManager2.NetworkConfig.Prefabs.Initialize();
|
||||||
|
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject));
|
||||||
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject));
|
||||||
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject));
|
||||||
|
|
||||||
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
Assert.IsTrue(sharedList.Contains(object1.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
Assert.IsFalse(sharedList.Contains(object2.gameObject));
|
||||||
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
Assert.IsFalse(sharedList.Contains(object3.gameObject));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
networkManager.ShutdownInternal();
|
||||||
|
networkManager2.ShutdownInternal();
|
||||||
|
// Shutdown doesn't get called correctly because we called Initialize()
|
||||||
|
// instead of calling StartHost/StartClient/StartServer. See MTT-860 for
|
||||||
|
// why.
|
||||||
|
networkManager.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
networkManager2.NetworkConfig?.NetworkTransport.Shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.Editor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
@@ -64,9 +65,95 @@ namespace Unity.Netcode.EditorTests
|
|||||||
Object.DestroyImmediate(gameObject);
|
Object.DestroyImmediate(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that a NetworkObject component that is positioned after a NetworkBehaviour component will
|
||||||
|
/// be migrated to a component index value that is before the lowest NetworkBehaviour component index value.
|
||||||
|
/// (The lowest NetworkBehaviour component's index value will also change when this happens)
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void NetworkObjectComponentOrder()
|
||||||
|
{
|
||||||
|
var gameObject = new GameObject(nameof(GetBehaviourIndexOne));
|
||||||
|
// Add the Networkbehaviour first
|
||||||
|
var networkBehaviour = gameObject.AddComponent<EmptyNetworkBehaviour>();
|
||||||
|
// Add an empty MonoBehaviour inbetween the NetworkBehaviour and NetworkObject
|
||||||
|
gameObject.AddComponent<EmptyMonoBehaviour>();
|
||||||
|
// Add the NetworkObject
|
||||||
|
var networkObject = gameObject.AddComponent<NetworkObject>();
|
||||||
|
var componentIndices = GetIndices(gameObject);
|
||||||
|
|
||||||
|
// Verify the NetworkObject procedes the NetworkBehaviour
|
||||||
|
Assert.True(componentIndices.NetworkObjectIndex > componentIndices.NetworkBehaviourIndex, $"[Initial Setup] NetworkObject index ({componentIndices.NetworkObjectIndex}) is not greater than the NetworkBehaviour index ({componentIndices.NetworkBehaviourIndex})!");
|
||||||
|
|
||||||
|
// Force-Invoke the CheckForNetworkObject method in order to verify the NetworkObject is moved
|
||||||
|
NetworkBehaviourEditor.CheckForNetworkObject(gameObject);
|
||||||
|
var adjustedIndices = GetIndices(gameObject);
|
||||||
|
|
||||||
|
Assert.True(ValidateComponentIndices(componentIndices, GetIndices(gameObject)), "NetworkObject did not get migrated below the NetworkBehaviour!");
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
Object.DestroyImmediate(gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateComponentIndices(ComponentIndices previous, ComponentIndices current)
|
||||||
|
{
|
||||||
|
if (previous.NetworkObjectIndex != current.NetworkObjectIndex && previous.NetworkBehaviourIndex != current.NetworkBehaviourIndex)
|
||||||
|
{
|
||||||
|
if (current.NetworkObjectIndex < previous.NetworkObjectIndex && current.NetworkObjectIndex < current.NetworkBehaviourIndex)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ComponentIndices GetIndices(GameObject gameObject)
|
||||||
|
{
|
||||||
|
// Get the index/order values for the added NetworkBehaviour and NetworkObject
|
||||||
|
var components = gameObject.GetComponents<MonoBehaviour>();
|
||||||
|
var componentIndices = new ComponentIndices()
|
||||||
|
{
|
||||||
|
NetworkObjectIndex = -1,
|
||||||
|
NetworkBehaviourIndex = -1
|
||||||
|
};
|
||||||
|
for (int i = 0; i < components.Length; i++)
|
||||||
|
{
|
||||||
|
if (componentIndices.NetworkObjectIndex != -1 && componentIndices.NetworkBehaviourIndex != -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var component = components[i];
|
||||||
|
var networkObjectComponent = component as NetworkObject;
|
||||||
|
if (networkObjectComponent != null)
|
||||||
|
{
|
||||||
|
componentIndices.NetworkObjectIndex = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var networkBehaviourComponent = component as EmptyNetworkBehaviour;
|
||||||
|
if (networkBehaviourComponent != null)
|
||||||
|
{
|
||||||
|
componentIndices.NetworkBehaviourIndex = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ComponentIndices
|
||||||
|
{
|
||||||
|
public int NetworkObjectIndex;
|
||||||
|
public int NetworkBehaviourIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public class EmptyNetworkBehaviour : NetworkBehaviour
|
public class EmptyNetworkBehaviour : NetworkBehaviour
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EmptyMonoBehaviour : MonoBehaviour
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,6 +237,35 @@ namespace Unity.Netcode.EditorTests
|
|||||||
AssertIsTestMessage(data);
|
AssertIsTestMessage(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BatchedSendQueue_FillWriterWithMessages_StopOnSoftMaxBytes()
|
||||||
|
{
|
||||||
|
var smallMessage = new ArraySegment<byte>(new byte[10]);
|
||||||
|
var largeMessage = new ArraySegment<byte>(new byte[3000]);
|
||||||
|
|
||||||
|
var smallMessageSize = smallMessage.Count + BatchedSendQueue.PerMessageOverhead;
|
||||||
|
var largeMessageSize = largeMessage.Count + BatchedSendQueue.PerMessageOverhead;
|
||||||
|
|
||||||
|
using var q = new BatchedSendQueue(k_TestQueueCapacity);
|
||||||
|
using var data = new NativeArray<byte>(largeMessageSize, Allocator.Temp);
|
||||||
|
|
||||||
|
q.PushMessage(smallMessage);
|
||||||
|
q.PushMessage(largeMessage);
|
||||||
|
q.PushMessage(smallMessage);
|
||||||
|
|
||||||
|
var writer = new DataStreamWriter(data);
|
||||||
|
Assert.AreEqual(smallMessageSize, q.FillWriterWithMessages(ref writer, 1000));
|
||||||
|
q.Consume(smallMessageSize);
|
||||||
|
|
||||||
|
writer = new DataStreamWriter(data);
|
||||||
|
Assert.AreEqual(largeMessageSize, q.FillWriterWithMessages(ref writer, 1000));
|
||||||
|
q.Consume(largeMessageSize);
|
||||||
|
|
||||||
|
writer = new DataStreamWriter(data);
|
||||||
|
Assert.AreEqual(smallMessageSize, q.FillWriterWithMessages(ref writer, 1000));
|
||||||
|
q.Consume(smallMessageSize);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void BatchedSendQueue_FillWriterWithBytes_NoopIfNoData()
|
public void BatchedSendQueue_FillWriterWithBytes_NoopIfNoData()
|
||||||
{
|
{
|
||||||
@@ -293,6 +322,23 @@ namespace Unity.Netcode.EditorTests
|
|||||||
AssertIsTestMessage(data);
|
AssertIsTestMessage(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BatchedSendQueue_FillWriterWithBytes_MaxBytesGreaterThanCapacity()
|
||||||
|
{
|
||||||
|
var dataLength = k_TestMessageSize + BatchedSendQueue.PerMessageOverhead;
|
||||||
|
|
||||||
|
using var q = new BatchedSendQueue(k_TestQueueCapacity);
|
||||||
|
using var data = new NativeArray<byte>(dataLength, Allocator.Temp);
|
||||||
|
|
||||||
|
q.PushMessage(m_TestMessage);
|
||||||
|
q.PushMessage(m_TestMessage);
|
||||||
|
|
||||||
|
var writer = new DataStreamWriter(data);
|
||||||
|
Assert.AreEqual(dataLength, q.FillWriterWithBytes(ref writer, dataLength * 2));
|
||||||
|
AssertIsTestMessage(data);
|
||||||
|
Assert.False(writer.HasFailedWrites);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void BatchedSendQueue_Consume_LessThanLength()
|
public void BatchedSendQueue_Consume_LessThanLength()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
public override void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key)
|
public override void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key)
|
||||||
{
|
{
|
||||||
|
if (trigger == IDeferredNetworkMessageManager.TriggerType.OnNextFrame)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
ProcessTriggersCalled = true;
|
ProcessTriggersCalled = true;
|
||||||
base.ProcessTriggers(trigger, key);
|
base.ProcessTriggers(trigger, key);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user