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

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

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

### Added

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

### Changed

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

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

### Added

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

### Changed

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

### Removed

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

### Fixed

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

### Known Issues

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

## [0.2.0] - 2021-06-03

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

## [0.1.1] - 2021-06-01

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

### Changed

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

## [0.1.0] - 2021-03-23

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

### Added

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

### Changed

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

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

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

### Fixed

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

### Removed

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

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

### Known Issues

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

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

This was an internally-only-used version of the Unity MLAPI Package
This commit is contained in:
Unity Technologies
2020-12-20 00:00:00 +00:00
commit 22d877d1b2
489 changed files with 43246 additions and 0 deletions

12
Runtime/AssemblyInfo.cs Normal file
View File

@@ -0,0 +1,12 @@
using System.Runtime.CompilerServices;
#if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
#endif
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9fd74adf4a0f6e479be3978543dc6a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Collections.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f2ef964afcae91248b2298b479ed1b53
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,77 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Queue with a fixed size
/// </summary>
/// <typeparam name="T">The type of the queue</typeparam>
public sealed class FixedQueue<T>
{
private readonly T[] m_Queue;
private int m_QueueCount = 0;
private int m_QueueStart;
/// <summary>
/// The amount of enqueued objects
/// </summary>
public int Count => m_QueueCount;
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
/// <summary>
/// Creates a new FixedQueue with a given size
/// </summary>
/// <param name="maxSize">The size of the queue</param>
public FixedQueue(int maxSize)
{
m_Queue = new T[maxSize];
m_QueueStart = 0;
}
/// <summary>
/// Enqueues an object
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool Enqueue(T t)
{
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
if (++m_QueueCount > m_Queue.Length)
{
--m_QueueCount;
return true;
}
return false;
}
/// <summary>
/// Dequeues an object
/// </summary>
/// <returns></returns>
public T Dequeue()
{
if (--m_QueueCount == -1)
{
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
}
T res = m_Queue[m_QueueStart];
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
return res;
}
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8514b4eca0c7044d9b92faf9407ec93
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d3cc7700dfd03ee4397858710461d179
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
namespace Unity.Netcode
{
/// <summary>
/// Represents the length of a var int encoded hash
/// Note that the HashSize does not say anything about the actual final output due to the var int encoding
/// It just says how many bytes the maximum will be
/// </summary>
public enum HashSize : byte
{
/// <summary>
/// Four byte hash
/// </summary>
VarIntFourBytes,
/// <summary>
/// Eight byte hash
/// </summary>
VarIntEightBytes
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ae94548754e0a0409da85c0e3235bb4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Unity.Collections;
namespace Unity.Netcode
{
/// <summary>
/// The configuration object used to start server, client and hosts
/// </summary>
[Serializable]
public class NetworkConfig
{
/// <summary>
/// The protocol version. Different versions doesn't talk to each other.
/// </summary>
[Tooltip("Use this to make two builds incompatible with each other")]
public ushort ProtocolVersion = 0;
/// <summary>
/// The transport hosts the sever uses
/// </summary>
[Tooltip("The NetworkTransport to use")]
public NetworkTransport NetworkTransport = null;
/// <summary>
/// The default player prefab
/// </summary>
[Tooltip("When set, NetworkManager will automatically create and spawn the assigned player prefab. This can be overridden by adding it to the NetworkPrefabs list and selecting override.")]
public GameObject PlayerPrefab;
/// <summary>
/// A list of prefabs that can be dynamically spawned.
/// </summary>
[SerializeField]
[Tooltip("The prefabs that can be spawned across the network")]
internal List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
/// <summary>
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
/// Generated at runtime and OnValidate
/// </summary>
internal Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
internal Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
/// <summary>
/// The tickrate of network ticks. This value controls how often netcode runs user code and sends out data.
/// </summary>
[Tooltip("The tickrate. This value controls how often netcode runs user code and sends out data. The value is in 'ticks per seconds' which means a value of 50 will result in 50 ticks being executed per second or a fixed delta time of 0.02.")]
public uint TickRate = 30;
/// <summary>
/// The amount of seconds to wait for handshake to complete before timing out a client
/// </summary>
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
public int ClientConnectionBufferTimeout = 10;
/// <summary>
/// Whether or not to use connection approval
/// </summary>
[Tooltip("Whether or not to force clients to be approved before they connect")]
public bool ConnectionApproval = false;
/// <summary>
/// The data to send during connection which can be used to decide on if a client should get accepted
/// </summary>
[Tooltip("The connection data sent along with connection requests")]
public byte[] ConnectionData = new byte[0];
/// <summary>
/// If your logic uses the NetworkTime, this should probably be turned off. If however it's needed to maximize accuracy, this is recommended to be turned on
/// </summary>
[Tooltip("Enable this to re-sync the NetworkTime after the initial sync")]
public bool EnableTimeResync = false;
/// <summary>
/// If time re-sync is turned on, this specifies the interval between syncs in seconds.
/// </summary>
[Tooltip("The amount of seconds between re-syncs of NetworkTime, if enabled")]
public int TimeResyncInterval = 30;
/// <summary>
/// Whether or not to ensure that NetworkVariables can be read even if a client accidentally writes where its not allowed to. This costs some CPU and bandwidth.
/// </summary>
[Tooltip("Ensures that NetworkVariables can be read even if a client accidental writes where its not allowed to. This will cost some CPU time and bandwidth")]
public bool EnsureNetworkVariableLengthSafety = false;
/// <summary>
/// Enables scene management. This will allow network scene switches and automatic scene difference corrections upon connect.
/// SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.
/// </summary>
[Tooltip("Enables scene management. This will allow network scene switches and automatic scene difference corrections upon connect.\n" +
"SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.")]
public bool EnableSceneManagement = true;
/// <summary>
/// Whether or not the netcode should check for differences in the prefabs at connection.
/// If you dynamically add prefabs at runtime, turn this OFF
/// </summary>
[Tooltip("Whether or not the netcode should check for differences in the prefab lists at connection")]
public bool ForceSamePrefabs = true;
/// <summary>
/// If true, NetworkIds will be reused after the NetworkIdRecycleDelay.
/// </summary>
[Tooltip("If true, NetworkIds will be reused after the NetworkIdRecycleDelay")]
public bool RecycleNetworkIds = true;
/// <summary>
/// The amount of seconds a NetworkId has to be unused in order for it to be reused.
/// </summary>
[Tooltip("The amount of seconds a NetworkId has to unused in order for it to be reused")]
public float NetworkIdRecycleDelay = 120f;
/// <summary>
/// Decides how many bytes to use for Rpc messaging. Leave this to 2 bytes unless you are facing hash collisions
/// </summary>
[Tooltip("The maximum amount of bytes to use for RPC messages.")]
public HashSize RpcHashSize = HashSize.VarIntFourBytes;
/// <summary>
/// The amount of seconds to wait for all clients to load or unload a requested scene
/// </summary>
[Tooltip("The amount of seconds to wait for all clients to load or unload a requested scene (only when EnableSceneManagement is enabled)")]
public int LoadSceneTimeOut = 120;
/// <summary>
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
/// </summary>
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
public float MessageBufferTimeout = 20f;
/// <summary>
/// Whether or not to enable network logs.
/// </summary>
public bool EnableNetworkLogs = true;
/// <summary>
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
/// </summary>
public bool UseSnapshotDelta { get; } = false;
/// <summary>
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
/// </summary>
public bool UseSnapshotSpawn { get; } = false;
/// <summary>
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
/// </summary>
public int SnapshotMaxSpawnUsage { get; } = 1200;
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
/// <summary>
/// Returns a base64 encoded version of the configuration
/// </summary>
/// <returns></returns>
public string ToBase64()
{
NetworkConfig config = this;
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(config.ProtocolVersion);
writer.WriteValueSafe(config.TickRate);
writer.WriteValueSafe(config.ClientConnectionBufferTimeout);
writer.WriteValueSafe(config.ConnectionApproval);
writer.WriteValueSafe(config.LoadSceneTimeOut);
writer.WriteValueSafe(config.EnableTimeResync);
writer.WriteValueSafe(config.EnsureNetworkVariableLengthSafety);
writer.WriteValueSafe(config.RpcHashSize);
writer.WriteValueSafe(ForceSamePrefabs);
writer.WriteValueSafe(EnableSceneManagement);
writer.WriteValueSafe(RecycleNetworkIds);
writer.WriteValueSafe(NetworkIdRecycleDelay);
writer.WriteValueSafe(EnableNetworkLogs);
// Allocates
return Convert.ToBase64String(writer.ToArray());
}
}
/// <summary>
/// Sets the NetworkConfig data with that from a base64 encoded version
/// </summary>
/// <param name="base64">The base64 encoded version</param>
public void FromBase64(string base64)
{
NetworkConfig config = this;
byte[] binary = Convert.FromBase64String(base64);
using var reader = new FastBufferReader(binary, Allocator.Temp);
using (reader)
{
reader.ReadValueSafe(out config.ProtocolVersion);
reader.ReadValueSafe(out config.TickRate);
reader.ReadValueSafe(out config.ClientConnectionBufferTimeout);
reader.ReadValueSafe(out config.ConnectionApproval);
reader.ReadValueSafe(out config.LoadSceneTimeOut);
reader.ReadValueSafe(out config.EnableTimeResync);
reader.ReadValueSafe(out config.EnsureNetworkVariableLengthSafety);
reader.ReadValueSafe(out config.RpcHashSize);
reader.ReadValueSafe(out config.ForceSamePrefabs);
reader.ReadValueSafe(out config.EnableSceneManagement);
reader.ReadValueSafe(out config.RecycleNetworkIds);
reader.ReadValueSafe(out config.NetworkIdRecycleDelay);
reader.ReadValueSafe(out config.EnableNetworkLogs);
}
}
private ulong? m_ConfigHash = null;
/// <summary>
/// Gets a SHA256 hash of parts of the NetworkConfig instance
/// </summary>
/// <param name="cache"></param>
/// <returns></returns>
public ulong GetConfig(bool cache = true)
{
if (m_ConfigHash != null && cache)
{
return m_ConfigHash.Value;
}
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(ProtocolVersion);
writer.WriteValueSafe(NetworkConstants.PROTOCOL_VERSION);
if (ForceSamePrefabs)
{
var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
foreach (var sortedEntry in sortedDictionary)
{
writer.WriteValueSafe(sortedEntry.Key);
}
}
writer.WriteValueSafe(ConnectionApproval);
writer.WriteValueSafe(ForceSamePrefabs);
writer.WriteValueSafe(EnableSceneManagement);
writer.WriteValueSafe(EnsureNetworkVariableLengthSafety);
writer.WriteValueSafe(RpcHashSize);
if (cache)
{
m_ConfigHash = XXHash.Hash64(writer.ToArray());
return m_ConfigHash.Value;
}
return XXHash.Hash64(writer.ToArray());
}
}
/// <summary>
/// Compares a SHA256 hash with the current NetworkConfig instances hash
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
public bool CompareConfig(ulong hash)
{
return hash == GetConfig();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b056e7faa4d1cb4aac7bc304c765c3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
namespace Unity.Netcode
{
/// <summary>
/// A static class containing network constants
/// </summary>
internal static class NetworkConstants
{
internal const string PROTOCOL_VERSION = "14.0.0";
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dae82900f88f6bb4a90c29d431f2b45a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using System;
using UnityEngine;
namespace Unity.Netcode
{
internal enum NetworkPrefabOverride
{
None,
Prefab,
Hash
}
/// <summary>
/// Class that represents a NetworkPrefab
/// </summary>
[Serializable]
internal class NetworkPrefab
{
/// <summary>
/// The override setttings for this NetworkPrefab
/// </summary>
public NetworkPrefabOverride Override;
/// <summary>
/// Asset reference of the network prefab
/// </summary>
public GameObject Prefab;
/// <summary>
/// Used when prefab is selected for the source prefab to override value (i.e. direct reference, the prefab is within the same project)
/// We keep a separate value as the user might want to have something different than the default Prefab for the SourcePrefabToOverride
/// </summary>
public GameObject SourcePrefabToOverride;
/// <summary>
/// Used when hash is selected for the source prefab to override value (i.e. a direct reference is not possible such as in a multi-project pattern)
/// </summary>
public uint SourceHashToOverride;
/// <summary>
/// The prefab to replace (override) the source prefab with
/// </summary>
public GameObject OverridingTargetPrefab;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10e0511afda4e7743b2cd7c9cf95e0ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Connection.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c7155f1167eb96846aab2132b37c2815
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Unity.Netcode
{
/// <summary>
/// A NetworkClient
/// </summary>
public class NetworkClient
{
/// <summary>
/// The ClientId of the NetworkClient
/// </summary>
public ulong ClientId;
/// <summary>
/// The PlayerObject of the Client
/// </summary>
public NetworkObject PlayerObject;
/// <summary>
/// The NetworkObject's owned by this Client
/// </summary>
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23ca4d14911834b41a761c62fb23773e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
namespace Unity.Netcode
{
/// <summary>
/// A class representing a client that is currently in the process of connecting
/// </summary>
public class PendingClient
{
/// <summary>
/// The ClientId of the client
/// </summary>
public ulong ClientId { get; internal set; }
/// <summary>
/// The state of the connection process for the client
/// </summary>
public State ConnectionState { get; internal set; }
/// <summary>
/// The states of a connection
/// </summary>
public enum State
{
/// <summary>
/// Waiting for client to send it's initial connection request
/// </summary>
PendingConnection,
/// <summary>
/// Waiting for client connection request to be approved
/// </summary>
PendingApproval
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ffbc5b303de84a4196e24f503752218
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Core.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eb92f4010a5924b408aca753b55bd5ae
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,388 @@
using UnityEngine;
namespace Unity.Netcode
{
internal struct IndexAllocatorEntry
{
internal int Pos; // Position where the memory of this slot is
internal int Length; // Length of the memory allocated to this slot
internal int Next; // Next and Prev define the order of the slots in the buffer
internal int Prev;
internal bool Free; // Whether this is a free slot
}
internal class IndexAllocator
{
private const int k_NotSet = -1;
private readonly int m_MaxSlot; // Maximum number of sections (free or not) in the buffer
private readonly int m_BufferSize; // Size of the buffer we allocated into
private int m_LastSlot = 0; // Last allocated slot
private IndexAllocatorEntry[] m_Slots; // Array of slots
private int[] m_IndexToSlot; // Mapping from the client's index to the slot index
internal IndexAllocator(int bufferSize, int maxSlot)
{
m_MaxSlot = maxSlot;
m_BufferSize = bufferSize;
m_Slots = new IndexAllocatorEntry[m_MaxSlot];
m_IndexToSlot = new int[m_MaxSlot];
Reset();
}
/// <summary>
/// Reset this IndexAllocator to an empty one, with the same sized buffer and slots
/// </summary>
internal void Reset()
{
// todo: could be made faster, for example by having a last index
// and not needing valid stuff past it
for (int i = 0; i < m_MaxSlot; i++)
{
m_Slots[i].Free = true;
m_Slots[i].Next = i + 1;
m_Slots[i].Prev = i - 1;
m_Slots[i].Pos = m_BufferSize;
m_Slots[i].Length = 0;
m_IndexToSlot[i] = k_NotSet;
}
m_Slots[0].Pos = 0;
m_Slots[0].Length = m_BufferSize;
m_Slots[0].Prev = k_NotSet;
m_Slots[m_MaxSlot - 1].Next = k_NotSet;
}
/// <summary>
/// Returns the amount of memory used
/// </summary>
/// <returns>
/// Returns the amount of memory used, starting at 0, ending after the last used slot
/// </returns>
internal int Range
{
get
{
// when the whole buffer is free, m_LastSlot points to an empty slot
if (m_Slots[m_LastSlot].Free)
{
return 0;
}
// otherwise return the end of the last slot used
return m_Slots[m_LastSlot].Pos + m_Slots[m_LastSlot].Length;
}
}
/// <summary>
/// Allocate a slot with "size" position, for index "index"
/// </summary>
/// <param name="index">The client index to identify this. Used in Deallocate to identify which slot</param>
/// <param name="size">The size required. </param>
/// <param name="pos">Returns the position to use in the buffer </param>
/// <returns>
/// true if successful, false is there isn't enough memory available or no slots are large enough
/// </returns>
internal bool Allocate(int index, int size, out int pos)
{
pos = 0;
// size must be positive, index must be within range
if (size < 0 || index < 0 || index >= m_MaxSlot)
{
return false;
}
// refuse allocation if the index is already in use
if (m_IndexToSlot[index] != k_NotSet)
{
return false;
}
// todo: this is the slowest part
// improvement 1: list of free blocks (minor)
// improvement 2: heap of free blocks
for (int i = 0; i < m_MaxSlot; i++)
{
if (m_Slots[i].Free && m_Slots[i].Length >= size)
{
m_IndexToSlot[index] = i;
int leftOver = m_Slots[i].Length - size;
int next = m_Slots[i].Next;
if (m_Slots[next].Free)
{
m_Slots[next].Pos -= leftOver;
m_Slots[next].Length += leftOver;
}
else
{
int add = MoveSlotAfter(i);
m_Slots[add].Pos = m_Slots[i].Pos + size;
m_Slots[add].Length = m_Slots[i].Length - size;
}
m_Slots[i].Free = false;
m_Slots[i].Length = size;
pos = m_Slots[i].Pos;
// if we allocate past the current range, we are the last slot
if (m_Slots[i].Pos + m_Slots[i].Length > Range)
{
m_LastSlot = i;
}
return true;
}
}
return false;
}
/// <summary>
/// Deallocate a slot
/// </summary>
/// <param name="index">The client index to identify this. Same index used in Allocate </param>
/// <returns>
/// true if successful, false is there isn't an allocated slot at this index
/// </returns>
internal bool Deallocate(int index)
{
// size must be positive, index must be within range
if (index < 0 || index >= m_MaxSlot)
{
return false;
}
int slot = m_IndexToSlot[index];
if (slot == k_NotSet)
{
return false;
}
if (m_Slots[slot].Free)
{
return false;
}
m_Slots[slot].Free = true;
int prev = m_Slots[slot].Prev;
int next = m_Slots[slot].Next;
// if previous slot was free, merge and grow
if (prev != k_NotSet && m_Slots[prev].Free)
{
m_Slots[prev].Length += m_Slots[slot].Length;
m_Slots[slot].Length = 0;
// if the slot we're merging was the last one, the last one is now the one we merged with
if (slot == m_LastSlot)
{
m_LastSlot = prev;
}
// todo: verify what this does on full or nearly full cases
MoveSlotToEnd(slot);
slot = prev;
}
next = m_Slots[slot].Next;
// merge with next slot if it is free
if (next != k_NotSet && m_Slots[next].Free)
{
m_Slots[slot].Length += m_Slots[next].Length;
m_Slots[next].Length = 0;
MoveSlotToEnd(next);
}
// if we just deallocate the last one, we need to move last back
if (slot == m_LastSlot)
{
m_LastSlot = m_Slots[m_LastSlot].Prev;
// if there's nothing allocated anymore, use 0
if (m_LastSlot == k_NotSet)
{
m_LastSlot = 0;
}
}
// mark the index as available
m_IndexToSlot[index] = k_NotSet;
return true;
}
// Take a slot at the end and link it to go just after "slot".
// Used when allocating part of a slot and we need an entry for the rest
// Returns the slot that was picked
private int MoveSlotAfter(int slot)
{
int ret = m_Slots[m_MaxSlot - 1].Prev;
int p0 = m_Slots[ret].Prev;
m_Slots[p0].Next = m_MaxSlot - 1;
m_Slots[m_MaxSlot - 1].Prev = p0;
int p1 = m_Slots[slot].Next;
m_Slots[slot].Next = ret;
m_Slots[p1].Prev = ret;
m_Slots[ret].Prev = slot;
m_Slots[ret].Next = p1;
return ret;
}
// Move the slot "slot" to the end of the list.
// Used when merging two slots, that gives us an extra entry at the end
private void MoveSlotToEnd(int slot)
{
// if we're already there
if (m_Slots[slot].Next == k_NotSet)
{
return;
}
int prev = m_Slots[slot].Prev;
int next = m_Slots[slot].Next;
m_Slots[prev].Next = next;
if (next != k_NotSet)
{
m_Slots[next].Prev = prev;
}
int p0 = m_Slots[m_MaxSlot - 1].Prev;
m_Slots[p0].Next = slot;
m_Slots[slot].Next = m_MaxSlot - 1;
m_Slots[m_MaxSlot - 1].Prev = slot;
m_Slots[slot].Prev = p0;
m_Slots[slot].Pos = m_BufferSize;
}
// runs a bunch of consistency check on the Allocator
internal bool Verify()
{
int pos = k_NotSet;
int count = 0;
int total = 0;
int endPos = 0;
do
{
int prev = pos;
if (pos != k_NotSet)
{
pos = m_Slots[pos].Next;
if (pos == k_NotSet)
{
break;
}
}
else
{
pos = 0;
}
if (m_Slots[pos].Prev != prev)
{
// the previous is not correct
return false;
}
if (m_Slots[pos].Length < 0)
{
// Length should be positive
return false;
}
if (prev != k_NotSet && m_Slots[prev].Free && m_Slots[pos].Free && m_Slots[pos].Length > 0)
{
// should not have two consecutive free slots
return false;
}
if (m_Slots[pos].Pos != total)
{
// slots should all line up nicely
return false;
}
if (!m_Slots[pos].Free)
{
endPos = m_Slots[pos].Pos + m_Slots[pos].Length;
}
total += m_Slots[pos].Length;
count++;
} while (pos != k_NotSet);
if (count != m_MaxSlot)
{
// some slots were lost
return false;
}
if (total != m_BufferSize)
{
// total buffer should be accounted for
return false;
}
if (endPos != Range)
{
// end position should match reported end position
return false;
}
return true;
}
// Debug display the allocator structure
internal void DebugDisplay()
{
string logMessage = "IndexAllocator structure\n";
bool[] seen = new bool[m_MaxSlot];
int pos = 0;
int count = 0;
bool prevEmpty = false;
do
{
seen[pos] = true;
count++;
if (m_Slots[pos].Length == 0 && prevEmpty)
{
// don't display repetitive empty slots
}
else
{
logMessage += string.Format("{0}:{1}, {2} ({3}) \n", m_Slots[pos].Pos, m_Slots[pos].Length,
m_Slots[pos].Free ? "Free" : "Used", pos);
if (m_Slots[pos].Length == 0)
{
prevEmpty = true;
}
else
{
prevEmpty = false;
}
}
pos = m_Slots[pos].Next;
} while (pos != k_NotSet && !seen[pos]);
logMessage += string.Format("{0} Total entries\n", count);
Debug.Log(logMessage);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd9e1475e8c8e4a6d935fe2409e3bd26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,676 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Reflection;
using Unity.Collections;
namespace Unity.Netcode
{
/// <summary>
/// The base class to override to write network code. Inherits MonoBehaviour
/// </summary>
public abstract class NetworkBehaviour : MonoBehaviour
{
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal enum __RpcExecStage
#pragma warning restore IDE1006 // restore naming rule violation check
{
None = 0,
Server = 1,
Client = 2
}
#pragma warning disable IDE1006 // disable naming rule violation check
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
#pragma warning restore IDE1006 // restore naming rule violation check
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
[NonSerialized]
// RuntimeAccessModifiersILPP will make this `protected`
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
#pragma warning restore 414 // restore assigned but its value is never used
#pragma warning restore IDE1006 // restore naming rule violation check
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerRpcParams rpcParams, RpcDelivery delivery)
#pragma warning restore 414 // restore assigned but its value is never used
#pragma warning restore IDE1006 // restore naming rule violation check
{
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
switch (delivery)
{
case RpcDelivery.Reliable:
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
break;
case RpcDelivery.Unreliable:
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
networkDelivery = NetworkDelivery.Unreliable;
break;
}
var message = new RpcMessage
{
Header = new RpcMessage.HeaderData
{
Type = RpcMessage.RpcType.Server,
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
NetworkMethodId = rpcMethodId
},
RpcData = writer
};
var rpcMessageSize = 0;
// If we are a server/host then we just no op and send to ourself
if (IsHost || IsServer)
{
using var tempBuffer = new FastBufferReader(writer, Allocator.Temp);
var context = new NetworkContext
{
SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup,
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// Passing false to canDefer prevents it being accessed.
Header = new MessageHeader()
};
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
rpcMessageSize = tempBuffer.Length;
}
else
{
rpcMessageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ServerClientId);
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{
NetworkManager.NetworkMetrics.TrackRpcSent(
NetworkManager.ServerClientId,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcMessageSize);
}
#endif
}
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId, ClientRpcParams rpcParams, RpcDelivery delivery)
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
{
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
switch (delivery)
{
case RpcDelivery.Reliable:
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
break;
case RpcDelivery.Unreliable:
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
networkDelivery = NetworkDelivery.Unreliable;
break;
}
var message = new RpcMessage
{
Header = new RpcMessage.HeaderData
{
Type = RpcMessage.RpcType.Client,
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
NetworkMethodId = rpcMethodId
},
RpcData = writer
};
int messageSize;
// We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC
// to ourself. Sadly we have to figure that out from the list of clientIds :(
bool shouldSendToHost = false;
if (rpcParams.Send.TargetClientIds != null)
{
foreach (var clientId in rpcParams.Send.TargetClientIds)
{
if (clientId == NetworkManager.ServerClientId)
{
shouldSendToHost = true;
break;
}
}
messageSize = NetworkManager.SendMessage(message, networkDelivery, in rpcParams.Send.TargetClientIds);
}
else if (rpcParams.Send.TargetClientIdsNativeArray != null)
{
foreach (var clientId in rpcParams.Send.TargetClientIdsNativeArray)
{
if (clientId == NetworkManager.ServerClientId)
{
shouldSendToHost = true;
break;
}
}
messageSize = NetworkManager.SendMessage(message, networkDelivery, rpcParams.Send.TargetClientIdsNativeArray.Value);
}
else
{
shouldSendToHost = IsHost;
messageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ConnectedClientsIds);
}
// If we are a server/host then we just no op and send to ourself
if (shouldSendToHost)
{
using var tempBuffer = new FastBufferReader(writer, Allocator.Temp);
var context = new NetworkContext
{
SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup,
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// Passing false to canDefer prevents it being accessed.
Header = new MessageHeader()
};
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
messageSize = tempBuffer.Length;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
client.Key,
NetworkObject,
rpcMethodName,
__getTypeName(),
messageSize);
}
}
#endif
}
/// <summary>
/// Gets the NetworkManager that owns this NetworkBehaviour instance
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
/// </summary>
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
/// <summary>
/// Gets if the object is the the personal clients player object
/// </summary>
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
/// <summary>
/// Gets if the object is owned by the local player or if the object is the local player object
/// </summary>
public bool IsOwner => NetworkObject.IsOwner;
/// <summary>
/// Gets if we are executing as server
/// </summary>
protected bool IsServer => IsRunning && NetworkManager.IsServer;
/// <summary>
/// Gets if we are executing as client
/// </summary>
protected bool IsClient => IsRunning && NetworkManager.IsClient;
/// <summary>
/// Gets if we are executing as Host, I.E Server and Client
/// </summary>
protected bool IsHost => IsRunning && NetworkManager.IsHost;
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
/// <summary>
/// Gets Whether or not the object has a owner
/// </summary>
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
/// <summary>
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
/// </summary>
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false;
internal bool IsBehaviourEditable()
{
// Only server can MODIFY. So allow modification if network is either not running or we are server
return !m_NetworkObject ||
(m_NetworkObject.NetworkManager == null ||
!m_NetworkObject.NetworkManager.IsListening ||
m_NetworkObject.NetworkManager.IsServer);
}
/// <summary>
/// Gets the NetworkObject that owns this NetworkBehaviour instance
/// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
/// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
/// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
/// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
/// how NetworkObject works but it was close to the release and too risky to change
///
/// </summary>
public NetworkObject NetworkObject
{
get
{
if (m_NetworkObject == null)
{
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
}
return m_NetworkObject;
}
}
/// <summary>
/// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner.
/// </summary>
public bool HasNetworkObject => NetworkObject != null;
private NetworkObject m_NetworkObject = null;
/// <summary>
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
/// </summary>
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
/// <summary>
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
/// </summary>
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
/// <summary>
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
/// </summary>
internal ushort NetworkBehaviourIdCache = 0;
/// <summary>
/// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject
/// </summary>
/// <param name="behaviourId">The behaviourId to return</param>
/// <returns>Returns NetworkBehaviour with given behaviourId</returns>
protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
{
return NetworkObject.GetNetworkBehaviourAtOrderIndex(behaviourId);
}
/// <summary>
/// Gets the ClientId that owns the NetworkObject
/// </summary>
public ulong OwnerClientId => NetworkObject.OwnerClientId;
/// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
/// </summary>
public virtual void OnNetworkSpawn() { }
/// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients.
/// </summary>
public virtual void OnNetworkDespawn() { }
internal void InternalOnNetworkSpawn()
{
InitializeVariables();
}
internal void InternalOnNetworkDespawn()
{
}
/// <summary>
/// Gets called when the local client gains ownership of this object
/// </summary>
public virtual void OnGainedOwnership() { }
/// <summary>
/// Gets called when we loose ownership of this object
/// </summary>
public virtual void OnLostOwnership() { }
/// <summary>
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
/// </summary>
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
private bool m_VarInit = false;
private readonly List<HashSet<int>> m_DeliveryMappedNetworkVariableIndices = new List<HashSet<int>>();
private readonly List<NetworkDelivery> m_DeliveryTypesForNetworkVariableGroups = new List<NetworkDelivery>();
internal readonly List<NetworkVariableBase> NetworkVariableFields = new List<NetworkVariableBase>();
private static Dictionary<Type, FieldInfo[]> s_FieldTypes = new Dictionary<Type, FieldInfo[]>();
private static FieldInfo[] GetFieldInfoForType(Type type)
{
if (!s_FieldTypes.ContainsKey(type))
{
s_FieldTypes.Add(type, GetFieldInfoForTypeRecursive(type));
}
return s_FieldTypes[type];
}
private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List<FieldInfo> list = null)
{
if (list == null)
{
list = new List<FieldInfo>();
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
}
else
{
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
}
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
{
return GetFieldInfoForTypeRecursive(type.BaseType, list);
}
return list.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
}
internal void InitializeVariables()
{
if (m_VarInit)
{
return;
}
m_VarInit = true;
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
for (int i = 0; i < sortedFields.Length; i++)
{
Type fieldType = sortedFields[i].FieldType;
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
{
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
if (instance == null)
{
instance = (NetworkVariableBase)Activator.CreateInstance(fieldType, true);
sortedFields[i].SetValue(this, instance);
}
instance.Initialize(this);
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
var sanitizedVariableName = sortedFields[i].Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty);
instanceNameProperty?.SetValue(instance, sanitizedVariableName);
NetworkVariableFields.Add(instance);
}
}
{
// Create index map for delivery types
var firstLevelIndex = new Dictionary<NetworkDelivery, int>();
int secondLevelCounter = 0;
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
var networkDelivery = NetworkVariableBase.Delivery;
if (!firstLevelIndex.ContainsKey(networkDelivery))
{
firstLevelIndex.Add(networkDelivery, secondLevelCounter);
m_DeliveryTypesForNetworkVariableGroups.Add(networkDelivery);
secondLevelCounter++;
}
if (firstLevelIndex[networkDelivery] >= m_DeliveryMappedNetworkVariableIndices.Count)
{
m_DeliveryMappedNetworkVariableIndices.Add(new HashSet<int>());
}
m_DeliveryMappedNetworkVariableIndices[firstLevelIndex[networkDelivery]].Add(i);
}
}
}
internal void PreNetworkVariableWrite()
{
// reset our "which variables got written" data
NetworkVariableIndexesToReset.Clear();
NetworkVariableIndexesToResetSet.Clear();
}
internal void PostNetworkVariableWrite()
{
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
}
}
internal void VariableUpdate(ulong clientId)
{
if (!m_VarInit)
{
InitializeVariables();
}
PreNetworkVariableWrite();
NetworkVariableUpdate(clientId, NetworkBehaviourId);
}
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)
{
if (!CouldHaveDirtyNetworkVariables())
{
return;
}
if (NetworkManager.NetworkConfig.UseSnapshotDelta)
{
for (int k = 0; k < NetworkVariableFields.Count; k++)
{
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]);
}
}
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
{
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
{
var shouldSend = false;
for (int k = 0; k < NetworkVariableFields.Count; k++)
{
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer))
{
shouldSend = true;
}
}
if (shouldSend)
{
var message = new NetworkVariableDeltaMessage
{
NetworkObjectId = NetworkObjectId,
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
NetworkBehaviour = this,
ClientId = clientId,
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
};
// TODO: Serialization is where the IsDirty flag gets changed.
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
// we still have to actually serialize the message even though we're not sending it, otherwise
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
// so we don't have to do this serialization work if we're not going to use the result.
if (IsServer && clientId == NetworkManager.ServerClientId)
{
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
using (tmpWriter)
{
message.Serialize(tmpWriter);
}
}
else
{
NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
}
}
}
}
}
private bool CouldHaveDirtyNetworkVariables()
{
// TODO: There should be a better way by reading one dirty variable vs. 'n'
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
if (NetworkVariableFields[i].IsDirty())
{
return true;
}
}
return false;
}
internal void MarkVariablesDirty()
{
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
NetworkVariableFields[j].SetDirty(true);
}
}
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
{
if (NetworkVariableFields.Count == 0)
{
return;
}
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId);
if (canClientRead)
{
var writePos = writer.Position;
writer.WriteValueSafe((ushort)0);
var startPos = writer.Position;
NetworkVariableFields[j].WriteField(writer);
var size = writer.Position - startPos;
writer.Seek(writePos);
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
}
else
{
writer.WriteValueSafe((ushort)0);
}
}
}
internal void SetNetworkVariableData(FastBufferReader reader)
{
if (NetworkVariableFields.Count == 0)
{
return;
}
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
reader.ReadValueSafe(out ushort varSize);
if (varSize == 0)
{
continue;
}
var readStartPos = reader.Position;
NetworkVariableFields[j].ReadField(reader);
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (reader.Position > (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes.");
}
reader.Seek(readStartPos + varSize);
}
else if (reader.Position < (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes.");
}
reader.Seek(readStartPos + varSize);
}
}
}
}
/// <summary>
/// Gets the local instance of a object with a given NetworkId
/// </summary>
/// <param name="networkId"></param>
/// <returns></returns>
protected NetworkObject GetNetworkObject(ulong networkId)
{
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
}
public virtual void OnDestroy()
{
// this seems odd to do here, but in fact especially in tests we can find ourselves
// here without having called InitializedVariables, which causes problems if any
// of those variables use native containers (e.g. NetworkList) as they won't be
// registered here and therefore won't be cleaned up.
//
// we should study to understand the initialization patterns
if (!m_VarInit)
{
InitializeVariables();
}
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].Dispose();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8ea6ec00590bd44a983c228bcaee727
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
using System.Collections.Generic;
using Unity.Profiling;
namespace Unity.Netcode
{
public class NetworkBehaviourUpdater
{
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
#endif
internal void NetworkBehaviourUpdate(NetworkManager networkManager)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_NetworkBehaviourUpdate.Begin();
#endif
try
{
if (networkManager.IsServer)
{
m_Touched.Clear();
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
{
var client = networkManager.ConnectedClientsList[i];
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList;
m_Touched.UnionWith(spawnedObjs);
foreach (var sobj in spawnedObjs)
{
if (sobj.IsNetworkVisibleTo(client.ClientId))
{
// Sync just the variables for just the objects this client sees
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
}
}
}
}
// Now, reset all the no-longer-dirty variables
foreach (var sobj in m_Touched)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
}
else
{
// when client updates the server, it tells it about all its objects
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
{
if (sobj.IsOwner)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].VariableUpdate(networkManager.ServerClientId);
}
}
}
// Now, reset all the no-longer-dirty variables
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
}
}
finally
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_NetworkBehaviourUpdate.End();
#endif
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d084c01093b446878bcb76e5d7f3221e
timeCreated: 1622225163

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 593a2fe42fa9d37498c96f9a383b6521
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5a57f767e5e46a458fc5d3c628d0cbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,417 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
namespace Unity.Netcode
{
/// <summary>
/// Defines the required interface of a network update system being executed by the network update loop.
/// </summary>
public interface INetworkUpdateSystem
{
void NetworkUpdate(NetworkUpdateStage updateStage);
}
/// <summary>
/// Defines network update stages being executed by the network update loop.
/// </summary>
public enum NetworkUpdateStage : byte
{
Unset = 0, // Default
Initialization = 1,
EarlyUpdate = 2,
FixedUpdate = 3,
PreUpdate = 4,
Update = 5,
PreLateUpdate = 6,
PostLateUpdate = 7
}
/// <summary>
/// Represents the network update loop injected into low-level player loop in Unity.
/// </summary>
public static class NetworkUpdateLoop
{
private static Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>> s_UpdateSystem_Sets;
private static Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]> s_UpdateSystem_Arrays;
private const int k_UpdateSystem_InitialArrayCapacity = 1024;
static NetworkUpdateLoop()
{
s_UpdateSystem_Sets = new Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>>();
s_UpdateSystem_Arrays = new Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]>();
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
{
s_UpdateSystem_Sets.Add(updateStage, new HashSet<INetworkUpdateSystem>());
s_UpdateSystem_Arrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystem_InitialArrayCapacity]);
}
}
/// <summary>
/// Registers a network update system to be executed in all network update stages.
/// </summary>
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
{
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
{
RegisterNetworkUpdate(updateSystem, updateStage);
}
}
/// <summary>
/// Registers a network update system to be executed in a specific network update stage.
/// </summary>
public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
{
var sysSet = s_UpdateSystem_Sets[updateStage];
if (!sysSet.Contains(updateSystem))
{
sysSet.Add(updateSystem);
int setLen = sysSet.Count;
var sysArr = s_UpdateSystem_Arrays[updateStage];
int arrLen = sysArr.Length;
if (setLen > arrLen)
{
// double capacity
sysArr = s_UpdateSystem_Arrays[updateStage] = new INetworkUpdateSystem[arrLen *= 2];
}
sysSet.CopyTo(sysArr);
if (setLen < arrLen)
{
// null terminator
sysArr[setLen] = null;
}
}
}
/// <summary>
/// Unregisters a network update system from all network update stages.
/// </summary>
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
{
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
{
UnregisterNetworkUpdate(updateSystem, updateStage);
}
}
/// <summary>
/// Unregisters a network update system from a specific network update stage.
/// </summary>
public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
{
var sysSet = s_UpdateSystem_Sets[updateStage];
if (sysSet.Contains(updateSystem))
{
sysSet.Remove(updateSystem);
int setLen = sysSet.Count;
var sysArr = s_UpdateSystem_Arrays[updateStage];
int arrLen = sysArr.Length;
sysSet.CopyTo(sysArr);
if (setLen < arrLen)
{
// null terminator
sysArr[setLen] = null;
}
}
}
/// <summary>
/// The current network update stage being executed.
/// </summary>
public static NetworkUpdateStage UpdateStage;
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
{
UpdateStage = updateStage;
var sysArr = s_UpdateSystem_Arrays[updateStage];
int arrLen = sysArr.Length;
for (int curIdx = 0; curIdx < arrLen; curIdx++)
{
var curSys = sysArr[curIdx];
if (curSys == null)
{
// null terminator
break;
}
curSys.NetworkUpdate(updateStage);
}
}
internal struct NetworkInitialization
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkInitialization),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Initialization)
};
}
}
internal struct NetworkEarlyUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkEarlyUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.EarlyUpdate)
};
}
}
internal struct NetworkFixedUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkFixedUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.FixedUpdate)
};
}
}
internal struct NetworkPreUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkPreUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreUpdate)
};
}
}
internal struct NetworkUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Update)
};
}
}
internal struct NetworkPreLateUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkPreLateUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreLateUpdate)
};
}
}
internal struct NetworkPostLateUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkPostLateUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostLateUpdate)
};
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Initialize()
{
UnregisterLoopSystems();
RegisterLoopSystems();
}
private enum LoopSystemPosition
{
After,
Before
}
private static bool TryAddLoopSystem(ref PlayerLoopSystem parentLoopSystem, PlayerLoopSystem childLoopSystem, Type anchorSystemType, LoopSystemPosition loopSystemPosition)
{
int systemPosition = -1;
if (anchorSystemType != null)
{
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
{
var subsystem = parentLoopSystem.subSystemList[i];
if (subsystem.type == anchorSystemType)
{
systemPosition = loopSystemPosition == LoopSystemPosition.After ? i + 1 : i;
break;
}
}
}
else
{
systemPosition = loopSystemPosition == LoopSystemPosition.After ? parentLoopSystem.subSystemList.Length : 0;
}
if (systemPosition == -1)
{
return false;
}
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length + 1];
// begin = systemsBefore + systemsAfter
// + systemsBefore
if (systemPosition > 0)
{
Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
}
// + childSystem
newSubsystemList[systemPosition] = childLoopSystem;
// + systemsAfter
if (systemPosition < parentLoopSystem.subSystemList.Length)
{
Array.Copy(parentLoopSystem.subSystemList, systemPosition, newSubsystemList, systemPosition + 1, parentLoopSystem.subSystemList.Length - systemPosition);
}
// end = systemsBefore + childSystem + systemsAfter
parentLoopSystem.subSystemList = newSubsystemList;
return true;
}
private static bool TryRemoveLoopSystem(ref PlayerLoopSystem parentLoopSystem, Type childSystemType)
{
int systemPosition = -1;
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
{
var subsystem = parentLoopSystem.subSystemList[i];
if (subsystem.type == childSystemType)
{
systemPosition = i;
break;
}
}
if (systemPosition == -1)
{
return false;
}
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length - 1];
// begin = systemsBefore + childSystem + systemsAfter
// + systemsBefore
if (systemPosition > 0)
{
Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
}
// + systemsAfter
if (systemPosition < parentLoopSystem.subSystemList.Length - 1)
{
Array.Copy(parentLoopSystem.subSystemList, systemPosition + 1, newSubsystemList, systemPosition, parentLoopSystem.subSystemList.Length - systemPosition - 1);
}
// end = systemsBefore + systemsAfter
parentLoopSystem.subSystemList = newSubsystemList;
return true;
}
internal static void RegisterLoopSystems()
{
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
{
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
if (currentSystem.type == typeof(Initialization))
{
TryAddLoopSystem(ref currentSystem, NetworkInitialization.CreateLoopSystem(), null, LoopSystemPosition.After);
}
else if (currentSystem.type == typeof(EarlyUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkEarlyUpdate.CreateLoopSystem(), typeof(EarlyUpdate.ScriptRunDelayedStartupFrame), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(FixedUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkFixedUpdate.CreateLoopSystem(), typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(PreUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPreUpdate.CreateLoopSystem(), typeof(PreUpdate.PhysicsUpdate), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(Update))
{
TryAddLoopSystem(ref currentSystem, NetworkUpdate.CreateLoopSystem(), typeof(Update.ScriptRunBehaviourUpdate), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPostLateUpdate.CreateLoopSystem(), typeof(PostLateUpdate.PlayerSendFrameComplete), LoopSystemPosition.After);
}
}
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
}
internal static void UnregisterLoopSystems()
{
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
{
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
if (currentSystem.type == typeof(Initialization))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkInitialization));
}
else if (currentSystem.type == typeof(EarlyUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkEarlyUpdate));
}
else if (currentSystem.type == typeof(FixedUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkFixedUpdate));
}
else if (currentSystem.type == typeof(PreUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreUpdate));
}
else if (currentSystem.type == typeof(Update))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkUpdate));
}
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostLateUpdate));
}
}
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cd9c24b9acfd4e82a71c795f37235c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
using System;
namespace Unity.Netcode
{
internal class ConnectionRtt
{
private double[] m_RttSendTimes; // times at which packet were sent for RTT computations
private int[] m_SendSequence; // tick, or other key, at which packets were sent (to allow matching)
private double[] m_MeasuredLatencies; // measured latencies (ring buffer)
private int m_LatenciesBegin = 0; // ring buffer begin
private int m_LatenciesEnd = 0; // ring buffer end
/// <summary>
/// Round-trip-time data
/// </summary>
public struct Rtt
{
public double BestSec; // best RTT
public double AverageSec; // average RTT
public double WorstSec; // worst RTT
public double LastSec; // latest ack'ed RTT
public int SampleCount; // number of contributing samples
}
public ConnectionRtt()
{
m_RttSendTimes = new double[NetworkConfig.RttWindowSize];
m_SendSequence = new int[NetworkConfig.RttWindowSize];
m_MeasuredLatencies = new double[NetworkConfig.RttWindowSize];
}
/// <summary>
/// Returns the Round-trip-time computation for this client
/// </summary>
public Rtt GetRtt()
{
var ret = new Rtt();
var index = m_LatenciesBegin;
double total = 0.0;
ret.BestSec = m_MeasuredLatencies[m_LatenciesBegin];
ret.WorstSec = m_MeasuredLatencies[m_LatenciesBegin];
while (index != m_LatenciesEnd)
{
total += m_MeasuredLatencies[index];
ret.SampleCount++;
ret.BestSec = Math.Min(ret.BestSec, m_MeasuredLatencies[index]);
ret.WorstSec = Math.Max(ret.WorstSec, m_MeasuredLatencies[index]);
index = (index + 1) % NetworkConfig.RttAverageSamples;
}
if (ret.SampleCount != 0)
{
ret.AverageSec = total / ret.SampleCount;
// the latest RTT is one before m_LatenciesEnd
ret.LastSec = m_MeasuredLatencies[(m_LatenciesEnd + (NetworkConfig.RttWindowSize - 1)) % NetworkConfig.RttWindowSize];
}
else
{
ret.AverageSec = 0;
ret.BestSec = 0;
ret.WorstSec = 0;
ret.SampleCount = 0;
ret.LastSec = 0;
}
return ret;
}
internal void NotifySend(int sequence, double timeSec)
{
m_RttSendTimes[sequence % NetworkConfig.RttWindowSize] = timeSec;
m_SendSequence[sequence % NetworkConfig.RttWindowSize] = sequence;
}
internal void NotifyAck(int sequence, double timeSec)
{
// if the same slot was not used by a later send
if (m_SendSequence[sequence % NetworkConfig.RttWindowSize] == sequence)
{
double latency = timeSec - m_RttSendTimes[sequence % NetworkConfig.RttWindowSize];
m_MeasuredLatencies[m_LatenciesEnd] = latency;
m_LatenciesEnd = (m_LatenciesEnd + 1) % NetworkConfig.RttAverageSamples;
if (m_LatenciesEnd == m_LatenciesBegin)
{
m_LatenciesBegin = (m_LatenciesBegin + 1) % NetworkConfig.RttAverageSamples;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c275febadb27c4d18b41218e3353b84b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Exceptions.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2643fec0e8509fd409e5aceeba931323
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Exception thrown when the new parent candidate of the NetworkObject is not valid
/// </summary>
public class InvalidParentException : Exception
{
public InvalidParentException() { }
public InvalidParentException(string message) : base(message) { }
public InvalidParentException(string message, Exception innerException) : base(message, innerException) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abe1766318991412cb1aba96c4dbcc4e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Exception thrown when a change to a configuration is wrong
/// </summary>
public class NetworkConfigurationException : Exception
{
/// <summary>
/// Constructs a NetworkConfigurationException
/// </summary>
public NetworkConfigurationException() { }
/// <summary>
/// Constructs a NetworkConfigurationException with a message
/// </summary>
/// <param name="message">The exception message</param>
public NetworkConfigurationException(string message) : base(message) { }
/// <summary>
/// Constructs a NetworkConfigurationException with a message and a inner exception
/// </summary>
/// <param name="message">The exception message</param>
/// <param name="inner">The inner exception</param>
public NetworkConfigurationException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f50fae9dd20afbd438766cc330b188a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Exception thrown when the operation require NetworkManager to be listening.
/// </summary>
public class NotListeningException : Exception
{
/// <summary>
/// Constructs a NotListeningException
/// </summary>
public NotListeningException() { }
/// <summary>
/// Constructs a NotListeningException with a message
/// </summary>
/// <param name="message">The exception message</param>
public NotListeningException(string message) : base(message) { }
/// <summary>
/// Constructs a NotListeningException with a message and a inner exception
/// </summary>
/// <param name="message">The exception message</param>
/// <param name="inner">The inner exception</param>
public NotListeningException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc58b328162929140aeb42e6a4a9354b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Exception thrown when the operation can only be done on the server
/// </summary>
public class NotServerException : Exception
{
/// <summary>
/// Constructs a NotServerException
/// </summary>
public NotServerException() { }
/// <summary>
/// Constructs a NotServerException with a message
/// </summary>
/// <param name="message">The exception message</param>
public NotServerException(string message) : base(message) { }
/// <summary>
/// Constructs a NotServerException with a message and a inner exception
/// </summary>
/// <param name="message">The exception message</param>
/// <param name="inner">The inner exception</param>
public NotServerException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 751e55ce1418ecd4e9b08fef9d8e9591
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Exception thrown when an object is not yet spawned
/// </summary>
public class SpawnStateException : Exception
{
/// <summary>
/// Constructs a SpawnStateException
/// </summary>
public SpawnStateException() { }
/// <summary>
/// Constructs a SpawnStateException with a message
/// </summary>
/// <param name="message">The exception message</param>
public SpawnStateException(string message) : base(message) { }
/// <summary>
/// Constructs a SpawnStateException with a message and a inner exception
/// </summary>
/// <param name="message">The exception message</param>
/// <param name="inner">The inner exception</param>
public SpawnStateException(string message, Exception inner) : base(message, inner) { }
}
public class InvalidChannelException : Exception
{
public InvalidChannelException(string message) : base(message) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70c2a851ae78c6e49b34c7e4ca46305f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Exception thrown when a visibility change fails
/// </summary>
public class VisibilityChangeException : Exception
{
/// <summary>
/// Constructs a VisibilityChangeException
/// </summary>
public VisibilityChangeException() { }
/// <summary>
/// Constructs a VisibilityChangeException with a message
/// </summary>
/// <param name="message">The exception message</param>
public VisibilityChangeException(string message) : base(message) { }
/// <summary>
/// Constructs a VisibilityChangeException with a message and a inner exception
/// </summary>
/// <param name="message">The exception message</param>
/// <param name="inner">The inner exception</param>
public VisibilityChangeException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85d754bbea7d74a48918ebe05833e7ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Hashing.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e0b5218305f20f9419d00c4ab2adabef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2c61e8fe9a68a486fbbc3128d233ded2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015, 2016 Sedat Kapanoglu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cf89ecbf6f9954c8ea6d0848b1e79d87
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,318 @@
// <copyright file="XXHash.cs" company="Sedat Kapanoglu">
// Copyright (c) 2015-2019 Sedat Kapanoglu
// MIT License (see LICENSE file for details)
// </copyright>
// @mfatihmar (Unity): Modified for Unity support
using System.Text;
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
/// <summary>
/// XXHash implementation.
/// </summary>
internal static class XXHash
{
private const ulong k_Prime64v1 = 11400714785074694791ul;
private const ulong k_Prime64v2 = 14029467366897019727ul;
private const ulong k_Prime64v3 = 1609587929392839161ul;
private const ulong k_Prime64v4 = 9650029242287828579ul;
private const ulong k_Prime64v5 = 2870177450012600261ul;
private const uint k_Prime32v1 = 2654435761u;
private const uint k_Prime32v2 = 2246822519u;
private const uint k_Prime32v3 = 3266489917u;
private const uint k_Prime32v4 = 668265263u;
private const uint k_Prime32v5 = 374761393u;
public static uint Hash32(string text) => Hash32(text, Encoding.UTF8);
public static uint Hash32(string text, Encoding encoding) => Hash32(encoding.GetBytes(text));
public static uint Hash32(byte[] buffer)
{
unsafe
{
fixed (byte* ptr = buffer)
{
return Hash32(ptr, buffer.Length);
}
}
}
/// <summary>
/// Generate a 32-bit xxHash value.
/// </summary>
/// <param name="buffer">Input buffer.</param>
/// <param name="bufferLength">Input buffer length.</param>
/// <param name="seed">Optional seed.</param>
/// <returns>32-bit hash value.</returns>
public static unsafe uint Hash32(byte* buffer, int bufferLength, uint seed = 0)
{
const int stripeLength = 16;
int len = bufferLength;
int remainingLen = len;
uint acc;
byte* pInput = buffer;
if (len >= stripeLength)
{
uint acc1 = seed + k_Prime32v1 + k_Prime32v2;
uint acc2 = seed + k_Prime32v2;
uint acc3 = seed;
uint acc4 = seed - k_Prime32v1;
do
{
acc = processStripe32(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4);
remainingLen -= stripeLength;
} while (remainingLen >= stripeLength);
}
else
{
acc = seed + k_Prime32v5;
}
acc += (uint)len;
acc = processRemaining32(pInput, acc, remainingLen);
return avalanche32(acc);
}
public static ulong Hash64(string text) => Hash64(text, Encoding.UTF8);
public static ulong Hash64(string text, Encoding encoding) => Hash64(encoding.GetBytes(text));
public static ulong Hash64(byte[] buffer)
{
unsafe
{
fixed (byte* ptr = buffer)
{
return Hash64(ptr, buffer.Length);
}
}
}
/// <summary>
/// Generate a 64-bit xxHash value.
/// </summary>
/// <param name="buffer">Input buffer.</param>
/// <param name="bufferLength">Input buffer length.</param>
/// <param name="seed">Optional seed.</param>
/// <returns>Computed 64-bit hash value.</returns>
public static unsafe ulong Hash64(byte* buffer, int bufferLength, ulong seed = 0)
{
const int stripeLength = 32;
int len = bufferLength;
int remainingLen = len;
ulong acc;
byte* pInput = buffer;
if (len >= stripeLength)
{
ulong acc1 = seed + k_Prime64v1 + k_Prime64v2;
ulong acc2 = seed + k_Prime64v2;
ulong acc3 = seed;
ulong acc4 = seed - k_Prime64v1;
do
{
acc = processStripe64(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4);
remainingLen -= stripeLength;
} while (remainingLen >= stripeLength);
}
else
{
acc = seed + k_Prime64v5;
}
acc += (ulong)len;
acc = processRemaining64(pInput, acc, remainingLen);
return avalanche64(acc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ulong processStripe64(
ref byte* pInput,
ref ulong acc1,
ref ulong acc2,
ref ulong acc3,
ref ulong acc4)
{
processLane64(ref acc1, ref pInput);
processLane64(ref acc2, ref pInput);
processLane64(ref acc3, ref pInput);
processLane64(ref acc4, ref pInput);
ulong acc = Bits.RotateLeft(acc1, 1)
+ Bits.RotateLeft(acc2, 7)
+ Bits.RotateLeft(acc3, 12)
+ Bits.RotateLeft(acc4, 18);
mergeAccumulator64(ref acc, acc1);
mergeAccumulator64(ref acc, acc2);
mergeAccumulator64(ref acc, acc3);
mergeAccumulator64(ref acc, acc4);
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void processLane64(ref ulong accn, ref byte* pInput)
{
ulong lane = *(ulong*)pInput;
accn = round64(accn, lane);
pInput += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ulong processRemaining64(
byte* pInput,
ulong acc,
int remainingLen)
{
for (ulong lane; remainingLen >= 8; remainingLen -= 8, pInput += 8)
{
lane = *(ulong*)pInput;
acc ^= round64(0, lane);
acc = Bits.RotateLeft(acc, 27) * k_Prime64v1;
acc += k_Prime64v4;
}
for (uint lane32; remainingLen >= 4; remainingLen -= 4, pInput += 4)
{
lane32 = *(uint*)pInput;
acc ^= lane32 * k_Prime64v1;
acc = Bits.RotateLeft(acc, 23) * k_Prime64v2;
acc += k_Prime64v3;
}
for (byte lane8; remainingLen >= 1; remainingLen--, pInput++)
{
lane8 = *pInput;
acc ^= lane8 * k_Prime64v5;
acc = Bits.RotateLeft(acc, 11) * k_Prime64v1;
}
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong avalanche64(ulong acc)
{
acc ^= acc >> 33;
acc *= k_Prime64v2;
acc ^= acc >> 29;
acc *= k_Prime64v3;
acc ^= acc >> 32;
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong round64(ulong accn, ulong lane)
{
accn += lane * k_Prime64v2;
return Bits.RotateLeft(accn, 31) * k_Prime64v1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void mergeAccumulator64(ref ulong acc, ulong accn)
{
acc ^= round64(0, accn);
acc *= k_Prime64v1;
acc += k_Prime64v4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint processStripe32(
ref byte* pInput,
ref uint acc1,
ref uint acc2,
ref uint acc3,
ref uint acc4)
{
processLane32(ref pInput, ref acc1);
processLane32(ref pInput, ref acc2);
processLane32(ref pInput, ref acc3);
processLane32(ref pInput, ref acc4);
return Bits.RotateLeft(acc1, 1)
+ Bits.RotateLeft(acc2, 7)
+ Bits.RotateLeft(acc3, 12)
+ Bits.RotateLeft(acc4, 18);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void processLane32(ref byte* pInput, ref uint accn)
{
uint lane = *(uint*)pInput;
accn = round32(accn, lane);
pInput += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint processRemaining32(
byte* pInput,
uint acc,
int remainingLen)
{
for (uint lane; remainingLen >= 4; remainingLen -= 4, pInput += 4)
{
lane = *(uint*)pInput;
acc += lane * k_Prime32v3;
acc = Bits.RotateLeft(acc, 17) * k_Prime32v4;
}
for (byte lane; remainingLen >= 1; remainingLen--, pInput++)
{
lane = *pInput;
acc += lane * k_Prime32v5;
acc = Bits.RotateLeft(acc, 11) * k_Prime32v1;
}
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint round32(uint accn, uint lane)
{
accn += lane * k_Prime32v2;
accn = Bits.RotateLeft(accn, 13);
accn *= k_Prime32v1;
return accn;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint avalanche32(uint acc)
{
acc ^= acc >> 15;
acc *= k_Prime32v2;
acc ^= acc >> 13;
acc *= k_Prime32v3;
acc ^= acc >> 16;
return acc;
}
/// <summary>
/// Bit operations.
/// </summary>
private static class Bits
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong RotateLeft(ulong value, int bits)
{
return (value << bits) | (value >> (64 - bits));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static uint RotateLeft(uint value, int bits)
{
return (value << bits) | (value >> (32 - bits));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5aa7a49e9e694f148d810d34577546b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Logging.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eb2e0b0239cebdd44b534f09be21f1e6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
namespace Unity.Netcode
{
/// <summary>
/// Log level
/// </summary>
public enum LogLevel
{
/// <summary>
/// Developer logging level, most verbose
/// </summary>
Developer,
/// <summary>
/// Normal logging level, medium verbose
/// </summary>
Normal,
/// <summary>
/// Error logging level, very quiet
/// </summary>
Error,
/// <summary>
/// Nothing logging level, no logging will be done
/// </summary>
Nothing
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b97845a27cb50d44da22aae51800abfe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// Helper class for logging
/// </summary>
public static class NetworkLog
{
/// <summary>
/// Gets the current log level.
/// </summary>
/// <value>The current log level.</value>
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
// internal logging
internal static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
internal static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
internal static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
/// <summary>
/// Logs an info log locally and on the server if possible.
/// </summary>
/// <param name="message">The message to log</param>
public static void LogInfoServer(string message) => LogServer(message, LogType.Info);
/// <summary>
/// Logs a warning log locally and on the server if possible.
/// </summary>
/// <param name="message">The message to log</param>
public static void LogWarningServer(string message) => LogServer(message, LogType.Warning);
/// <summary>
/// Logs an error log locally and on the server if possible.
/// </summary>
/// <param name="message">The message to log</param>
public static void LogErrorServer(string message) => LogServer(message, LogType.Error);
private static void LogServer(string message, LogType logType)
{
// Get the sender of the local log
ulong localId = NetworkManager.Singleton != null ? NetworkManager.Singleton.LocalClientId : 0;
switch (logType)
{
case LogType.Info:
LogInfoServerLocal(message, localId);
break;
case LogType.Warning:
LogWarningServerLocal(message, localId);
break;
case LogType.Error:
LogErrorServerLocal(message, localId);
break;
}
if (NetworkManager.Singleton != null && !NetworkManager.Singleton.IsServer && NetworkManager.Singleton.NetworkConfig.EnableNetworkLogs)
{
var networkMessage = new ServerLogMessage
{
LogType = logType,
Message = message
};
var size = NetworkManager.Singleton.SendMessage(networkMessage, NetworkDelivery.ReliableFragmentedSequenced,
NetworkManager.Singleton.ServerClientId);
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size);
}
}
internal static void LogInfoServerLocal(string message, ulong sender) => Debug.Log($"[Netcode-Server Sender={sender}] {message}");
internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[Netcode-Server Sender={sender}] {message}");
internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[Netcode-Server Sender={sender}] {message}");
internal enum LogType : byte
{
Info,
Warning,
Error,
None
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b33d7f8ddec3b140a4ff09659174e8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Messaging.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 563c514253a555b48a68707decc5c088
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
namespace Unity.Netcode
{
/// <summary>
/// Header placed at the start of each message batch
/// </summary>
internal struct BatchHeader
{
/// <summary>
/// Total number of messages in the batch.
/// </summary>
public ushort BatchSize;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 941fcfe2222f8734ab5bfb9bc4787717
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,281 @@
using System;
using System.Collections.Generic;
namespace Unity.Netcode
{
/// <summary>
/// The manager class to manage custom messages, note that this is different from the NetworkManager custom messages.
/// These are named and are much easier to use.
/// </summary>
public class CustomMessagingManager
{
private readonly NetworkManager m_NetworkManager;
internal CustomMessagingManager(NetworkManager networkManager)
{
m_NetworkManager = networkManager;
}
/// <summary>
/// Delegate used for incoming unnamed messages
/// </summary>
/// <param name="clientId">The clientId that sent the message</param>
/// <param name="reader">The stream containing the message data</param>
public delegate void UnnamedMessageDelegate(ulong clientId, FastBufferReader reader);
/// <summary>
/// Event invoked when unnamed messages arrive
/// </summary>
public event UnnamedMessageDelegate OnUnnamedMessage;
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader)
{
if (OnUnnamedMessage != null)
{
var pos = reader.Position;
var delegates = OnUnnamedMessage.GetInvocationList();
foreach (var handler in delegates)
{
reader.Seek(pos);
((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
}
}
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length);
}
/// <summary>
/// Sends unnamed message to all clients
/// </summary>
/// <param name="messageBuffer">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessageToAll(FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
SendUnnamedMessage(m_NetworkManager.ConnectedClientsIds, messageBuffer, networkDelivery);
}
/// <summary>
/// Sends unnamed message to a list of clients
/// </summary>
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
/// <param name="messageBuffer">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessage(IReadOnlyList<ulong> clientIds, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
if (!m_NetworkManager.IsServer)
{
throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client");
}
if (clientIds == null)
{
throw new ArgumentNullException("You must pass in a valid clientId List");
}
var message = new UnnamedMessage
{
Data = messageBuffer
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
{
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientIds, size);
}
}
/// <summary>
/// Sends a unnamed message to a specific client
/// </summary>
/// <param name="clientId">The client to send the message to</param>
/// <param name="messageBuffer">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
var message = new UnnamedMessage
{
Data = messageBuffer
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
{
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageSent(clientId, size);
}
}
/// <summary>
/// Delegate used to handle named messages
/// </summary>
public delegate void HandleNamedMessageDelegate(ulong senderClientId, FastBufferReader messagePayload);
private Dictionary<ulong, HandleNamedMessageDelegate> m_NamedMessageHandlers32 = new Dictionary<ulong, HandleNamedMessageDelegate>();
private Dictionary<ulong, HandleNamedMessageDelegate> m_NamedMessageHandlers64 = new Dictionary<ulong, HandleNamedMessageDelegate>();
private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>();
private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>();
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader)
{
var bytesCount = reader.Length;
if (m_NetworkManager == null)
{
// We dont know what size to use. Try every (more collision prone)
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{
messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
}
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{
messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
}
}
else
{
// Only check the right size.
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
{
case HashSize.VarIntFourBytes:
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{
messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
}
break;
case HashSize.VarIntEightBytes:
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{
messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
}
break;
}
}
}
/// <summary>
/// Registers a named message handler delegate.
/// </summary>
/// <param name="name">Name of the message.</param>
/// <param name="callback">The callback to run when a named message is received.</param>
public void RegisterNamedMessageHandler(string name, HandleNamedMessageDelegate callback)
{
var hash32 = XXHash.Hash32(name);
var hash64 = XXHash.Hash64(name);
m_NamedMessageHandlers32[hash32] = callback;
m_NamedMessageHandlers64[hash64] = callback;
m_MessageHandlerNameLookup32[hash32] = name;
m_MessageHandlerNameLookup64[hash64] = name;
}
/// <summary>
/// Unregisters a named message handler.
/// </summary>
/// <param name="name">The name of the message.</param>
public void UnregisterNamedMessageHandler(string name)
{
var hash32 = XXHash.Hash32(name);
var hash64 = XXHash.Hash64(name);
m_NamedMessageHandlers32.Remove(hash32);
m_NamedMessageHandlers64.Remove(hash64);
m_MessageHandlerNameLookup32.Remove(hash32);
m_MessageHandlerNameLookup64.Remove(hash64);
}
/// <summary>
/// Sends a named message to all clients
/// </summary>
/// <param name="messageStream">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendNamedMessageToAll(string messageName, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
SendNamedMessage(messageName, m_NetworkManager.ConnectedClientsIds, messageStream, networkDelivery);
}
/// <summary>
/// Sends a named message
/// </summary>
/// <param name="messageName">The message name to send</param>
/// <param name="clientId">The client to send the message to</param>
/// <param name="messageStream">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendNamedMessage(string messageName, ulong clientId, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
ulong hash = 0;
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
{
case HashSize.VarIntFourBytes:
hash = XXHash.Hash32(messageName);
break;
case HashSize.VarIntEightBytes:
hash = XXHash.Hash64(messageName);
break;
}
var message = new NamedMessage
{
Hash = hash,
Data = messageStream
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
{
m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientId, messageName, size);
}
}
/// <summary>
/// Sends the named message
/// </summary>
/// <param name="messageName">The message name to send</param>
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
/// <param name="messageStream">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
if (!m_NetworkManager.IsServer)
{
throw new InvalidOperationException("Can not send unnamed messages to multiple users as a client");
}
if (clientIds == null)
{
throw new ArgumentNullException("You must pass in a valid clientId List");
}
ulong hash = 0;
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
{
case HashSize.VarIntFourBytes:
hash = XXHash.Hash32(messageName);
break;
case HashSize.VarIntEightBytes:
hash = XXHash.Hash64(messageName);
break;
}
var message = new NamedMessage
{
Hash = hash,
Data = messageStream
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
{
m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15c647dad40a44d46886dca112cfd524
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Unity.Netcode
{
internal struct ILPPMessageProvider : IMessageProvider
{
#pragma warning disable IDE1006 // disable naming rule violation check
// This is NOT modified by RuntimeAccessModifiersILPP right now, but is populated by ILPP.
internal static readonly List<MessagingSystem.MessageWithHandler> __network_message_types = new List<MessagingSystem.MessageWithHandler>();
#pragma warning restore IDE1006 // restore naming rule violation check
public List<MessagingSystem.MessageWithHandler> GetMessages()
{
return __network_message_types;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5bfc2b13811eb5429a0eef59dd88b71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Unity.Netcode
{
internal interface IMessageProvider
{
List<MessagingSystem.MessageWithHandler> GetMessages();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ed41def88d712c4b9c115310e6b0127
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
namespace Unity.Netcode
{
internal interface IMessageSender
{
void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15b54cd88eba22648ade4240523b8c65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Used to react to different events in the messaging system. Primary use case is for
/// collecting profiling data and metrics data. Additionally, it provides OnVerifyCanSend and OnVerifyCanReceive
/// to allow for networking implementations to put limits on when certain messages can or can't be sent or received.
/// </summary>
internal interface INetworkHooks
{
/// <summary>
/// Called before an individual message is sent.
/// </summary>
/// <param name="clientId">The destination clientId</param>
/// <param name="messageType">The type of the message being sent</param>
/// <param name="delivery"></param>
void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery);
/// <summary>
/// Called after an individual message is sent.
/// </summary>
/// <param name="clientId">The destination clientId</param>
/// <param name="messageType">The type of the message being sent</param>
/// <param name="delivery"></param>
/// <param name="messageSizeBytes">Number of bytes in the message, not including the message header</param>
void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes);
/// <summary>
/// Called before an individual message is received.
/// </summary>
/// <param name="senderId">The source clientId</param>
/// <param name="messageType">The type of the message being sent</param>
/// <param name="messageSizeBytes">Number of bytes in the message, not including the message header</param>
void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes);
/// <summary>
/// Called after an individual message is received.
/// </summary>
/// <param name="senderId">The source clientId</param>
/// <param name="messageType">The type of the message being sent</param>
/// <param name="messageSizeBytes">Number of bytes in the message, not including the message header</param>
void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes);
/// <summary>
/// Called before a batch of messages is sent
/// </summary>
/// <param name="clientId">The destination clientId</param>
/// <param name="messageCount">Number of messages in the batch</param>
/// <param name="batchSizeInBytes">Number of bytes in the batch, including the batch header</param>
/// <param name="delivery"></param>
void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery);
/// <summary>
/// Called after a batch of messages is sent
/// </summary>
/// <param name="clientId">The destination clientId</param>
/// <param name="messageCount">Number of messages in the batch</param>
/// <param name="batchSizeInBytes">Number of bytes in the batch, including the batch header</param>
/// <param name="delivery"></param>
void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery);
/// <summary>
/// Called before a batch of messages is received
/// </summary>
/// <param name="senderId">The source clientId</param>
/// <param name="messageCount">Number of messages in the batch</param>
/// <param name="batchSizeInBytes">Number of bytes in the batch, including the batch header</param>
void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes);
/// <summary>
/// Called after a batch of messages is received
/// </summary>
/// <param name="senderId">The source clientId</param>
/// <param name="messageCount">Number of messages in the batch</param>
/// <param name="batchSizeInBytes">Number of bytes in the batch, including the batch header</param>
void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes);
/// <summary>
/// Called before a message is sent. If this returns false, the message will be discarded.
/// </summary>
/// <param name="destinationId">The destination clientId</param>
/// <param name="messageType">The type of the message</param>
/// <param name="delivery"></param>
/// <returns></returns>
bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery);
/// <summary>
/// Called before a message is received. If this returns false, the message will be discarded.
/// </summary>
/// <param name="senderId">The source clientId</param>
/// <param name="messageType">The type of the message</param>
/// <returns></returns>
bool OnVerifyCanReceive(ulong senderId, Type messageType);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b199c5a160beabb47bd6b0e4f06cfcd2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using Unity.Collections;
namespace Unity.Netcode
{
/// <summary>
/// Base building block for creating a message. Any struct (or class) that implements INetworkMessage
/// will automatically be found by the system and all the proper mechanisms for sending and receiving
/// that message will be hooked up automatically.
///
/// It's generally recommended to implement INetworkMessage types as structs, and define your messages
/// as close as you can to the network transport format. For messages with no dynamic-length or optional
/// data, FastBufferWriter allows for serializing the entire struct at once via writer.WriteValue(this)
///
/// In addition to the specified Serialize method, all INetworkMessage types must also have a
/// static message handler for receiving messages of the following name and signature:
///
/// <code>
/// public static void Receive(FastBufferReader reader, in NetworkContext context)
/// </code>
///
/// It is the responsibility of the Serialize and Receive methods to ensure there is enough buffer space
/// to perform the serialization/deserialization, either via <see cref="FastBufferWriter.TryBeginWrite"/> and
/// <see cref="FastBufferReader.TryBeginRead"/>, or via <see cref="FastBufferWriter.WriteValueSafe{T}(T)"/> and
/// <see cref="FastBufferReader.ReadValueSafe{T}(T)"/>. The former is more efficient when it can be used
/// for bounds checking for multiple values at once.
///
/// When bandwidth is a bigger concern than CPU usage, values can be packed with <see cref="BytePacker"/>
/// and <see cref="ByteUnpacker"/>.
///
/// Note that for messages sent using non-fragmenting delivery modes (anything other than
/// <see cref="NetworkDelivery.ReliableFragmentedSequenced"/>), there is a hard limit of 1300 bytes per message.
/// With the fragmenting delivery mode, the limit is 64000 bytes per message. If your messages exceed that limit,
/// you will have to split them into multiple smaller messages.
///
/// Messages are sent with:
/// <see cref="NetworkManager.SendMessage{T}(T, NetworkDelivery, ulong, bool)"/>
/// <see cref="NetworkManager.SendMessage{T}(T, NetworkDelivery, ulong*, int, bool)"/>
/// <see cref="NetworkManager.SendMessage{T, U}(T, NetworkDelivery, U, bool)"/>
/// <see cref="NetworkManager.SendMessage{T}(T, NetworkDelivery, NativeArray{ulong}, bool)"/>
/// </summary>
internal interface INetworkMessage
{
/// <summary>
/// Used to serialize the message.
/// </summary>
/// <param name="writer"></param>
void Serialize(FastBufferWriter writer);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16079de8d8821a24c91db930bc892b5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
namespace Unity.Netcode
{
/// <summary>
/// This is the header data that's serialized to the network when sending an <see cref="INetworkMessage"/>
/// </summary>
internal struct MessageHeader
{
/// <summary>
/// The byte representation of the message type. This is automatically assigned to each message
/// by the MessagingSystem. This value is deterministic only so long as the list of messages remains
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be
/// calculated differently.
/// </summary>
public byte MessageType;
/// <summary>
/// The total size of the message, NOT including the header.
/// </summary>
public ushort MessageSize;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74fa727ddec342c48ab49156a32b977b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fd834639d7f09614fa4f3296921871d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
namespace Unity.Netcode
{
internal struct ChangeOwnershipMessage : INetworkMessage
{
public ulong NetworkObjectId;
public ulong OwnerClientId;
public void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(this);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
}
reader.ReadValueSafe(out ChangeOwnershipMessage message);
message.Handle(reader, context, context.SenderId, networkManager, reader.Length);
}
public void Handle(FastBufferReader reader, in NetworkContext context, ulong senderId, NetworkManager networkManager, int messageSize)
{
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
return;
}
if (networkObject.OwnerClientId == networkManager.LocalClientId)
{
//We are current owner.
networkObject.InvokeBehaviourOnLostOwnership();
}
networkObject.OwnerClientId = OwnerClientId;
if (OwnerClientId == networkManager.LocalClientId)
{
//We are new owner.
networkObject.InvokeBehaviourOnGainedOwnership();
}
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(senderId, networkObject, messageSize);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 841becdc46b20d5408a81bc30ac950f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
namespace Unity.Netcode
{
internal struct ConnectionApprovedMessage : INetworkMessage
{
public ulong OwnerClientId;
public int NetworkTick;
public int SceneObjectCount;
// Not serialized, held as references to serialize NetworkVariable data
public HashSet<NetworkObject> SpawnedObjectsList;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int)))
{
throw new OverflowException(
$"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
}
writer.WriteValue(OwnerClientId);
writer.WriteValue(NetworkTick);
writer.WriteValue(SceneObjectCount);
if (SceneObjectCount != 0)
{
// Serialize NetworkVariable data
foreach (var sobj in SpawnedObjectsList)
{
if (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))
{
sobj.Observers.Add(OwnerClientId);
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);
sceneObject.Serialize(writer);
}
}
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
}
if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int)))
{
throw new OverflowException(
$"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
}
var message = new ConnectionApprovedMessage();
reader.ReadValue(out message.OwnerClientId);
reader.ReadValue(out message.NetworkTick);
reader.ReadValue(out message.SceneObjectCount);
message.Handle(reader, context.SenderId, networkManager);
}
public void Handle(FastBufferReader reader, ulong clientId, NetworkManager networkManager)
{
networkManager.LocalClientId = OwnerClientId;
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick);
networkManager.NetworkTimeSystem.Reset(time.Time, 0.15f); // Start with a constant RTT of 150 until we receive values from the transport.
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement)
{
networkManager.SpawnManager.DestroySceneObjects();
// Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing
// to create a list to hold the data. This is a breach of convention for performance reasons.
for (ushort i = 0; i < SceneObjectCount; i++)
{
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(reader);
NetworkObject.AddSceneObject(sceneObject, reader, networkManager);
}
// Mark the client being connected
networkManager.IsConnectedClient = true;
// When scene management is disabled we notify after everything is synchronized
networkManager.InvokeOnClientConnectedCallback(clientId);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ebbc74ce01b073340aa445f3bd59ff62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,115 @@
namespace Unity.Netcode
{
internal struct ConnectionRequestMessage : INetworkMessage
{
public ulong ConfigHash;
public byte[] ConnectionData;
public bool ShouldSendConnectionData;
public void Serialize(FastBufferWriter writer)
{
if (ShouldSendConnectionData)
{
writer.WriteValueSafe(ConfigHash);
writer.WriteValueSafe(ConnectionData);
}
else
{
writer.WriteValueSafe(ConfigHash);
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsServer)
{
return;
}
var message = new ConnectionRequestMessage();
if (networkManager.NetworkConfig.ConnectionApproval)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash) +
FastBufferWriter.GetWriteSize<int>()))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Incomplete connection request message given config - possible {nameof(NetworkConfig)} mismatch.");
}
networkManager.DisconnectClient(context.SenderId);
return;
}
reader.ReadValue(out message.ConfigHash);
if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match");
}
networkManager.DisconnectClient(context.SenderId);
return;
}
reader.ReadValueSafe(out message.ConnectionData);
}
else
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash)))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Incomplete connection request message.");
}
networkManager.DisconnectClient(context.SenderId);
return;
}
reader.ReadValue(out message.ConfigHash);
if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match");
}
networkManager.DisconnectClient(context.SenderId);
return;
}
}
message.Handle(networkManager, context.SenderId);
}
public void Handle(NetworkManager networkManager, ulong senderId)
{
if (networkManager.PendingClients.TryGetValue(senderId, out PendingClient client))
{
// Set to pending approval to prevent future connection requests from being approved
client.ConnectionState = PendingClient.State.PendingApproval;
}
if (networkManager.NetworkConfig.ConnectionApproval)
{
// Note: Delegate creation allocates.
// Note: ToArray() also allocates. :(
networkManager.InvokeConnectionApproval(ConnectionData, senderId,
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
{
var localCreatePlayerObject = createPlayerObject;
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved,
position, rotation);
});
}
else
{
networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd160468676e06049ad81dcfb22c3dc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
namespace Unity.Netcode
{
internal struct CreateObjectMessage : INetworkMessage
{
public NetworkObject.SceneObject ObjectInfo;
public void Serialize(FastBufferWriter writer)
{
ObjectInfo.Serialize(writer);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
}
var message = new CreateObjectMessage();
message.ObjectInfo.Deserialize(reader);
message.Handle(context.SenderId, reader, networkManager);
}
public void Handle(ulong senderId, FastBufferReader reader, NetworkManager networkManager)
{
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, reader, networkManager);
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, reader.Length);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ff075de988adf5a4294620aa2d85fcd0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
namespace Unity.Netcode
{
internal struct DestroyObjectMessage : INetworkMessage
{
public ulong NetworkObjectId;
public void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(this);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
}
reader.ReadValueSafe(out DestroyObjectMessage message);
message.Handle(context.SenderId, networkManager, reader.Length);
}
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
{
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
// This is the same check and log message that happens inside OnDespawnObject, but we have to do it here
// while we still have access to the network ID, otherwise the log message will be less useful.
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Trying to destroy {nameof(NetworkObject)} #{NetworkObjectId} but it does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!");
}
return;
}
networkManager.NetworkMetrics.TrackObjectDestroyReceived(senderId, networkObject, messageSize);
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18473ed11c97e7241aecb0c72d4bffb2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
namespace Unity.Netcode
{
internal struct NamedMessage : INetworkMessage
{
public ulong Hash;
public FastBufferWriter Data;
public unsafe void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(Hash);
writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var message = new NamedMessage();
reader.ReadValueSafe(out message.Hash);
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b6bcd41dcce65743ba387fc4e2c6fa8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
/// <summary>
/// This particular struct is a little weird because it doesn't actually contain the data
/// it's serializing. Instead, it contains references to the data it needs to do the
/// serialization. This is due to the generally amorphous nature of network variable
/// deltas, since they're all driven by custom virtual method overloads.
/// </summary>
internal struct NetworkVariableDeltaMessage : INetworkMessage
{
public ulong NetworkObjectId;
public ushort NetworkBehaviourIndex;
public HashSet<int> DeliveryMappedNetworkVariableIndex;
public ulong ClientId;
public NetworkBehaviour NetworkBehaviour;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) +
FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException(
$"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
writer.WriteValue(NetworkObjectId);
writer.WriteValue(NetworkBehaviourIndex);
for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++)
{
if (!DeliveryMappedNetworkVariableIndex.Contains(k))
{
// This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
writer.WriteValueSafe((short)0);
}
else
{
writer.WriteValueSafe(false);
}
continue;
}
// if I'm dirty AND a client, write (server always has all permissions)
// if I'm dirty AND the server AND the client can read me, send.
bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer);
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (!shouldWrite)
{
writer.WriteValueSafe((ushort)0);
}
}
else
{
writer.WriteValueSafe(shouldWrite);
}
if (shouldWrite)
{
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue);
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter);
writer.WriteValueSafe((ushort)tmpWriter.Length);
tmpWriter.CopyTo(writer);
}
else
{
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(writer);
}
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k))
{
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k);
NetworkBehaviour.NetworkVariableIndexesToReset.Add(k);
}
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
ClientId,
NetworkBehaviour.NetworkObject,
NetworkBehaviour.NetworkVariableFields[k].Name,
NetworkBehaviour.__getTypeName(),
writer.Length);
}
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
var message = new NetworkVariableDeltaMessage();
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.NetworkObjectId) +
FastBufferWriter.GetWriteSize(message.NetworkBehaviourIndex)))
{
throw new OverflowException(
$"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
}
reader.ReadValue(out message.NetworkObjectId);
reader.ReadValue(out message.NetworkBehaviourIndex);
message.Handle(context.SenderId, reader, context, networkManager);
}
public void Handle(ulong senderId, FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
{
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
{
NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
if (behaviour == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(NetworkObjectId)}: {NetworkObjectId}, {nameof(NetworkBehaviourIndex)}: {NetworkBehaviourIndex}");
}
}
else
{
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
{
ushort varSize = 0;
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
reader.ReadValueSafe(out varSize);
if (varSize == 0)
{
continue;
}
}
else
{
reader.ReadValueSafe(out bool deltaExists);
if (!deltaExists)
{
continue;
}
}
if (networkManager.IsServer)
{
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
}
reader.Seek(reader.Position + varSize);
continue;
}
//This client wrote somewhere they are not allowed. This is critical
//We can't just skip this field. Because we don't actually know how to dummy read
//That is, we don't know how many bytes to skip. Because the interface doesn't have a
//Read that gives us the value. Only a Read that applies the value straight away
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
//This is after all a developer fault. A critical error should be fine.
// - TwoTen
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
}
return;
}
int readStartPos = reader.Position;
behaviour.NetworkVariableFields[i].ReadDelta(reader, networkManager.IsServer);
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
senderId,
networkObject,
behaviour.NetworkVariableFields[i].Name,
behaviour.__getTypeName(),
reader.Length);
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (reader.Position > (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
$"Var delta read too far. {reader.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
}
reader.Seek(readStartPos + varSize);
}
else if (reader.Position < (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
$"Var delta read too little. {(readStartPos + varSize) - reader.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
}
reader.Seek(readStartPos + varSize);
}
}
}
}
}
else
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dfa1a454cc9fdb647ba89479fa6b8299
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
namespace Unity.Netcode
{
internal struct ParentSyncMessage : INetworkMessage
{
public ulong NetworkObjectId;
public bool IsReparented;
//If(Metadata.IsReparented)
public bool IsLatestParentSet;
//If(IsLatestParentSet)
public ulong? LatestParent;
public void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(NetworkObjectId);
writer.WriteValueSafe(IsReparented);
if (IsReparented)
{
writer.WriteValueSafe(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValueSafe((ulong)LatestParent);
}
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
}
var message = new ParentSyncMessage();
reader.ReadValueSafe(out message.NetworkObjectId);
reader.ReadValueSafe(out message.IsReparented);
if (message.IsReparented)
{
reader.ReadValueSafe(out message.IsLatestParentSet);
if (message.IsLatestParentSet)
{
reader.ReadValueSafe(out ulong latestParent);
message.LatestParent = latestParent;
}
}
message.Handle(reader, context, networkManager);
}
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
{
if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
networkObject.SetNetworkParenting(IsReparented, LatestParent);
networkObject.ApplyNetworkParenting();
}
else
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 237bfa46868f1ff48863f3a6df2f5506
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using System;
namespace Unity.Netcode
{
internal struct RpcMessage : INetworkMessage
{
public enum RpcType : byte
{
Server,
Client
}
public struct HeaderData
{
public RpcType Type;
public ulong NetworkObjectId;
public ushort NetworkBehaviourId;
public uint NetworkMethodId;
}
public HeaderData Header;
public FastBufferWriter RpcData;
public unsafe void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Header) + RpcData.Length))
{
throw new OverflowException("Not enough space in the buffer to store RPC data.");
}
writer.WriteValue(Header);
writer.WriteBytes(RpcData.GetUnsafePtr(), RpcData.Length);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var message = new RpcMessage();
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.Header)))
{
throw new OverflowException("Not enough space in the buffer to read RPC data.");
}
reader.ReadValue(out message.Header);
message.Handle(reader, context, (NetworkManager)context.SystemOwner, context.SenderId, true);
}
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager, ulong senderId, bool canDefer)
{
if (NetworkManager.__rpc_func_table.ContainsKey(Header.NetworkMethodId))
{
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(Header.NetworkObjectId))
{
if (canDefer)
{
networkManager.SpawnManager.TriggerOnSpawn(Header.NetworkObjectId, reader, context);
}
else
{
NetworkLog.LogError($"Tried to invoke an RPC on a non-existent {nameof(NetworkObject)} with {nameof(canDefer)}=false");
}
return;
}
var networkObject = networkManager.SpawnManager.SpawnedObjects[Header.NetworkObjectId];
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(Header.NetworkBehaviourId);
if (networkBehaviour == null)
{
return;
}
var rpcParams = new __RpcParams();
switch (Header.Type)
{
case RpcType.Server:
rpcParams.Server = new ServerRpcParams
{
Receive = new ServerRpcReceiveParams
{
SenderClientId = senderId
}
};
break;
case RpcType.Client:
rpcParams.Client = new ClientRpcParams
{
Receive = new ClientRpcReceiveParams
{
}
};
break;
}
NetworkManager.__rpc_func_table[Header.NetworkMethodId](networkBehaviour, reader, rpcParams);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(Header.NetworkMethodId, out var rpcMethodName))
{
networkManager.NetworkMetrics.TrackRpcReceived(
senderId,
networkObject,
rpcMethodName,
networkBehaviour.__getTypeName(),
reader.Length);
}
#endif
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f8fc9f8cca6a18b428460b62bce2d8f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
namespace Unity.Netcode
{
// Todo: Would be lovely to get this one nicely formatted with all the data it sends in the struct
// like most of the other messages when we have some more time and can come back and refactor this.
internal struct SceneEventMessage : INetworkMessage
{
public SceneEventData EventData;
public void Serialize(FastBufferWriter writer)
{
EventData.Serialize(writer);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, reader);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 152f6ccef0320cf4abcf19099c1997e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
namespace Unity.Netcode
{
internal struct ServerLogMessage : INetworkMessage
{
public NetworkLog.LogType LogType;
// It'd be lovely to be able to replace this with FixedString or NativeArray...
// But it's not really practical. On the sending side, the user is likely to want
// to work with strings and would need to convert, and on the receiving side,
// we'd have to convert it to a string to be able to pass it to the log system.
// So an allocation is unavoidable here on both sides.
public string Message;
public void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(LogType);
BytePacker.WriteValuePacked(writer, Message);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)
{
var message = new ServerLogMessage();
reader.ReadValueSafe(out message.LogType);
ByteUnpacker.ReadValuePacked(reader, out message.Message);
message.Handle(context.SenderId, networkManager, reader.Length);
}
}
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
{
networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, messageSize);
switch (LogType)
{
case NetworkLog.LogType.Info:
NetworkLog.LogInfoServerLocal(Message, senderId);
break;
case NetworkLog.LogType.Warning:
NetworkLog.LogWarningServerLocal(Message, senderId);
break;
case NetworkLog.LogType.Error:
NetworkLog.LogErrorServerLocal(Message, senderId);
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d1c8aae1f7b7194eb3ab1cab260f34f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More