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:
388
Runtime/Core/IndexAllocator.cs
Normal file
388
Runtime/Core/IndexAllocator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/IndexAllocator.cs.meta
Normal file
11
Runtime/Core/IndexAllocator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9e1475e8c8e4a6d935fe2409e3bd26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
676
Runtime/Core/NetworkBehaviour.cs
Normal file
676
Runtime/Core/NetworkBehaviour.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/NetworkBehaviour.cs.meta
Normal file
11
Runtime/Core/NetworkBehaviour.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8ea6ec00590bd44a983c228bcaee727
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Runtime/Core/NetworkBehaviourUpdater.cs
Normal file
84
Runtime/Core/NetworkBehaviourUpdater.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
3
Runtime/Core/NetworkBehaviourUpdater.cs.meta
Normal file
3
Runtime/Core/NetworkBehaviourUpdater.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d084c01093b446878bcb76e5d7f3221e
|
||||
timeCreated: 1622225163
|
||||
1618
Runtime/Core/NetworkManager.cs
Normal file
1618
Runtime/Core/NetworkManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Core/NetworkManager.cs.meta
Normal file
11
Runtime/Core/NetworkManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 593a2fe42fa9d37498c96f9a383b6521
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1155
Runtime/Core/NetworkObject.cs
Normal file
1155
Runtime/Core/NetworkObject.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Core/NetworkObject.cs.meta
Normal file
11
Runtime/Core/NetworkObject.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5a57f767e5e46a458fc5d3c628d0cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
417
Runtime/Core/NetworkUpdateLoop.cs
Normal file
417
Runtime/Core/NetworkUpdateLoop.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/NetworkUpdateLoop.cs.meta
Normal file
11
Runtime/Core/NetworkUpdateLoop.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cd9c24b9acfd4e82a71c795f37235c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
93
Runtime/Core/SnapshotRTT.cs
Normal file
93
Runtime/Core/SnapshotRTT.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/SnapshotRTT.cs.meta
Normal file
11
Runtime/Core/SnapshotRTT.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1062
Runtime/Core/SnapshotSystem.cs
Normal file
1062
Runtime/Core/SnapshotSystem.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Core/SnapshotSystem.cs.meta
Normal file
11
Runtime/Core/SnapshotSystem.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c275febadb27c4d18b41218e3353b84b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user