Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8060718e04 | ||
|
|
fe02ca682e | ||
|
|
1e7078c160 |
130
CHANGELOG.md
130
CHANGELOG.md
@@ -1,4 +1,3 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
@@ -7,9 +6,123 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||
|
||||
## [1.3.1] - 2023-03-27
|
||||
|
||||
### Added
|
||||
|
||||
- Added detection and graceful handling of corrupt packets for additional safety. (#2419)
|
||||
|
||||
### Changed
|
||||
|
||||
- The UTP component UI has been updated to be more user-friendly for new users by adding a simple toggle to switch between local-only (127.0.0.1) and remote (0.0.0.0) binding modes, using the toggle "Allow Remote Connections" (#2408)
|
||||
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.3. (#2450)
|
||||
- `NetworkShow()` of `NetworkObject`s are delayed until the end of the frame to ensure consistency of delta-driven variables like `NetworkList`.
|
||||
- Dirty `NetworkObject` are reset at end-of-frame and not at serialization time.
|
||||
- `NetworkHide()` of an object that was just `NetworkShow()`n produces a warning, as remote clients will _not_ get a spawn/despawn pair.
|
||||
- Renamed the NetworkTransform.SetState parameter `shouldGhostsInterpolate` to `teleportDisabled` for better clarity of what that parameter does. (#2228)
|
||||
- Network prefabs are now stored in a ScriptableObject that can be shared between NetworkManagers, and have been exposed for public access. By default, a Default Prefabs List is created that contains all NetworkObject prefabs in the project, and new NetworkManagers will default to using that unless that option is turned off in the Netcode for GameObjects settings. Existing NetworkManagers will maintain their existing lists, which can be migrated to the new format via a button in their inspector. (#2322)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where changes to a layer's weight would not synchronize unless a state transition was occurring.(#2399)
|
||||
- Fixed issue where `NetworkManager.LocalClientId` was returning the `NetworkTransport.ServerClientId` as opposed to the `NetworkManager.m_LocalClientId`. (#2398)
|
||||
- Fixed issue where a dynamically spawned `NetworkObject` parented under an in-scene placed `NetworkObject` would have its `InScenePlaced` value changed to `true`. This would result in a soft synchronization error for late joining clients. (#2396)
|
||||
- Fixed a UTP test that was failing when you install Unity Transport package 2.0.0 or newer. (#2347)
|
||||
- Fixed issue where `NetcodeSettingsProvider` would throw an exception in Unity 2020.3.x versions. (#2345)
|
||||
- Fixed server side issue where, depending upon component ordering, some NetworkBehaviour components might not have their OnNetworkDespawn method invoked if the client side disconnected. (#2323)
|
||||
- Fixed a case where data corruption could occur when using UnityTransport when reaching a certain level of send throughput. (#2332)
|
||||
- Fixed an issue in `UnityTransport` where an exception would be thrown if starting a Relay host/server on WebGL. This exception should only be thrown if using direct connections (where WebGL can't act as a host/server). (#2321)
|
||||
- Fixed `NetworkAnimator` issue where it was not checking for `AnimatorStateTtansition.destinationStateMachine` and any possible sub-states defined within it. (#2309)
|
||||
- Fixed `NetworkAnimator` issue where the host client was receiving the ClientRpc animation updates when the host was the owner.(#2309)
|
||||
- Fixed `NetworkAnimator` issue with using pooled objects and when specific properties are cleaned during despawn and destroy.(#2309)
|
||||
- Fixed issue where `NetworkAnimator` was checking for animation changes when the associated `NetworkObject` was not spawned.(#2309)
|
||||
- Corrected an issue with the documentation for BufferSerializer (#2401)
|
||||
|
||||
## [1.2.0] - 2022-11-21
|
||||
|
||||
### Added
|
||||
|
||||
- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298)
|
||||
- Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290)
|
||||
- Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285)
|
||||
- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280)
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310)
|
||||
- When `NetworkConfig.EnsureNetworkVariableLengthSafety` is disabled `NetworkVariable` fields do not write the additional `ushort` size value (_which helps to reduce the total synchronization message size_), but when enabled it still writes the additional `ushort` value. (#2298)
|
||||
- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276)
|
||||
|
||||
### Fixed
|
||||
- Fixed `IsSpawnedObjectsPendingInDontDestroyOnLoad` is only set to true when loading a scene using `LoadSceneMode.Singleonly`. (#2330)
|
||||
- Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298)
|
||||
- Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298)
|
||||
- Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298)
|
||||
- Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296)
|
||||
- Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292)
|
||||
- Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289)
|
||||
- Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281)
|
||||
- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277)
|
||||
- Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285)
|
||||
|
||||
## [1.1.0] - 2022-10-21
|
||||
|
||||
### Added
|
||||
|
||||
- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261)
|
||||
- `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235)
|
||||
- IPv6 is now supported for direct connections when using `UnityTransport`. (#2232)
|
||||
- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201)
|
||||
- Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146)
|
||||
- Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.0. (#2231)
|
||||
- The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212)
|
||||
- As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212)
|
||||
- The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196)
|
||||
- `NetworkVariable<>` now supports managed `INetworkSerializable` types, as well as other managed types with serialization/deserialization delegates registered to `UserNetworkVariableSerialization<T>.WriteValue` and `UserNetworkVariableSerialization<T>.ReadValue` (#2219)
|
||||
- `NetworkVariable<>` and `BufferSerializer<BufferSerializerReader>` now deserialize `INetworkSerializable` types in-place, rather than constructing new ones. (#2219)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261)
|
||||
- Fixed issue caused when changing ownership of objects hidden to some clients (#2242)
|
||||
- Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239)
|
||||
- Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226)
|
||||
- Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225)
|
||||
- Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222)
|
||||
- Fixed issue where `SceneEventProgress` would not complete if a client disconnects. (#2222)
|
||||
- Fixed issues with detecting if a `SceneEventProgress` has timed out. (#2222)
|
||||
- Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220)
|
||||
- Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203)
|
||||
- Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199)
|
||||
- Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158)
|
||||
- Fixed warning resulting from a stray NetworkAnimator.meta file (#2153)
|
||||
- Fixed Connection Approval Timeout not working client side. (#2164)
|
||||
- Fixed issue where the `WorldPositionStays` parenting parameter was not being synchronized with clients. (#2146)
|
||||
- Fixed issue where parented in-scene placed `NetworkObject`s would fail for late joining clients. (#2146)
|
||||
- Fixed issue where scale was not being synchronized which caused issues with nested parenting and scale when `WorldPositionStays` was true. (#2146)
|
||||
- Fixed issue with `NetworkTransform.ApplyTransformToNetworkStateWithInfo` where it was not honoring axis sync settings when `NetworkTransformState.IsTeleportingNextFrame` was true. (#2146)
|
||||
- Fixed issue with `NetworkTransform.TryCommitTransformToServer` where it was not honoring the `InLocalSpace` setting. (#2146)
|
||||
- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144)
|
||||
- Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List<int>) and extensions for multiple instantiations of that type have been defined (i.e., List<int> and List<string>) (#2142)
|
||||
- Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127)
|
||||
- Fixed issue where late-joining client transition synchronization could fail when more than one transition was occurring.(#2127)
|
||||
- Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739)
|
||||
- Fixed synchronization when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independent from the local time scale. (#2171)
|
||||
- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987)
|
||||
- Fixed IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership (#2211)
|
||||
|
||||
## [1.0.2] - 2022-09-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170)
|
||||
- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170)
|
||||
- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170)
|
||||
@@ -106,6 +219,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
- Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs) (#1912)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898)
|
||||
- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890)
|
||||
- Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
|
||||
@@ -159,10 +273,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
## [1.0.0-pre.6] - 2022-03-02
|
||||
|
||||
### Added
|
||||
|
||||
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765)
|
||||
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683)
|
||||
- Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683)
|
||||
- Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683)
|
||||
@@ -214,6 +330,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
|
||||
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
|
||||
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
|
||||
@@ -228,6 +345,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
|
||||
|
||||
### Changed
|
||||
|
||||
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
|
||||
- Updated com.unity.collections to 1.1.0 (#1451)
|
||||
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
|
||||
@@ -321,7 +439,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
- 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 `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)
|
||||
@@ -448,10 +566,10 @@ 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 `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.
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
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")]
|
||||
#endif
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
#endif // UNITY_EDITOR
|
||||
|
||||
#if UNITY_INCLUDE_TESTS
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
#endif // UNITY_EDITOR
|
||||
#endif // UNITY_INCLUDE_TESTS
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ namespace Unity.Netcode.Components
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody")]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Unity.Netcode.Components
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
|
||||
public class NetworkRigidbody2D : NetworkBehaviour
|
||||
{
|
||||
private Rigidbody2D m_Rigidbody;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Unity.Netcode.Components
|
||||
/// The replicated value will be automatically be interpolated (if active) and applied to the underlying GameObject's transform.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkTransform))]
|
||||
[AddComponentMenu("Netcode/Network Transform")]
|
||||
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
|
||||
public class NetworkTransform : NetworkBehaviour
|
||||
{
|
||||
@@ -282,14 +282,7 @@ namespace Unity.Netcode.Components
|
||||
{
|
||||
// Go ahead and mark the local state dirty or not dirty as well
|
||||
/// <see cref="TryCommitTransformToServer"/>
|
||||
if (HasPositionChange || HasRotAngleChange || HasScaleChange)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsDirty = false;
|
||||
}
|
||||
IsDirty = HasPositionChange || HasRotAngleChange || HasScaleChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,17 +452,31 @@ namespace Unity.Netcode.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculated when spawned, this is used to offset a newly received non-authority side state by 1 tick duration
|
||||
/// in order to end the extrapolation for that state's values.
|
||||
/// This is invoked when a new client joins (server and client sides)
|
||||
/// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState)
|
||||
/// Client Side: Adds the interpolated state which applies the NetworkTransformState as well
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Example:
|
||||
/// NetworkState-A is received, processed, and measurements added
|
||||
/// NetworkState-A is duplicated (NetworkState-A-Post) and its sent time is offset by the tick frequency
|
||||
/// One tick later, NetworkState-A-Post is applied to end that delta's extrapolation.
|
||||
/// <see cref="OnNetworkStateChanged"/> to see how NetworkState-A-Post doesn't get excluded/missed
|
||||
/// </remarks>
|
||||
private double m_TickFrequency;
|
||||
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
|
||||
{
|
||||
// We don't need to synchronize NetworkTransforms that are on the same
|
||||
// GameObject as the NetworkObject.
|
||||
if (NetworkObject.gameObject == gameObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var synchronizationState = new NetworkTransformState();
|
||||
if (serializer.IsWriter)
|
||||
{
|
||||
synchronizationState.IsTeleportingNextFrame = true;
|
||||
ApplyTransformToNetworkStateWithInfo(ref synchronizationState, m_CachedNetworkManager.LocalTime.Time, transform);
|
||||
synchronizationState.NetworkSerialize(serializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
synchronizationState.NetworkSerialize(serializer);
|
||||
AddInterpolatedState(synchronizationState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will try to send/commit the current transform delta states (if any)
|
||||
@@ -495,14 +502,16 @@ namespace Unity.Netcode.Components
|
||||
}
|
||||
else // Non-Authority
|
||||
{
|
||||
var position = InLocalSpace ? transformToCommit.localPosition : transformToCommit.position;
|
||||
var rotation = InLocalSpace ? transformToCommit.localRotation : transformToCommit.rotation;
|
||||
// We are an owner requesting to update our state
|
||||
if (!m_CachedIsServer)
|
||||
{
|
||||
SetStateServerRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false);
|
||||
SetStateServerRpc(position, rotation, transformToCommit.localScale, false);
|
||||
}
|
||||
else // Server is always authoritative (including owner authoritative)
|
||||
{
|
||||
SetStateClientRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false);
|
||||
SetStateClientRpc(position, rotation, transformToCommit.localScale, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,37 +537,24 @@ namespace Unity.Netcode.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the interpolators with the current transform values
|
||||
/// </summary>
|
||||
private void ResetInterpolatedStateToCurrentAuthoritativeState()
|
||||
{
|
||||
var serverTime = NetworkManager.ServerTime.Time;
|
||||
|
||||
// TODO: Look into a better way to communicate the entire state for late joining clients.
|
||||
// Since the replicated network state will just be the most recent deltas and not the entire state.
|
||||
//m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime);
|
||||
//m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime);
|
||||
//m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime);
|
||||
|
||||
//m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime);
|
||||
|
||||
//m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime);
|
||||
//m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime);
|
||||
//m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
|
||||
|
||||
// NOTE ABOUT THIS CHANGE:
|
||||
// !!! This will exclude any scale changes because we currently do not spawn network objects with scale !!!
|
||||
// Regarding Scale: It will be the same scale as the default scale for the object being spawned.
|
||||
var position = InLocalSpace ? transform.localPosition : transform.position;
|
||||
m_PositionXInterpolator.ResetTo(position.x, serverTime);
|
||||
m_PositionYInterpolator.ResetTo(position.y, serverTime);
|
||||
m_PositionZInterpolator.ResetTo(position.z, serverTime);
|
||||
|
||||
var rotation = InLocalSpace ? transform.localRotation : transform.rotation;
|
||||
m_RotationInterpolator.ResetTo(rotation, serverTime);
|
||||
|
||||
// TODO: (Create Jira Ticket) Synchronize local scale during NetworkObject synchronization
|
||||
// (We will probably want to byte pack TransformData to offset the 3 float addition)
|
||||
m_ScaleXInterpolator.ResetTo(transform.localScale.x, serverTime);
|
||||
m_ScaleYInterpolator.ResetTo(transform.localScale.y, serverTime);
|
||||
m_ScaleZInterpolator.ResetTo(transform.localScale.z, serverTime);
|
||||
var scale = transform.localScale;
|
||||
m_ScaleXInterpolator.ResetTo(scale.x, serverTime);
|
||||
m_ScaleYInterpolator.ResetTo(scale.y, serverTime);
|
||||
m_ScaleZInterpolator.ResetTo(scale.z, serverTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -609,63 +605,63 @@ namespace Unity.Netcode.Components
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionX && Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.PositionX = position.x;
|
||||
networkState.HasPositionX = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionY && Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.PositionY = position.y;
|
||||
networkState.HasPositionY = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionZ && Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.PositionZ = position.z;
|
||||
networkState.HasPositionZ = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleX && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.RotAngleX = rotAngles.x;
|
||||
networkState.HasRotAngleX = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleY && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.RotAngleY = rotAngles.y;
|
||||
networkState.HasRotAngleY = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleZ && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.RotAngleZ = rotAngles.z;
|
||||
networkState.HasRotAngleZ = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleX && Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.ScaleX = scale.x;
|
||||
networkState.HasScaleX = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleY && Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.ScaleY = scale.y;
|
||||
networkState.HasScaleY = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleZ && Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame)
|
||||
if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame))
|
||||
{
|
||||
networkState.ScaleZ = scale.z;
|
||||
networkState.HasScaleZ = true;
|
||||
@@ -1014,7 +1010,6 @@ namespace Unity.Netcode.Components
|
||||
{
|
||||
m_CachedIsServer = IsServer;
|
||||
m_CachedNetworkManager = NetworkManager;
|
||||
m_TickFrequency = 1.0 / NetworkManager.NetworkConfig.TickRate;
|
||||
|
||||
Initialize();
|
||||
|
||||
@@ -1097,10 +1092,9 @@ namespace Unity.Netcode.Components
|
||||
/// <param name="posIn"></param> new position to move to. Can be null
|
||||
/// <param name="rotIn"></param> new rotation to rotate to. Can be null
|
||||
/// <param name="scaleIn">new scale to scale to. Can be null</param>
|
||||
/// <param name="shouldGhostsInterpolate">Should other clients interpolate this change or not. True by default</param>
|
||||
/// new scale to scale to. Can be null
|
||||
/// <param name="teleportDisabled">When true (the default) the <see cref="NetworkObject"/> will not be teleported and, if enabled, will interpolate. When false the <see cref="NetworkObject"/> will teleport/apply the parameters provided immediately.</param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool shouldGhostsInterpolate = true)
|
||||
public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool teleportDisabled = true)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
@@ -1124,16 +1118,16 @@ namespace Unity.Netcode.Components
|
||||
{
|
||||
m_ClientIds[0] = OwnerClientId;
|
||||
m_ClientRpcParams.Send.TargetClientIds = m_ClientIds;
|
||||
SetStateClientRpc(pos, rot, scale, !shouldGhostsInterpolate, m_ClientRpcParams);
|
||||
SetStateClientRpc(pos, rot, scale, !teleportDisabled, m_ClientRpcParams);
|
||||
}
|
||||
else // Preserving the ability for server authoritative mode to accept state changes from owner
|
||||
{
|
||||
SetStateServerRpc(pos, rot, scale, !shouldGhostsInterpolate);
|
||||
SetStateServerRpc(pos, rot, scale, !teleportDisabled);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SetStateInternal(pos, rot, scale, !shouldGhostsInterpolate);
|
||||
SetStateInternal(pos, rot, scale, !teleportDisabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1150,8 +1144,7 @@ namespace Unity.Netcode.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = pos;
|
||||
transform.rotation = rot;
|
||||
transform.SetPositionAndRotation(pos, rot);
|
||||
}
|
||||
transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
||||
@@ -1169,11 +1162,7 @@ namespace Unity.Netcode.Components
|
||||
private void SetStateClientRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport, ClientRpcParams clientRpcParams = default)
|
||||
{
|
||||
// Server dictated state is always applied
|
||||
transform.position = pos;
|
||||
transform.rotation = rot;
|
||||
transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
||||
TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time);
|
||||
SetStateInternal(pos, rot, scale, shouldTeleport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1190,12 +1179,7 @@ namespace Unity.Netcode.Components
|
||||
{
|
||||
(pos, rot, scale) = OnClientRequestChange(pos, rot, scale);
|
||||
}
|
||||
|
||||
transform.position = pos;
|
||||
transform.rotation = rot;
|
||||
transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
||||
TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time);
|
||||
SetStateInternal(pos, rot, scale, shouldTeleport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if UNITY_INCLUDE_TESTS
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
|
||||
#endif // UNITY_EDITOR
|
||||
#endif // UNITY_INCLUDE_TESTS
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal static class CodeGenHelpers
|
||||
{
|
||||
public const string DotnetModuleName = "netstandard.dll";
|
||||
public const string UnityModuleName = "UnityEngine.CoreModule.dll";
|
||||
public const string NetcodeModuleName = "Unity.Netcode.Runtime.dll";
|
||||
|
||||
public const string RuntimeAssemblyName = "Unity.Netcode.Runtime";
|
||||
|
||||
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName;
|
||||
@@ -119,6 +123,19 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
try
|
||||
{
|
||||
var typeDef = typeReference.Resolve();
|
||||
// Note: this won't catch generics correctly.
|
||||
//
|
||||
// class Foo<T>: IInterface<T> {}
|
||||
// class Bar: Foo<int> {}
|
||||
//
|
||||
// Bar.HasInterface(IInterface<int>) -> returns false even though it should be true.
|
||||
//
|
||||
// This can be fixed (see GetAllFieldsAndResolveGenerics() in NetworkBehaviourILPP to understand how)
|
||||
// but right now we don't need that to work so it's left alone to reduce complexity
|
||||
if (typeDef.BaseType.HasInterface(interfaceTypeFullName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var typeFaces = typeDef.Interfaces;
|
||||
return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName);
|
||||
}
|
||||
@@ -380,5 +397,74 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
|
||||
private static void SearchForBaseModulesRecursive(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver, ref ModuleDefinition unityModule, ref ModuleDefinition netcodeModule, HashSet<string> visited)
|
||||
{
|
||||
|
||||
foreach (var module in assemblyDefinition.Modules)
|
||||
{
|
||||
if (module == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (unityModule == null && module.Name == UnityModuleName)
|
||||
{
|
||||
unityModule = module;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (netcodeModule == null && module.Name == NetcodeModuleName)
|
||||
{
|
||||
netcodeModule = module;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var assemblyNameReference in assemblyDefinition.MainModule.AssemblyReferences)
|
||||
{
|
||||
if (assemblyNameReference == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (visited.Contains(assemblyNameReference.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.Add(assemblyNameReference.Name);
|
||||
|
||||
var assembly = assemblyResolver.Resolve(assemblyNameReference);
|
||||
if (assembly == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SearchForBaseModulesRecursive(assembly, assemblyResolver, ref unityModule, ref netcodeModule, visited);
|
||||
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static (ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver)
|
||||
{
|
||||
ModuleDefinition unityModule = null;
|
||||
ModuleDefinition netcodeModule = null;
|
||||
var visited = new HashSet<string>();
|
||||
SearchForBaseModulesRecursive(assemblyDefinition, assemblyResolver, ref unityModule, ref netcodeModule, visited);
|
||||
|
||||
return (unityModule, netcodeModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
@@ -17,8 +16,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
@@ -32,13 +30,22 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out m_AssemblyResolver);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// modules
|
||||
(_, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver);
|
||||
|
||||
if (m_NetcodeModule == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
@@ -60,7 +67,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -91,77 +98,136 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private ModuleDefinition m_NetcodeModule;
|
||||
private PostProcessorAssemblyResolver m_AssemblyResolver;
|
||||
|
||||
private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef;
|
||||
private MethodReference m_MessagingSystem_CreateMessageAndGetVersion_MethodRef;
|
||||
private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
|
||||
private MethodReference m_MessagingSystem_MessageHandler_Constructor_TypeRef;
|
||||
private MethodReference m_MessagingSystem_VersionGetter_Constructor_TypeRef;
|
||||
private FieldReference m_ILPPMessageProvider___network_message_types_FieldRef;
|
||||
private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef;
|
||||
private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef;
|
||||
private FieldReference m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef;
|
||||
private MethodReference m_Type_GetTypeFromHandle_MethodRef;
|
||||
private MethodReference m_List_Add_MethodRef;
|
||||
|
||||
private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage);
|
||||
private const string k_CreateMessageAndGetVersionName = nameof(MessagingSystem.CreateMessageAndGetVersion);
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]);
|
||||
// Different environments seem to have different situations...
|
||||
// Some have these definitions in netstandard.dll...
|
||||
// some seem to have them elsewhere...
|
||||
// Since they're standard .net classes they're not going to cause
|
||||
// the same issues as referencing other assemblies, in theory, since
|
||||
// the definitions should be standard and consistent across platforms
|
||||
// (i.e., there's no #if UNITY_EDITOR in them that could create
|
||||
// invalid IL code)
|
||||
TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve();
|
||||
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
|
||||
|
||||
var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler);
|
||||
m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerType);
|
||||
foreach (var fieldInfo in messageWithHandlerType.GetFields())
|
||||
TypeDefinition messageHandlerTypeDef = null;
|
||||
TypeDefinition versionGetterTypeDef = null;
|
||||
TypeDefinition messageWithHandlerTypeDef = null;
|
||||
TypeDefinition ilppMessageProviderTypeDef = null;
|
||||
TypeDefinition messagingSystemTypeDef = null;
|
||||
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
if (messageHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageHandler))
|
||||
{
|
||||
messageHandlerTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (versionGetterTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.VersionGetter))
|
||||
{
|
||||
versionGetterTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler))
|
||||
{
|
||||
messageWithHandlerTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ilppMessageProviderTypeDef == null && netcodeTypeDef.Name == nameof(ILPPMessageProvider))
|
||||
{
|
||||
ilppMessageProviderTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (messagingSystemTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem))
|
||||
{
|
||||
messagingSystemTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First());
|
||||
m_MessagingSystem_VersionGetter_Constructor_TypeRef = moduleDefinition.ImportReference(versionGetterTypeDef.GetConstructors().First());
|
||||
|
||||
m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef);
|
||||
foreach (var fieldDef in messageWithHandlerTypeDef.Fields)
|
||||
{
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case nameof(MessagingSystem.MessageWithHandler.MessageType):
|
||||
m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
case nameof(MessagingSystem.MessageWithHandler.Handler):
|
||||
m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
case nameof(MessagingSystem.MessageWithHandler.GetVersion):
|
||||
m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var typeType = typeof(Type);
|
||||
foreach (var methodInfo in typeType.GetMethods())
|
||||
foreach (var methodDef in typeTypeDef.Methods)
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case nameof(Type.GetTypeFromHandle):
|
||||
m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ilppMessageProviderType = typeof(ILPPMessageProvider);
|
||||
foreach (var fieldInfo in ilppMessageProviderType.GetFields(BindingFlags.Static | BindingFlags.NonPublic))
|
||||
foreach (var fieldDef in ilppMessageProviderTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case nameof(ILPPMessageProvider.__network_message_types):
|
||||
m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var listType = typeof(List<MessagingSystem.MessageWithHandler>);
|
||||
foreach (var methodInfo in listType.GetMethods())
|
||||
foreach (var methodDef in listTypeDef.Methods)
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case nameof(List<MessagingSystem.MessageWithHandler>.Add):
|
||||
m_List_Add_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
case "Add":
|
||||
m_List_Add_MethodRef = methodDef;
|
||||
m_List_Add_MethodRef.DeclaringType = listTypeDef.MakeGenericInstanceType(messageWithHandlerTypeDef);
|
||||
m_List_Add_MethodRef = moduleDefinition.ImportReference(m_List_Add_MethodRef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var messagingSystemType = typeof(MessagingSystem);
|
||||
foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var methodDef in messagingSystemTypeDef.Methods)
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_ReceiveMessageName:
|
||||
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_CreateMessageAndGetVersionName:
|
||||
m_MessagingSystem_CreateMessageAndGetVersion_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -188,7 +254,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return staticCtorMethodDef;
|
||||
}
|
||||
|
||||
private void CreateInstructionsToRegisterType(ILProcessor processor, List<Instruction> instructions, TypeReference type, MethodReference receiveMethod)
|
||||
private void CreateInstructionsToRegisterType(ILProcessor processor, List<Instruction> instructions, TypeReference type, MethodReference receiveMethod, MethodReference versionMethod)
|
||||
{
|
||||
// MessagingSystem.__network_message_types.Add(new MessagingSystem.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
|
||||
processor.Body.Variables.Add(new VariableDefinition(m_MessagingSystem_MessageWithHandler_TypeRef));
|
||||
@@ -204,7 +270,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
instructions.Add(processor.Create(OpCodes.Call, m_Type_GetTypeFromHandle_MethodRef));
|
||||
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_MessageType_FieldRef));
|
||||
|
||||
// tmp.Handler = type.Receive
|
||||
// tmp.Handler = MessageHandler.ReceveMessage<type>
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Ldnull));
|
||||
|
||||
@@ -212,15 +278,22 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_MessageHandler_Constructor_TypeRef));
|
||||
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_Handler_FieldRef));
|
||||
|
||||
|
||||
// tmp.GetVersion = MessageHandler.CreateMessageAndGetVersion<type>
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Ldnull));
|
||||
|
||||
instructions.Add(processor.Create(OpCodes.Ldftn, versionMethod));
|
||||
instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_VersionGetter_Constructor_TypeRef));
|
||||
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef));
|
||||
|
||||
// ILPPMessageProvider.__network_message_types.Add(tmp);
|
||||
instructions.Add(processor.Create(OpCodes.Ldloc, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Callvirt, m_List_Add_MethodRef));
|
||||
}
|
||||
|
||||
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
|
||||
// message types in the assembly with MessagingSystem.
|
||||
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
|
||||
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
|
||||
// Creates a static module constructor (which is executed when the module is loaded) that registers all the message types in the assembly with MessagingSystem.
|
||||
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized C# (that attribute doesn't exist in Unity, but the static module constructor still works).
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
|
||||
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
|
||||
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkMessageTypes)
|
||||
@@ -239,7 +312,9 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef);
|
||||
receiveMethod.GenericArguments.Add(type);
|
||||
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod);
|
||||
var versionMethod = new GenericInstanceMethod(m_MessagingSystem_CreateMessageAndGetVersion_MethodRef);
|
||||
versionMethod.GenericArguments.Add(type);
|
||||
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod, versionMethod);
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
|
||||
@@ -10,7 +10,6 @@ using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostPr
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
|
||||
internal sealed class INetworkSerializableILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
@@ -92,7 +91,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
@@ -23,8 +22,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
@@ -35,7 +33,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
@@ -46,11 +43,27 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return null;
|
||||
}
|
||||
|
||||
// modules
|
||||
(m_UnityModule, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver);
|
||||
|
||||
if (m_UnityModule == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot find Unity module: {CodeGenHelpers.UnityModuleName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (m_NetcodeModule == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
m_MainModule = mainModule;
|
||||
|
||||
if (ImportReferences(mainModule))
|
||||
{
|
||||
// process `NetworkBehaviour` types
|
||||
@@ -60,10 +73,12 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
.Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName))
|
||||
.ToList()
|
||||
.ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines));
|
||||
|
||||
CreateNetworkVariableTypeInitializers(assemblyDefinition);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -92,7 +107,135 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
if (staticCtorMethodDef == null)
|
||||
{
|
||||
staticCtorMethodDef = new MethodDefinition(
|
||||
".cctor", // Static Constructor (constant-constructor)
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.RTSpecialName |
|
||||
MethodAttributes.Static,
|
||||
typeDefinition.Module.TypeSystem.Void);
|
||||
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
typeDefinition.Methods.Add(staticCtorMethodDef);
|
||||
}
|
||||
|
||||
return staticCtorMethodDef;
|
||||
}
|
||||
|
||||
private bool IsMemcpyableType(TypeReference type)
|
||||
{
|
||||
foreach (var supportedType in BaseSupportedTypes)
|
||||
{
|
||||
if (type.FullName == supportedType.FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsSpecialCaseType(TypeReference type)
|
||||
{
|
||||
foreach (var supportedType in SpecialCaseTypes)
|
||||
{
|
||||
if (type.FullName == supportedType.FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
|
||||
{
|
||||
foreach (var typeDefinition in assembly.MainModule.Types)
|
||||
{
|
||||
if (typeDefinition.FullName == "<Module>")
|
||||
{
|
||||
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
|
||||
|
||||
var processor = staticCtorMethodDef.Body.GetILProcessor();
|
||||
|
||||
var instructions = new List<Instruction>();
|
||||
|
||||
foreach (var type in m_WrappedNetworkVariableTypes)
|
||||
{
|
||||
if (IsSpecialCaseType(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a serializable type isn't found, FallbackSerializer will be used automatically, which will
|
||||
// call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton
|
||||
// for types that aren't in our official supported types list.
|
||||
GenericInstanceMethod serializeMethod = null;
|
||||
GenericInstanceMethod equalityMethod;
|
||||
|
||||
if (type.IsValueType)
|
||||
{
|
||||
if (type.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || type.Resolve().IsEnum || IsMemcpyableType(type))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef);
|
||||
}
|
||||
else if (type.HasInterface(typeof(INetworkSerializable).FullName))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef);
|
||||
}
|
||||
else if (type.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && type.HasInterface(k_INativeListBool_FullName))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef);
|
||||
}
|
||||
|
||||
if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">"))
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type.HasInterface(typeof(INetworkSerializable).FullName))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef);
|
||||
}
|
||||
|
||||
if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">"))
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (serializeMethod != null)
|
||||
{
|
||||
serializeMethod.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod)));
|
||||
}
|
||||
equalityMethod.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(equalityMethod)));
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModuleDefinition m_MainModule;
|
||||
private ModuleDefinition m_UnityModule;
|
||||
private ModuleDefinition m_NetcodeModule;
|
||||
private PostProcessorAssemblyResolver m_AssemblyResolver;
|
||||
|
||||
private MethodReference m_Debug_LogError_MethodRef;
|
||||
@@ -123,14 +266,76 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
private FieldReference m_ServerRpcParams_Receive_FieldRef;
|
||||
private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef;
|
||||
private TypeReference m_ClientRpcParams_TypeRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
|
||||
|
||||
private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef;
|
||||
private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
|
||||
private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef;
|
||||
private MethodReference m_BytePacker_WriteValueBitPacked_UInt_MethodRef;
|
||||
private MethodReference m_BytePacker_WriteValueBitPacked_Long_MethodRef;
|
||||
private MethodReference m_BytePacker_WriteValueBitPacked_ULong_MethodRef;
|
||||
|
||||
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef;
|
||||
private MethodReference m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef;
|
||||
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef;
|
||||
private MethodReference m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef;
|
||||
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
|
||||
private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;
|
||||
|
||||
private TypeReference m_FastBufferWriter_TypeRef;
|
||||
private Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
|
||||
private readonly Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private readonly List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
|
||||
|
||||
private TypeReference m_FastBufferReader_TypeRef;
|
||||
private Dictionary<string, MethodReference> m_FastBufferReader_ReadValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private List<MethodReference> m_FastBufferReader_ExtensionMethodRefs = new List<MethodReference>();
|
||||
private readonly Dictionary<string, MethodReference> m_FastBufferReader_ReadValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private readonly List<MethodReference> m_FastBufferReader_ExtensionMethodRefs = new List<MethodReference>();
|
||||
|
||||
private HashSet<TypeReference> m_WrappedNetworkVariableTypes = new HashSet<TypeReference>();
|
||||
|
||||
internal static readonly Type[] BaseSupportedTypes = new[]
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(char),
|
||||
typeof(decimal),
|
||||
typeof(double),
|
||||
typeof(float),
|
||||
// the following types have special handling
|
||||
/*typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(short),
|
||||
typeof(ushort),*/
|
||||
typeof(Vector2),
|
||||
typeof(Vector3),
|
||||
typeof(Vector2Int),
|
||||
typeof(Vector3Int),
|
||||
typeof(Vector4),
|
||||
typeof(Quaternion),
|
||||
typeof(Color),
|
||||
typeof(Color32),
|
||||
typeof(Ray),
|
||||
typeof(Ray2D)
|
||||
};
|
||||
internal static readonly Type[] SpecialCaseTypes = new[]
|
||||
{
|
||||
// the following types have special handling
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
};
|
||||
|
||||
private const string k_Debug_LogError = nameof(Debug.LogError);
|
||||
private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId);
|
||||
@@ -157,160 +362,257 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
private const string k_ServerRpcParams_Receive = nameof(ServerRpcParams.Receive);
|
||||
private const string k_ServerRpcReceiveParams_SenderClientId = nameof(ServerRpcReceiveParams.SenderClientId);
|
||||
|
||||
// CodeGen cannot reference the collections assembly to do a typeof() on it due to a bug that causes that to crash.
|
||||
private const string k_INativeListBool_FullName = "Unity.Collections.INativeList`1<System.Byte>";
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
var debugType = typeof(Debug);
|
||||
foreach (var methodInfo in debugType.GetMethods())
|
||||
TypeDefinition debugTypeDef = null;
|
||||
foreach (var unityTypeDef in m_UnityModule.GetAllTypes())
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
if (debugTypeDef == null && unityTypeDef.FullName == typeof(Debug).FullName)
|
||||
{
|
||||
debugTypeDef = unityTypeDef;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TypeDefinition networkManagerTypeDef = null;
|
||||
TypeDefinition networkBehaviourTypeDef = null;
|
||||
TypeDefinition networkHandlerDelegateTypeDef = null;
|
||||
TypeDefinition rpcParamsTypeDef = null;
|
||||
TypeDefinition serverRpcParamsTypeDef = null;
|
||||
TypeDefinition clientRpcParamsTypeDef = null;
|
||||
TypeDefinition fastBufferWriterTypeDef = null;
|
||||
TypeDefinition fastBufferReaderTypeDef = null;
|
||||
TypeDefinition networkVariableSerializationTypesTypeDef = null;
|
||||
TypeDefinition bytePackerTypeDef = null;
|
||||
TypeDefinition byteUnpackerTypeDef = null;
|
||||
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
|
||||
{
|
||||
if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager))
|
||||
{
|
||||
networkManagerTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkBehaviourTypeDef == null && netcodeTypeDef.Name == nameof(NetworkBehaviour))
|
||||
{
|
||||
networkBehaviourTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler))
|
||||
{
|
||||
networkHandlerDelegateTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(__RpcParams))
|
||||
{
|
||||
rpcParamsTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serverRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ServerRpcParams))
|
||||
{
|
||||
serverRpcParamsTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (clientRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ClientRpcParams))
|
||||
{
|
||||
clientRpcParamsTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fastBufferWriterTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferWriter))
|
||||
{
|
||||
fastBufferWriterTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fastBufferReaderTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferReader))
|
||||
{
|
||||
fastBufferReaderTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkVariableSerializationTypesTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableSerializationTypes))
|
||||
{
|
||||
networkVariableSerializationTypesTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bytePackerTypeDef == null && netcodeTypeDef.Name == nameof(BytePacker))
|
||||
{
|
||||
bytePackerTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (byteUnpackerTypeDef == null && netcodeTypeDef.Name == nameof(ByteUnpacker))
|
||||
{
|
||||
byteUnpackerTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodDef in debugTypeDef.Methods)
|
||||
{
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_Debug_LogError:
|
||||
if (methodInfo.GetParameters().Length == 1)
|
||||
if (methodDef.Parameters.Count == 1)
|
||||
{
|
||||
m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var networkManagerType = typeof(NetworkManager);
|
||||
m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerType);
|
||||
foreach (var propertyInfo in networkManagerType.GetProperties())
|
||||
m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerTypeDef);
|
||||
foreach (var propertyDef in networkManagerTypeDef.Properties)
|
||||
{
|
||||
switch (propertyInfo.Name)
|
||||
switch (propertyDef.Name)
|
||||
{
|
||||
case k_NetworkManager_LocalClientId:
|
||||
m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsListening:
|
||||
m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsHost:
|
||||
m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsServer:
|
||||
m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsClient:
|
||||
m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fieldInfo in networkManagerType.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var fieldDef in networkManagerTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_NetworkManager_LogLevel:
|
||||
m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
case k_NetworkManager_rpc_func_table:
|
||||
m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add"));
|
||||
m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add");
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef.DeclaringType = fieldDef.FieldType;
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_func_table_Add_MethodRef);
|
||||
break;
|
||||
case k_NetworkManager_rpc_name_table:
|
||||
m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add"));
|
||||
m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add");
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef.DeclaringType = fieldDef.FieldType;
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_name_table_Add_MethodRef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var networkBehaviourType = typeof(NetworkBehaviour);
|
||||
m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourType);
|
||||
foreach (var propertyInfo in networkBehaviourType.GetProperties())
|
||||
m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourTypeDef);
|
||||
foreach (var propertyDef in networkBehaviourTypeDef.Properties)
|
||||
{
|
||||
switch (propertyInfo.Name)
|
||||
switch (propertyDef.Name)
|
||||
{
|
||||
case k_NetworkBehaviour_NetworkManager:
|
||||
m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkBehaviour_OwnerClientId:
|
||||
m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodInfo in networkBehaviourType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var methodDef in networkBehaviourTypeDef.Methods)
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_NetworkBehaviour_beginSendServerRpc:
|
||||
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_NetworkBehaviour_endSendServerRpc:
|
||||
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_NetworkBehaviour_beginSendClientRpc:
|
||||
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_NetworkBehaviour_endSendClientRpc:
|
||||
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fieldInfo in networkBehaviourType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var fieldDef in networkBehaviourTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_NetworkBehaviour_rpc_exec_stage:
|
||||
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var networkHandlerDelegateType = typeof(NetworkManager.RpcReceiveHandler);
|
||||
m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(networkHandlerDelegateType.GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
|
||||
|
||||
var rpcParamsType = typeof(__RpcParams);
|
||||
m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsType);
|
||||
foreach (var fieldInfo in rpcParamsType.GetFields())
|
||||
foreach (var ctor in networkHandlerDelegateTypeDef.Resolve().GetConstructors())
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
if (ctor.HasParameters &&
|
||||
ctor.Parameters.Count == 2 &&
|
||||
ctor.Parameters[0].ParameterType.Name == nameof(System.Object) &&
|
||||
ctor.Parameters[1].ParameterType.Name == nameof(IntPtr))
|
||||
{
|
||||
m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(ctor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsTypeDef);
|
||||
foreach (var fieldDef in rpcParamsTypeDef.Fields)
|
||||
{
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_RpcParams_Server:
|
||||
m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
case k_RpcParams_Client:
|
||||
m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var serverRpcParamsType = typeof(ServerRpcParams);
|
||||
m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsType);
|
||||
foreach (var fieldInfo in serverRpcParamsType.GetFields())
|
||||
m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsTypeDef);
|
||||
foreach (var fieldDef in serverRpcParamsTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_ServerRpcParams_Receive:
|
||||
foreach (var recvFieldInfo in fieldInfo.FieldType.GetFields())
|
||||
foreach (var recvFieldDef in fieldDef.FieldType.Resolve().Fields)
|
||||
{
|
||||
switch (recvFieldInfo.Name)
|
||||
switch (recvFieldDef.Name)
|
||||
{
|
||||
case k_ServerRpcReceiveParams_SenderClientId:
|
||||
m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldInfo);
|
||||
m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var clientRpcParamsType = typeof(ClientRpcParams);
|
||||
m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsType);
|
||||
m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsTypeDef);
|
||||
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterTypeDef);
|
||||
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderTypeDef);
|
||||
|
||||
var fastBufferWriterType = typeof(FastBufferWriter);
|
||||
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType);
|
||||
|
||||
var fastBufferReaderType = typeof(FastBufferReader);
|
||||
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType);
|
||||
|
||||
// Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented
|
||||
// methods to be called.
|
||||
// Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented methods to be called
|
||||
var assemblies = new List<AssemblyDefinition> { m_MainModule.Assembly };
|
||||
foreach (var reference in m_MainModule.AssemblyReferences)
|
||||
{
|
||||
@@ -371,9 +673,170 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var method in networkVariableSerializationTypesTypeDef.Methods)
|
||||
{
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (method.Name)
|
||||
{
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var method in bytePackerTypeDef.Methods)
|
||||
{
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (method.Name)
|
||||
{
|
||||
case nameof(BytePacker.WriteValueBitPacked):
|
||||
if (method.Parameters[1].ParameterType.FullName == typeof(short).FullName)
|
||||
{
|
||||
m_BytePacker_WriteValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).FullName)
|
||||
{
|
||||
m_BytePacker_WriteValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(int).FullName)
|
||||
{
|
||||
m_BytePacker_WriteValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(uint).FullName)
|
||||
{
|
||||
m_BytePacker_WriteValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(long).FullName)
|
||||
{
|
||||
m_BytePacker_WriteValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).FullName)
|
||||
{
|
||||
m_BytePacker_WriteValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var method in byteUnpackerTypeDef.Methods)
|
||||
{
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (method.Name)
|
||||
{
|
||||
case nameof(ByteUnpacker.ReadValueBitPacked):
|
||||
if (method.Parameters[1].ParameterType.FullName == typeof(short).MakeByRefType().FullName)
|
||||
{
|
||||
m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).MakeByRefType().FullName)
|
||||
{
|
||||
m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(int).MakeByRefType().FullName)
|
||||
{
|
||||
m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(uint).MakeByRefType().FullName)
|
||||
{
|
||||
m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(long).MakeByRefType().FullName)
|
||||
{
|
||||
m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).MakeByRefType().FullName)
|
||||
{
|
||||
m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This gets all fields from this type as well as any parent types, up to (but not including) the base NetworkBehaviour class
|
||||
// Importantly... this also resolves any generics, so if the base class is Foo<T> and contains a field of NetworkVariable<T>,
|
||||
// and this class is Bar : Foo<int>, it will properly resolve NetworkVariable<T> to NetworkVariable<int>.
|
||||
private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List<TypeReference> fieldTypes, Dictionary<string, TypeReference> genericParameters = null)
|
||||
{
|
||||
foreach (var field in type.Fields)
|
||||
{
|
||||
if (field.FieldType.IsGenericInstance)
|
||||
{
|
||||
var genericType = (GenericInstanceType)field.FieldType;
|
||||
var newGenericType = new GenericInstanceType(field.FieldType.Resolve());
|
||||
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
|
||||
{
|
||||
var argument = genericType.GenericArguments[i];
|
||||
|
||||
if (genericParameters != null && genericParameters.ContainsKey(argument.Name))
|
||||
{
|
||||
newGenericType.GenericArguments.Add(genericParameters[argument.Name]);
|
||||
}
|
||||
else
|
||||
{
|
||||
newGenericType.GenericArguments.Add(argument);
|
||||
}
|
||||
}
|
||||
fieldTypes.Add(newGenericType);
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldTypes.Add(field.FieldType);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.BaseType == null || type.BaseType.Name == nameof(NetworkBehaviour))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var genericParams = new Dictionary<string, TypeReference>();
|
||||
var resolved = type.BaseType.Resolve();
|
||||
if (type.BaseType.IsGenericInstance)
|
||||
{
|
||||
var genericType = (GenericInstanceType)type.BaseType;
|
||||
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
|
||||
{
|
||||
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
|
||||
}
|
||||
}
|
||||
GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams);
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines)
|
||||
{
|
||||
var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>();
|
||||
@@ -416,6 +879,28 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance)
|
||||
{
|
||||
var fieldTypes = new List<TypeReference>();
|
||||
GetAllFieldsAndResolveGenerics(typeDefinition, ref fieldTypes);
|
||||
foreach (var type in fieldTypes)
|
||||
{
|
||||
//var type = field.FieldType;
|
||||
if (type.IsGenericInstance)
|
||||
{
|
||||
if (type.Resolve().Name == typeof(NetworkVariable<>).Name || type.Resolve().Name == typeof(NetworkList<>).Name)
|
||||
{
|
||||
var genericInstanceType = (GenericInstanceType)type;
|
||||
var wrappedType = genericInstanceType.GenericArguments[0];
|
||||
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
|
||||
{
|
||||
m_WrappedNetworkVariableTypes.Add(wrappedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rpcHandlers.Count > 0 || rpcNames.Count > 0)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
@@ -656,6 +1141,36 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef)
|
||||
{
|
||||
if (paramType.FullName == typeof(short).FullName)
|
||||
{
|
||||
methodRef = m_BytePacker_WriteValueBitPacked_Short_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(ushort).FullName)
|
||||
{
|
||||
methodRef = m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(int).FullName)
|
||||
{
|
||||
methodRef = m_BytePacker_WriteValueBitPacked_Int_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(uint).FullName)
|
||||
{
|
||||
methodRef = m_BytePacker_WriteValueBitPacked_UInt_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(long).FullName)
|
||||
{
|
||||
methodRef = m_BytePacker_WriteValueBitPacked_Long_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(ulong).FullName)
|
||||
{
|
||||
methodRef = m_BytePacker_WriteValueBitPacked_ULong_MethodRef;
|
||||
return true;
|
||||
}
|
||||
var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName;
|
||||
var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef);
|
||||
|
||||
@@ -669,7 +1184,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
if (parameters[1].IsIn)
|
||||
{
|
||||
if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() &&
|
||||
if (((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName &&
|
||||
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
|
||||
{
|
||||
methodRef = method;
|
||||
@@ -679,8 +1194,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (parameters[1].ParameterType.Resolve() == paramType.Resolve() &&
|
||||
if (parameters[1].ParameterType.FullName == paramType.FullName &&
|
||||
parameters[1].ParameterType.IsArray == paramType.IsArray)
|
||||
{
|
||||
methodRef = method;
|
||||
@@ -803,6 +1317,36 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef)
|
||||
{
|
||||
if (paramType.FullName == typeof(short).FullName)
|
||||
{
|
||||
methodRef = m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(ushort).FullName)
|
||||
{
|
||||
methodRef = m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(int).FullName)
|
||||
{
|
||||
methodRef = m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(uint).FullName)
|
||||
{
|
||||
methodRef = m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(long).FullName)
|
||||
{
|
||||
methodRef = m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
|
||||
return true;
|
||||
}
|
||||
if (paramType.FullName == typeof(ulong).FullName)
|
||||
{
|
||||
methodRef = m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;
|
||||
return true;
|
||||
}
|
||||
var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName;
|
||||
|
||||
var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef);
|
||||
@@ -813,7 +1357,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
var parameters = method.Resolve().Parameters;
|
||||
if (method.Name == k_ReadValueMethodName &&
|
||||
parameters[1].IsOut &&
|
||||
parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() &&
|
||||
((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName &&
|
||||
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
|
||||
{
|
||||
methodRef = method;
|
||||
|
||||
3
Editor/Configuration.meta
Normal file
3
Editor/Configuration.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2f70916f7024c66aa5dfe1e43c151a2
|
||||
timeCreated: 1654274400
|
||||
53
Editor/Configuration/NetcodeForGameObjectsSettings.cs
Normal file
53
Editor/Configuration/NetcodeForGameObjectsSettings.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace Unity.Netcode.Editor.Configuration
|
||||
{
|
||||
internal class NetcodeForGameObjectsEditorSettings
|
||||
{
|
||||
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
|
||||
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
|
||||
|
||||
internal static int GetNetcodeInstallMultiplayerToolTips()
|
||||
{
|
||||
if (EditorPrefs.HasKey(InstallMultiplayerToolsTipDismissedPlayerPrefKey))
|
||||
{
|
||||
return EditorPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static void SetNetcodeInstallMultiplayerToolTips(int toolTipPrefSetting)
|
||||
{
|
||||
EditorPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, toolTipPrefSetting);
|
||||
}
|
||||
|
||||
internal static bool GetAutoAddNetworkObjectSetting()
|
||||
{
|
||||
if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists))
|
||||
{
|
||||
return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void SetAutoAddNetworkObjectSetting(bool autoAddSetting)
|
||||
{
|
||||
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting);
|
||||
}
|
||||
}
|
||||
|
||||
[FilePath("ProjectSettings/NetcodeForGameObjects.settings", FilePathAttribute.Location.ProjectFolder)]
|
||||
internal class NetcodeForGameObjectsProjectSettings : ScriptableSingleton<NetcodeForGameObjectsProjectSettings>
|
||||
{
|
||||
[SerializeField] public bool GenerateDefaultNetworkPrefabs = true;
|
||||
|
||||
internal void SaveSettings()
|
||||
{
|
||||
Save(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee
|
||||
guid: 2f9c9b10bc41a0e46ab71324dd0ac6e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
179
Editor/Configuration/NetcodeSettingsProvider.cs
Normal file
179
Editor/Configuration/NetcodeSettingsProvider.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor.Configuration
|
||||
{
|
||||
internal static class NetcodeSettingsProvider
|
||||
{
|
||||
private const float k_MaxLabelWidth = 450f;
|
||||
private static float s_MaxLabelWidth;
|
||||
private static bool s_ShowEditorSettingFields = true;
|
||||
private static bool s_ShowProjectSettingFields = true;
|
||||
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateNetcodeSettingsProvider()
|
||||
{
|
||||
// First parameter is the path in the Settings window.
|
||||
// Second parameter is the scope of this setting: it only appears in the Settings window for the Project scope.
|
||||
var provider = new SettingsProvider("Project/NetcodeForGameObjects", SettingsScope.Project)
|
||||
{
|
||||
label = "Netcode for GameObjects",
|
||||
keywords = new[] { "netcode", "editor" },
|
||||
guiHandler = OnGuiHandler,
|
||||
};
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
|
||||
internal static NetcodeSettingsLabel NetworkObjectsSectionLabel;
|
||||
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle;
|
||||
internal static NetcodeSettingsLabel MultiplayerToolsLabel;
|
||||
internal static NetcodeSettingsToggle MultiplayerToolTipStatusToggle;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the settings UI Elements if they don't already exist.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We have to construct any NetcodeGUISettings derived classes here because in
|
||||
/// version 2020.x.x EditorStyles.label does not exist yet (higher versions it does)
|
||||
/// </remarks>
|
||||
private static void CheckForInitialize()
|
||||
{
|
||||
if (NetworkObjectsSectionLabel == null)
|
||||
{
|
||||
NetworkObjectsSectionLabel = new NetcodeSettingsLabel("NetworkObject Helper Settings", 20);
|
||||
}
|
||||
|
||||
if (AutoAddNetworkObjectToggle == null)
|
||||
{
|
||||
AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObject Component", "When enabled, NetworkObject components are automatically added to GameObjects when NetworkBehaviour components are added first.", 20);
|
||||
}
|
||||
|
||||
if (MultiplayerToolsLabel == null)
|
||||
{
|
||||
MultiplayerToolsLabel = new NetcodeSettingsLabel("Multiplayer Tools", 20);
|
||||
}
|
||||
|
||||
if (MultiplayerToolTipStatusToggle == null)
|
||||
{
|
||||
MultiplayerToolTipStatusToggle = new NetcodeSettingsToggle("Multiplayer Tools Install Reminder", "When enabled, the NetworkManager will display the notification to install the multiplayer tools package.", 20);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnGuiHandler(string obj)
|
||||
{
|
||||
// Make sure all NetcodeGUISettings derived classes are instantiated first
|
||||
CheckForInitialize();
|
||||
|
||||
var autoAddNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting();
|
||||
var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
|
||||
var settings = NetcodeForGameObjectsProjectSettings.instance;
|
||||
var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
GUILayout.BeginVertical("Box");
|
||||
s_ShowEditorSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowEditorSettingFields, "Editor Settings");
|
||||
|
||||
if (s_ShowEditorSettingFields)
|
||||
{
|
||||
GUILayout.BeginVertical("Box");
|
||||
NetworkObjectsSectionLabel.DrawLabel();
|
||||
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.BeginVertical("Box");
|
||||
MultiplayerToolsLabel.DrawLabel();
|
||||
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.BeginVertical("Box");
|
||||
s_ShowProjectSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowProjectSettingFields, "Project Settings");
|
||||
if (s_ShowProjectSettingFields)
|
||||
{
|
||||
GUILayout.BeginVertical("Box");
|
||||
const string generateNetworkPrefabsString = "Generate Default Network Prefabs List";
|
||||
|
||||
if (s_MaxLabelWidth == 0)
|
||||
{
|
||||
s_MaxLabelWidth = EditorStyles.label.CalcSize(new GUIContent(generateNetworkPrefabsString)).x;
|
||||
s_MaxLabelWidth = Mathf.Min(k_MaxLabelWidth, s_MaxLabelWidth);
|
||||
}
|
||||
|
||||
EditorGUIUtility.labelWidth = s_MaxLabelWidth;
|
||||
|
||||
GUILayout.Label("Network Prefabs", EditorStyles.boldLabel);
|
||||
generateDefaultPrefabs = EditorGUILayout.Toggle(
|
||||
new GUIContent(
|
||||
generateNetworkPrefabsString,
|
||||
"When enabled, a default NetworkPrefabsList object will be added to your project and kept up " +
|
||||
"to date with all NetworkObject prefabs."),
|
||||
generateDefaultPrefabs,
|
||||
GUILayout.Width(s_MaxLabelWidth + 20));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
GUILayout.EndVertical();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
|
||||
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
|
||||
settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs;
|
||||
settings.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetcodeSettingsLabel : NetcodeGUISettings
|
||||
{
|
||||
private string m_LabelContent;
|
||||
|
||||
public void DrawLabel()
|
||||
{
|
||||
EditorGUIUtility.labelWidth = m_LabelSize;
|
||||
GUILayout.Label(m_LabelContent, EditorStyles.boldLabel, m_LayoutWidth);
|
||||
}
|
||||
|
||||
public NetcodeSettingsLabel(string labelText, float layoutOffset = 0.0f)
|
||||
{
|
||||
m_LabelContent = labelText;
|
||||
AdjustLabelSize(labelText, layoutOffset);
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetcodeSettingsToggle : NetcodeGUISettings
|
||||
{
|
||||
private GUIContent m_ToggleContent;
|
||||
|
||||
public bool DrawToggle(bool currentSetting)
|
||||
{
|
||||
EditorGUIUtility.labelWidth = m_LabelSize;
|
||||
return EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth);
|
||||
}
|
||||
|
||||
public NetcodeSettingsToggle(string labelText, string toolTip, float layoutOffset)
|
||||
{
|
||||
AdjustLabelSize(labelText, layoutOffset);
|
||||
m_ToggleContent = new GUIContent(labelText, toolTip);
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetcodeGUISettings
|
||||
{
|
||||
private const float k_MaxLabelWidth = 450f;
|
||||
protected float m_LabelSize { get; private set; }
|
||||
|
||||
protected GUILayoutOption m_LayoutWidth { get; private set; }
|
||||
|
||||
protected void AdjustLabelSize(string labelText, float offset = 0.0f)
|
||||
{
|
||||
m_LabelSize = Mathf.Min(k_MaxLabelWidth, EditorStyles.label.CalcSize(new GUIContent(labelText)).x);
|
||||
m_LayoutWidth = GUILayout.Width(m_LabelSize + offset);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
11
Editor/Configuration/NetcodeSettingsProvider.cs.meta
Normal file
11
Editor/Configuration/NetcodeSettingsProvider.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b373a89fcbd41444a97ebd1798b326f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
187
Editor/Configuration/NetworkPrefabProcessor.cs
Normal file
187
Editor/Configuration/NetworkPrefabProcessor.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the default <see cref="NetworkPrefabsList"/> instance when prefabs are updated (created, moved, deleted) in the project.
|
||||
/// </summary>
|
||||
public class NetworkPrefabProcessor : AssetPostprocessor
|
||||
{
|
||||
private static string s_DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
|
||||
public static string DefaultNetworkPrefabsPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return s_DefaultNetworkPrefabsPath;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
s_DefaultNetworkPrefabsPath = value;
|
||||
// Force a recache of the prefab list
|
||||
s_PrefabsList = null;
|
||||
}
|
||||
}
|
||||
private static NetworkPrefabsList s_PrefabsList;
|
||||
private static Dictionary<string, NetworkPrefab> s_PrefabsListPath = new Dictionary<string, NetworkPrefab>();
|
||||
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
var settings = NetcodeForGameObjectsProjectSettings.instance;
|
||||
if (!settings.GenerateDefaultNetworkPrefabs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool ProcessImportedAssets(string[] importedAssets1)
|
||||
{
|
||||
var dirty = false;
|
||||
foreach (var assetPath in importedAssets1)
|
||||
{
|
||||
// We only care about GameObjects, skip everything else. Can't use the more targeted
|
||||
// OnPostProcessPrefabs since that's not called for moves or deletes
|
||||
if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) != typeof(GameObject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||||
if (go.TryGetComponent<NetworkObject>(out _))
|
||||
{
|
||||
// Make sure we are not duplicating an already existing entry
|
||||
if (s_PrefabsListPath.ContainsKey(assetPath))
|
||||
{
|
||||
// Is the imported asset different from the one we already have in the list?
|
||||
if (s_PrefabsListPath[assetPath].Prefab.GetHashCode() != go.GetHashCode())
|
||||
{
|
||||
// If so remove the one in the list and continue on to add the imported one
|
||||
s_PrefabsList.List.Remove(s_PrefabsListPath[assetPath]);
|
||||
}
|
||||
else // If they are identical, then just ignore the import
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
s_PrefabsList.List.Add(new NetworkPrefab { Prefab = go });
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
bool ProcessDeletedAssets(string[] strings)
|
||||
{
|
||||
var dirty = false;
|
||||
var deleted = new List<string>(strings);
|
||||
for (int i = s_PrefabsList.List.Count - 1; i >= 0 && deleted.Count > 0; --i)
|
||||
{
|
||||
GameObject prefab;
|
||||
try
|
||||
{
|
||||
prefab = s_PrefabsList.List[i].Prefab;
|
||||
}
|
||||
catch (MissingReferenceException)
|
||||
{
|
||||
s_PrefabsList.List.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
if (prefab == null)
|
||||
{
|
||||
s_PrefabsList.List.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
string noPath = AssetDatabase.GetAssetPath(prefab);
|
||||
for (int j = strings.Length - 1; j >= 0; --j)
|
||||
{
|
||||
if (noPath == strings[j])
|
||||
{
|
||||
s_PrefabsList.List.RemoveAt(i);
|
||||
deleted.RemoveAt(j);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
if (s_PrefabsList == null)
|
||||
{
|
||||
s_PrefabsList = GetOrCreateNetworkPrefabs(DefaultNetworkPrefabsPath, out var newList, true);
|
||||
// A new list already processed all existing assets, no need to double-process imports & deletes
|
||||
if (newList)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Clear our asset path to prefab table each time
|
||||
s_PrefabsListPath.Clear();
|
||||
|
||||
// Create our asst path to prefab table
|
||||
foreach (var prefabEntry in s_PrefabsList.List)
|
||||
{
|
||||
if (!s_PrefabsListPath.ContainsKey(AssetDatabase.GetAssetPath(prefabEntry.Prefab)))
|
||||
{
|
||||
s_PrefabsListPath.Add(AssetDatabase.GetAssetPath(prefabEntry.Prefab), prefabEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Process the imported and deleted assets
|
||||
var markDirty = ProcessImportedAssets(importedAssets);
|
||||
markDirty &= ProcessDeletedAssets(deletedAssets);
|
||||
|
||||
if (markDirty)
|
||||
{
|
||||
EditorUtility.SetDirty(s_PrefabsList);
|
||||
}
|
||||
}
|
||||
|
||||
internal static NetworkPrefabsList GetOrCreateNetworkPrefabs(string path, out bool isNew, bool addAll)
|
||||
{
|
||||
var defaultPrefabs = AssetDatabase.LoadAssetAtPath<NetworkPrefabsList>(path);
|
||||
if (defaultPrefabs == null)
|
||||
{
|
||||
isNew = true;
|
||||
defaultPrefabs = ScriptableObject.CreateInstance<NetworkPrefabsList>();
|
||||
defaultPrefabs.IsDefault = true;
|
||||
AssetDatabase.CreateAsset(defaultPrefabs, path);
|
||||
|
||||
if (addAll)
|
||||
{
|
||||
// This could be very expensive in large projects... maybe make it manually triggered via a menu?
|
||||
defaultPrefabs.List = FindAll();
|
||||
}
|
||||
EditorUtility.SetDirty(defaultPrefabs);
|
||||
AssetDatabase.SaveAssetIfDirty(defaultPrefabs);
|
||||
return defaultPrefabs;
|
||||
}
|
||||
|
||||
isNew = false;
|
||||
return defaultPrefabs;
|
||||
}
|
||||
|
||||
private static List<NetworkPrefab> FindAll()
|
||||
{
|
||||
var list = new List<NetworkPrefab>();
|
||||
|
||||
string[] guids = AssetDatabase.FindAssets("t:GameObject");
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||||
|
||||
if (go.TryGetComponent(out NetworkObject _))
|
||||
{
|
||||
list.Add(new NetworkPrefab { Prefab = go });
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Configuration/NetworkPrefabProcessor.cs.meta
Normal file
11
Editor/Configuration/NetworkPrefabProcessor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8b62a05d80cc444f9c74731c01b8e39
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
97
Editor/Configuration/NetworkPrefabsEditor.cs
Normal file
97
Editor/Configuration/NetworkPrefabsEditor.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkPrefabsList), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkPrefabsEditor : UnityEditor.Editor
|
||||
{
|
||||
private ReorderableList m_NetworkPrefabsList;
|
||||
private SerializedProperty m_IsDefaultBool;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_IsDefaultBool = serializedObject.FindProperty(nameof(NetworkPrefabsList.IsDefault));
|
||||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty("List"), true, true, true, true);
|
||||
m_NetworkPrefabsList.elementHeightCallback = index =>
|
||||
{
|
||||
var networkOverrideInt = 0;
|
||||
if (m_NetworkPrefabsList.count > 0)
|
||||
{
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
}
|
||||
|
||||
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
|
||||
};
|
||||
m_NetworkPrefabsList.drawElementCallback = (rect, index, isActive, isFocused) =>
|
||||
{
|
||||
rect.y += 5;
|
||||
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Prefab));
|
||||
var networkSourceHashProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourceHashToOverride));
|
||||
var networkSourcePrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourcePrefabToOverride));
|
||||
var networkTargetPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.OverridingTargetPrefab));
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
var networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
var networkOverrideEnum = (NetworkPrefabOverride)networkOverrideInt;
|
||||
EditorGUI.LabelField(new Rect(rect.x + rect.width - 70, rect.y, 60, EditorGUIUtility.singleLineHeight), "Override");
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.None)
|
||||
{
|
||||
if (EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), false))
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = (int)NetworkPrefabOverride.Prefab;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), true))
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = 0;
|
||||
networkOverrideEnum = NetworkPrefabOverride.None;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.None)
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 80, EditorGUIUtility.singleLineHeight), networkPrefabProp, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = GUI.Toolbar(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), networkOverrideInt - 1, new[] { "Prefab", "Hash" }) + 1;
|
||||
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.Prefab)
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourcePrefabProp, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourceHashProp, GUIContent.none);
|
||||
}
|
||||
|
||||
rect.y += EditorGUIUtility.singleLineHeight + 5;
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), "Overriding Prefab");
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 110, EditorGUIUtility.singleLineHeight), networkTargetPrefabProp, GUIContent.none);
|
||||
}
|
||||
};
|
||||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_IsDefaultBool);
|
||||
}
|
||||
|
||||
m_NetworkPrefabsList.DoLayoutList();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Configuration/NetworkPrefabsEditor.cs.meta
Normal file
11
Editor/Configuration/NetworkPrefabsEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d6d0919fa8ff41c9b1d1241256f7364
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
184
Editor/HiddenScriptEditor.cs
Normal file
184
Editor/HiddenScriptEditor.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using Unity.Netcode.Components;
|
||||
#if UNITY_UNET_PRESENT
|
||||
using Unity.Netcode.Transports.UNET;
|
||||
#endif
|
||||
using Unity.Netcode.Transports.UTP;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for the given component.
|
||||
/// </summary>
|
||||
public class HiddenScriptEditor : UnityEditor.Editor
|
||||
{
|
||||
private static readonly string[] k_HiddenFields = { "m_Script" };
|
||||
|
||||
/// <summary>
|
||||
/// Draws inspector properties without the script field.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
}
|
||||
#if UNITY_UNET_PRESENT
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for UNetTransport.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(UNetTransport), true)]
|
||||
public class UNetTransportEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for UnityTransport.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(UnityTransport), true)]
|
||||
public class UnityTransportEditor : HiddenScriptEditor
|
||||
{
|
||||
private static readonly string[] k_HiddenFields = { "m_Script", "ConnectionData" };
|
||||
|
||||
private bool m_AllowIncomingConnections;
|
||||
private bool m_Initialized;
|
||||
|
||||
private UnityTransport m_UnityTransport;
|
||||
|
||||
private SerializedProperty m_ServerAddressProperty;
|
||||
private SerializedProperty m_ServerPortProperty;
|
||||
private SerializedProperty m_OverrideBindIpProperty;
|
||||
|
||||
private const string k_LoopbackIpv4 = "127.0.0.1";
|
||||
private const string k_LoopbackIpv6 = "::1";
|
||||
private const string k_AnyIpv4 = "0.0.0.0";
|
||||
private const string k_AnyIpv6 = "::";
|
||||
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (m_Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_Initialized = true;
|
||||
m_UnityTransport = (UnityTransport)target;
|
||||
|
||||
var connectionDataProperty = serializedObject.FindProperty(nameof(UnityTransport.ConnectionData));
|
||||
|
||||
m_ServerAddressProperty = connectionDataProperty.FindPropertyRelative(nameof(UnityTransport.ConnectionAddressData.Address));
|
||||
m_ServerPortProperty = connectionDataProperty.FindPropertyRelative(nameof(UnityTransport.ConnectionAddressData.Port));
|
||||
m_OverrideBindIpProperty = connectionDataProperty.FindPropertyRelative(nameof(UnityTransport.ConnectionAddressData.ServerListenAddress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws inspector properties without the script field.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(m_ServerAddressProperty);
|
||||
EditorGUILayout.PropertyField(m_ServerPortProperty);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
EditorGUILayout.HelpBox("It's recommended to leave remote connections disabled for local testing to avoid exposing ports on your device.", MessageType.Info);
|
||||
bool allowRemoteConnections = m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv4 && m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv6 && !string.IsNullOrEmpty(m_UnityTransport.ConnectionData.ServerListenAddress);
|
||||
allowRemoteConnections = EditorGUILayout.Toggle(new GUIContent("Allow Remote Connections?", $"Bind IP: {m_UnityTransport.ConnectionData.ServerListenAddress}"), allowRemoteConnections);
|
||||
|
||||
bool isIpV6 = m_UnityTransport.ConnectionData.IsIpv6;
|
||||
|
||||
if (!allowRemoteConnections)
|
||||
{
|
||||
if (m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv4 && m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv6)
|
||||
{
|
||||
if (isIpV6)
|
||||
{
|
||||
m_UnityTransport.ConnectionData.ServerListenAddress = k_LoopbackIpv6;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_UnityTransport.ConnectionData.ServerListenAddress = k_LoopbackIpv4;
|
||||
}
|
||||
EditorUtility.SetDirty(m_UnityTransport);
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(!allowRemoteConnections))
|
||||
{
|
||||
string overrideIp = m_UnityTransport.ConnectionData.ServerListenAddress;
|
||||
if (overrideIp == k_AnyIpv4 || overrideIp == k_AnyIpv6 || overrideIp == k_LoopbackIpv4 || overrideIp == k_LoopbackIpv6)
|
||||
{
|
||||
overrideIp = "";
|
||||
}
|
||||
|
||||
overrideIp = EditorGUILayout.TextField("Override Bind IP (optional)", overrideIp);
|
||||
if (allowRemoteConnections)
|
||||
{
|
||||
if (overrideIp == "")
|
||||
{
|
||||
if (isIpV6)
|
||||
{
|
||||
overrideIp = k_AnyIpv6;
|
||||
}
|
||||
else
|
||||
{
|
||||
overrideIp = k_AnyIpv4;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_UnityTransport.ConnectionData.ServerListenAddress != overrideIp)
|
||||
{
|
||||
m_UnityTransport.ConnectionData.ServerListenAddress = overrideIp;
|
||||
EditorUtility.SetDirty(m_UnityTransport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if COM_UNITY_MODULES_ANIMATION
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for NetworkAnimator.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
||||
public class NetworkAnimatorEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for NetworkRigidbody.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkRigidbody), true)]
|
||||
public class NetworkRigidbodyEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for NetworkRigidbody2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkRigidbody2D), true)]
|
||||
public class NetworkRigidbody2DEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
3
Editor/HiddenScriptEditor.cs.meta
Normal file
3
Editor/HiddenScriptEditor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebf622cc80e94f488e59caf8b7419f50
|
||||
timeCreated: 1661959406
|
||||
@@ -3,9 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using Unity.Netcode.Editor.Configuration;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CustomEditor"/> for <see cref="NetworkBehaviour"/>
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkBehaviour), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkBehaviourEditor : UnityEditor.Editor
|
||||
@@ -16,6 +20,7 @@ namespace Unity.Netcode.Editor
|
||||
private readonly Dictionary<string, object> m_NetworkVariableObjects = new Dictionary<string, object>();
|
||||
|
||||
private GUIContent m_NetworkVariableLabelGuiContent;
|
||||
private GUIContent m_NetworkListLabelGuiContent;
|
||||
|
||||
private void Init(MonoScript script)
|
||||
{
|
||||
@@ -26,6 +31,7 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkVariableObjects.Clear();
|
||||
|
||||
m_NetworkVariableLabelGuiContent = new GUIContent("NetworkVariable", "This variable is a NetworkVariable. It can not be serialized and can only be changed during runtime.");
|
||||
m_NetworkListLabelGuiContent = new GUIContent("NetworkList", "This variable is a NetworkList. It is rendered, but you can't serialize or change it.");
|
||||
|
||||
var fields = script.GetClass().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
@@ -33,8 +39,15 @@ namespace Unity.Netcode.Editor
|
||||
var ft = fields[i].FieldType;
|
||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
|
||||
{
|
||||
m_NetworkVariableNames.Add(fields[i].Name);
|
||||
m_NetworkVariableFields.Add(fields[i].Name, fields[i]);
|
||||
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
|
||||
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
|
||||
Debug.Log($"Adding NetworkVariable {fields[i].Name}");
|
||||
}
|
||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
|
||||
{
|
||||
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
|
||||
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
|
||||
Debug.Log($"Adding NetworkList {fields[i].Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,25 +81,48 @@ namespace Unity.Netcode.Editor
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (genericType.IsValueType)
|
||||
{
|
||||
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkVariableValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
|
||||
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkContainerValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
|
||||
var genericMethod = method.MakeGenericMethod(genericType);
|
||||
genericMethod.Invoke(this, new[] { (object)index });
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("Type not renderable");
|
||||
|
||||
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderNetworkContainerValueType<T>(int index) where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
try
|
||||
{
|
||||
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
RenderNetworkVariableValueType(index, networkVariable);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
var networkList = (NetworkList<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
RenderNetworkListValueType(index, networkList);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void RenderNetworkVariableValueType<T>(int index) where T : unmanaged
|
||||
private void RenderNetworkVariableValueType<T>(int index, NetworkVariable<T> networkVariable) where T : unmanaged
|
||||
{
|
||||
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
var type = typeof(T);
|
||||
object val = networkVariable.Value;
|
||||
string name = m_NetworkVariableNames[index];
|
||||
string variableName = m_NetworkVariableNames[index];
|
||||
|
||||
var behaviour = (NetworkBehaviour)target;
|
||||
|
||||
@@ -95,47 +131,47 @@ namespace Unity.Netcode.Editor
|
||||
{
|
||||
if (type == typeof(int))
|
||||
{
|
||||
val = EditorGUILayout.IntField(name, (int)val);
|
||||
val = EditorGUILayout.IntField(variableName, (int)val);
|
||||
}
|
||||
else if (type == typeof(uint))
|
||||
{
|
||||
val = (uint)EditorGUILayout.LongField(name, (long)((uint)val));
|
||||
val = (uint)EditorGUILayout.LongField(variableName, (long)((uint)val));
|
||||
}
|
||||
else if (type == typeof(short))
|
||||
{
|
||||
val = (short)EditorGUILayout.IntField(name, (int)((short)val));
|
||||
val = (short)EditorGUILayout.IntField(variableName, (int)((short)val));
|
||||
}
|
||||
else if (type == typeof(ushort))
|
||||
{
|
||||
val = (ushort)EditorGUILayout.IntField(name, (int)((ushort)val));
|
||||
val = (ushort)EditorGUILayout.IntField(variableName, (int)((ushort)val));
|
||||
}
|
||||
else if (type == typeof(sbyte))
|
||||
{
|
||||
val = (sbyte)EditorGUILayout.IntField(name, (int)((sbyte)val));
|
||||
val = (sbyte)EditorGUILayout.IntField(variableName, (int)((sbyte)val));
|
||||
}
|
||||
else if (type == typeof(byte))
|
||||
{
|
||||
val = (byte)EditorGUILayout.IntField(name, (int)((byte)val));
|
||||
val = (byte)EditorGUILayout.IntField(variableName, (int)((byte)val));
|
||||
}
|
||||
else if (type == typeof(long))
|
||||
{
|
||||
val = EditorGUILayout.LongField(name, (long)val);
|
||||
val = EditorGUILayout.LongField(variableName, (long)val);
|
||||
}
|
||||
else if (type == typeof(ulong))
|
||||
{
|
||||
val = (ulong)EditorGUILayout.LongField(name, (long)((ulong)val));
|
||||
val = (ulong)EditorGUILayout.LongField(variableName, (long)((ulong)val));
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
val = EditorGUILayout.Toggle(name, (bool)val);
|
||||
val = EditorGUILayout.Toggle(variableName, (bool)val);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
val = EditorGUILayout.TextField(name, (string)val);
|
||||
val = EditorGUILayout.TextField(variableName, (string)val);
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
val = EditorGUILayout.EnumPopup(name, (Enum)val);
|
||||
val = EditorGUILayout.EnumPopup(variableName, (Enum)val);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -146,11 +182,31 @@ namespace Unity.Netcode.Editor
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(name, EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.LabelField(variableName, EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.SelectableLabel(val.ToString(), EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
|
||||
}
|
||||
|
||||
private void RenderNetworkListValueType<T>(int index, NetworkList<T> networkList)
|
||||
where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
string variableName = m_NetworkVariableNames[index];
|
||||
|
||||
string value = "";
|
||||
bool addComma = false;
|
||||
foreach (var v in networkList)
|
||||
{
|
||||
if (addComma)
|
||||
{
|
||||
value += ", ";
|
||||
}
|
||||
value += v.ToString();
|
||||
addComma = true;
|
||||
}
|
||||
EditorGUILayout.LabelField(variableName, value);
|
||||
GUILayout.Label(m_NetworkListLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkListLabelGuiContent).x));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
@@ -230,8 +286,6 @@ namespace Unity.Netcode.Editor
|
||||
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
|
||||
}
|
||||
|
||||
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
|
||||
|
||||
/// <summary>
|
||||
/// Recursively finds the root parent of a <see cref="Transform"/>
|
||||
/// </summary>
|
||||
@@ -263,8 +317,7 @@ namespace Unity.Netcode.Editor
|
||||
|
||||
// Now get the root parent transform to the current GameObject (or itself)
|
||||
var rootTransform = GetRootParentTransform(gameObject.transform);
|
||||
var networkManager = rootTransform.GetComponent<NetworkManager>();
|
||||
if (networkManager == null)
|
||||
if (!rootTransform.TryGetComponent<NetworkManager>(out var networkManager))
|
||||
{
|
||||
networkManager = rootTransform.GetComponentInChildren<NetworkManager>();
|
||||
}
|
||||
@@ -299,8 +352,7 @@ namespace Unity.Netcode.Editor
|
||||
|
||||
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
|
||||
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
|
||||
var networkObject = rootTransform.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
if (!rootTransform.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
|
||||
|
||||
@@ -310,7 +362,7 @@ namespace Unity.Netcode.Editor
|
||||
// and the user has already turned "Auto-Add NetworkObject" on when first notified about the requirement
|
||||
// then just send a reminder to the user why the NetworkObject they just deleted seemingly "re-appeared"
|
||||
// again.
|
||||
if (networkObjectRemoved && EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists) && EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists))
|
||||
if (networkObjectRemoved && NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting())
|
||||
{
|
||||
Debug.LogWarning($"{gameObject.name} still has {nameof(NetworkBehaviour)}s and Auto-Add NetworkObjects is enabled. A NetworkObject is being added back to {gameObject.name}.");
|
||||
Debug.Log($"To reset Auto-Add NetworkObjects: Select the Netcode->General->Reset Auto-Add NetworkObject menu item.");
|
||||
@@ -319,7 +371,7 @@ namespace Unity.Netcode.Editor
|
||||
// Notify and provide the option to add it one time, always add a NetworkObject, or do nothing and let the user manually add it
|
||||
if (EditorUtility.DisplayDialog($"{nameof(NetworkBehaviour)}s require a {nameof(NetworkObject)}",
|
||||
$"{gameObject.name} does not have a {nameof(NetworkObject)} component. Would you like to add one now?", "Yes", "No (manually add it)",
|
||||
DialogOptOutDecisionType.ForThisMachine, AutoAddNetworkObjectIfNoneExists))
|
||||
DialogOptOutDecisionType.ForThisMachine, NetcodeForGameObjectsEditorSettings.AutoAddNetworkObjectIfNoneExists))
|
||||
{
|
||||
gameObject.AddComponent<NetworkObject>();
|
||||
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
@@ -329,20 +381,5 @@ namespace Unity.Netcode.Editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This allows users to reset the Auto-Add NetworkObject preference
|
||||
/// so the next time they add a NetworkBehaviour to a GameObject without
|
||||
/// a NetworkObject it will display the dialog box again and not
|
||||
/// automatically add a NetworkObject.
|
||||
/// </summary>
|
||||
[MenuItem("Netcode/General/Reset Auto-Add NetworkObject", false, 1)]
|
||||
private static void ResetMultiplayerToolsTipStatus()
|
||||
{
|
||||
if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists))
|
||||
{
|
||||
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
using Unity.Netcode.Editor.Configuration;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
@@ -14,7 +15,6 @@ namespace Unity.Netcode.Editor
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
|
||||
private static GUIStyle s_CenteredWordWrappedLabelStyle;
|
||||
private static GUIStyle s_HelpBoxStyle;
|
||||
|
||||
@@ -40,8 +40,7 @@ namespace Unity.Netcode.Editor
|
||||
private SerializedProperty m_NetworkIdRecycleDelayProperty;
|
||||
private SerializedProperty m_RpcHashSizeProperty;
|
||||
private SerializedProperty m_LoadSceneTimeOutProperty;
|
||||
|
||||
private ReorderableList m_NetworkPrefabsList;
|
||||
private SerializedProperty m_PrefabsList;
|
||||
|
||||
private NetworkManager m_NetworkManager;
|
||||
private bool m_Initialized;
|
||||
@@ -106,7 +105,9 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
|
||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
|
||||
|
||||
m_PrefabsList = m_NetworkConfigProperty
|
||||
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
|
||||
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
|
||||
|
||||
ReloadTransports();
|
||||
}
|
||||
@@ -132,76 +133,9 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
|
||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true);
|
||||
m_NetworkPrefabsList.elementHeightCallback = index =>
|
||||
{
|
||||
var networkOverrideInt = 0;
|
||||
if (m_NetworkPrefabsList.count > 0)
|
||||
{
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
}
|
||||
|
||||
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
|
||||
};
|
||||
m_NetworkPrefabsList.drawElementCallback = (rect, index, isActive, isFocused) =>
|
||||
{
|
||||
rect.y += 5;
|
||||
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Prefab));
|
||||
var networkSourceHashProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourceHashToOverride));
|
||||
var networkSourcePrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourcePrefabToOverride));
|
||||
var networkTargetPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.OverridingTargetPrefab));
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
var networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
var networkOverrideEnum = (NetworkPrefabOverride)networkOverrideInt;
|
||||
EditorGUI.LabelField(new Rect(rect.x + rect.width - 70, rect.y, 60, EditorGUIUtility.singleLineHeight), "Override");
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.None)
|
||||
{
|
||||
if (EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), false))
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = (int)NetworkPrefabOverride.Prefab;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), true))
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = 0;
|
||||
networkOverrideEnum = NetworkPrefabOverride.None;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.None)
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 80, EditorGUIUtility.singleLineHeight), networkPrefabProp, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = GUI.Toolbar(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), networkOverrideInt - 1, new[] { "Prefab", "Hash" }) + 1;
|
||||
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.Prefab)
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourcePrefabProp, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourceHashProp, GUIContent.none);
|
||||
}
|
||||
|
||||
rect.y += EditorGUIUtility.singleLineHeight + 5;
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), "Overriding Prefab");
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 110, EditorGUIUtility.singleLineHeight), networkTargetPrefabProp, GUIContent.none);
|
||||
}
|
||||
};
|
||||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
|
||||
m_PrefabsList = m_NetworkConfigProperty
|
||||
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
|
||||
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -214,18 +148,6 @@ namespace Unity.Netcode.Editor
|
||||
DrawInstallMultiplayerToolsTip();
|
||||
#endif
|
||||
|
||||
{
|
||||
var iterator = serializedObject.GetIterator();
|
||||
|
||||
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
|
||||
{
|
||||
EditorGUILayout.PropertyField(iterator, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||
{
|
||||
serializedObject.Update();
|
||||
@@ -236,7 +158,62 @@ namespace Unity.Netcode.Editor
|
||||
EditorGUILayout.PropertyField(m_PlayerPrefabProperty);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
m_NetworkPrefabsList.DoLayoutList();
|
||||
if (m_NetworkManager.NetworkConfig.HasOldPrefabList())
|
||||
{
|
||||
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
|
||||
if (GUILayout.Button(new GUIContent("Migrate Prefab List", "Converts the old format Network Prefab list to a new Scriptable Object")))
|
||||
{
|
||||
// Default directory
|
||||
var directory = "Assets/";
|
||||
var assetPath = AssetDatabase.GetAssetPath(m_NetworkManager);
|
||||
if (assetPath == "")
|
||||
{
|
||||
assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(m_NetworkManager);
|
||||
}
|
||||
|
||||
if (assetPath != "")
|
||||
{
|
||||
directory = Path.GetDirectoryName(assetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
|
||||
#else
|
||||
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
|
||||
#endif
|
||||
if (prefabStage != null)
|
||||
{
|
||||
var prefabPath = prefabStage.assetPath;
|
||||
if (!string.IsNullOrEmpty(prefabPath))
|
||||
{
|
||||
directory = Path.GetDirectoryName(prefabPath);
|
||||
}
|
||||
}
|
||||
if (m_NetworkManager.gameObject.scene != null)
|
||||
{
|
||||
var scenePath = m_NetworkManager.gameObject.scene.path;
|
||||
if (!string.IsNullOrEmpty(scenePath))
|
||||
{
|
||||
directory = Path.GetDirectoryName(scenePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
var networkPrefabs = m_NetworkManager.NetworkConfig.MigrateOldNetworkPrefabsToNetworkPrefabsList();
|
||||
string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset");
|
||||
Debug.Log("Saving migrated Network Prefabs List to " + path);
|
||||
AssetDatabase.CreateAsset(networkPrefabs, path);
|
||||
EditorUtility.SetDirty(m_NetworkManager);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning);
|
||||
}
|
||||
EditorGUILayout.PropertyField(m_PrefabsList);
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
|
||||
@@ -371,7 +348,7 @@ namespace Unity.Netcode.Editor
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
|
||||
const string infoIconName = "console.infoicon";
|
||||
|
||||
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
||||
if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -417,7 +394,7 @@ namespace Unity.Netcode.Editor
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(dismissButtonText, dismissButtonStyle, GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
PlayerPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 1);
|
||||
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(1);
|
||||
}
|
||||
EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Netcode.Editor.Configuration;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEditor;
|
||||
@@ -32,6 +33,24 @@ namespace Unity.Netcode.Editor
|
||||
|
||||
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
|
||||
|
||||
// Initialize default values for new NetworkManagers
|
||||
//
|
||||
// When the default prefab list is enabled, this will default
|
||||
// new NetworkManagers to using it.
|
||||
//
|
||||
// This will get run when new NetworkManagers are added, and
|
||||
// when the user presses the "reset" button on a NetworkManager
|
||||
// in the inspector.
|
||||
NetworkManager.OnNetworkManagerReset = manager =>
|
||||
{
|
||||
var settings = NetcodeForGameObjectsProjectSettings.instance;
|
||||
if (settings.GenerateDefaultNetworkPrefabs)
|
||||
{
|
||||
manager.NetworkConfig = new NetworkConfig();
|
||||
manager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { NetworkPrefabProcessor.GetOrCreateNetworkPrefabs(NetworkPrefabProcessor.DefaultNetworkPrefabsPath, out _, true) };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
|
||||
@@ -64,8 +83,12 @@ namespace Unity.Netcode.Editor
|
||||
{
|
||||
var scenesList = EditorBuildSettings.scenes.ToList();
|
||||
var activeScene = SceneManager.GetActiveScene();
|
||||
var isSceneInBuildSettings = scenesList.Where((c) => c.path == activeScene.path).Count() == 1;
|
||||
var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1;
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkManager = Object.FindFirstObjectByType<NetworkManager>();
|
||||
#else
|
||||
var networkManager = Object.FindObjectOfType<NetworkManager>();
|
||||
#endif
|
||||
if (!isSceneInBuildSettings && networkManager != null)
|
||||
{
|
||||
if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement)
|
||||
@@ -109,9 +132,8 @@ namespace Unity.Netcode.Editor
|
||||
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
|
||||
{
|
||||
// Check for any NetworkObject at the same gameObject relative layer
|
||||
var networkObject = networkManager.gameObject.GetComponent<NetworkObject>();
|
||||
|
||||
if (networkObject == null)
|
||||
if (!networkManager.gameObject.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
// if none is found, check to see if any children have a NetworkObject
|
||||
networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>();
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Unity.Netcode.Editor
|
||||
private NetworkObject m_NetworkObject;
|
||||
private bool m_ShowObservers;
|
||||
|
||||
private static readonly string[] k_HiddenFields = { "m_Script" };
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (m_Initialized)
|
||||
@@ -95,7 +97,11 @@ namespace Unity.Netcode.Editor
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
|
||||
var guiEnabled = GUI.enabled;
|
||||
GUI.enabled = false;
|
||||
|
||||
@@ -13,6 +13,26 @@
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
"expression": "",
|
||||
"define": "MULTIPLAYER_TOOLS"
|
||||
},
|
||||
{
|
||||
"name": "Unity",
|
||||
"expression": "(0,2022.2.0a5)",
|
||||
"define": "UNITY_UNET_PRESENT"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.animation",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_ANIMATION"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics2d",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS2D"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Netcode for GameObjects is a Unity package that provides networking capabilities
|
||||
|
||||
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
|
||||
|
||||
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks.
|
||||
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld) guide for a taste of how to use the framework for basic networked tasks.
|
||||
|
||||
### Community and Feedback
|
||||
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
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("Unity.Netcode.Editor.CodeGen")]
|
||||
#endif
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
#endif // UNITY_EDITOR
|
||||
#if MULTIPLAYER_TOOLS
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")]
|
||||
#endif // MULTIPLAYER_TOOLS
|
||||
#if COM_UNITY_NETCODE_ADAPTER_UTP
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
|
||||
#endif // COM_UNITY_NETCODE_ADAPTER_UTP
|
||||
|
||||
#if UNITY_INCLUDE_TESTS
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
#endif // UNITY_EDITOR
|
||||
#if MULTIPLAYER_TOOLS
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
#endif // MULTIPLAYER_TOOLS
|
||||
#endif // UNITY_INCLUDE_TESTS
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -30,20 +31,8 @@ namespace Unity.Netcode
|
||||
[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>();
|
||||
public NetworkPrefabs Prefabs = new NetworkPrefabs();
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -239,7 +228,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (ForceSamePrefabs)
|
||||
{
|
||||
var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
|
||||
var sortedDictionary = Prefabs.NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
|
||||
foreach (var sortedEntry in sortedDictionary)
|
||||
|
||||
{
|
||||
@@ -273,6 +262,79 @@ namespace Unity.Netcode
|
||||
{
|
||||
return hash == GetConfig();
|
||||
}
|
||||
|
||||
internal void InitializePrefabs()
|
||||
{
|
||||
if (HasOldPrefabList())
|
||||
{
|
||||
MigrateOldNetworkPrefabsToNetworkPrefabsList();
|
||||
}
|
||||
|
||||
Prefabs.Initialize();
|
||||
}
|
||||
|
||||
#region Legacy Network Prefab List
|
||||
|
||||
[NonSerialized]
|
||||
private bool m_DidWarnOldPrefabList = false;
|
||||
|
||||
private void WarnOldPrefabList()
|
||||
{
|
||||
if (!m_DidWarnOldPrefabList)
|
||||
{
|
||||
Debug.LogWarning("Using Legacy Network Prefab List. Consider Migrating.");
|
||||
m_DidWarnOldPrefabList = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the old List<NetworkPrefab> serialized data is present.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Internal use only to help migrate projects. <seealso cref="MigrateOldNetworkPrefabsToNetworkPrefabsList"/></remarks>
|
||||
internal bool HasOldPrefabList()
|
||||
{
|
||||
return OldPrefabList?.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migrate the old format List<NetworkPrefab> prefab registration to the new NetworkPrefabsList ScriptableObject.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// OnAfterDeserialize cannot instantiate new objects (e.g. NetworkPrefabsList SO) since it executes in a thread, so we have to do it later.
|
||||
/// Since NetworkConfig isn't a Unity.Object it doesn't get an Awake callback, so we have to do this in NetworkManager and expose this API.
|
||||
/// </remarks>
|
||||
internal NetworkPrefabsList MigrateOldNetworkPrefabsToNetworkPrefabsList()
|
||||
{
|
||||
if (OldPrefabList == null || OldPrefabList.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Prefabs == null)
|
||||
{
|
||||
throw new Exception("Prefabs field is null.");
|
||||
}
|
||||
|
||||
Prefabs.NetworkPrefabsLists.Add(ScriptableObject.CreateInstance<NetworkPrefabsList>());
|
||||
|
||||
if (OldPrefabList?.Count > 0)
|
||||
{
|
||||
// Migrate legacy types/fields
|
||||
foreach (var networkPrefab in OldPrefabList)
|
||||
{
|
||||
Prefabs.NetworkPrefabsLists[Prefabs.NetworkPrefabsLists.Count - 1].Add(networkPrefab);
|
||||
}
|
||||
}
|
||||
|
||||
OldPrefabList = null;
|
||||
return Prefabs.NetworkPrefabsLists[Prefabs.NetworkPrefabsLists.Count - 1];
|
||||
}
|
||||
|
||||
[FormerlySerializedAs("NetworkPrefabs")]
|
||||
[SerializeField]
|
||||
internal List<NetworkPrefab> OldPrefabList;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,23 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal enum NetworkPrefabOverride
|
||||
/// <summary>
|
||||
/// The method of NetworkPrefab override used to identify the source prefab
|
||||
/// </summary>
|
||||
public enum NetworkPrefabOverride
|
||||
{
|
||||
/// <summary>
|
||||
/// No oeverride is present
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Override the prefab when the given SourcePrefabToOverride is requested
|
||||
/// </summary>
|
||||
Prefab,
|
||||
/// <summary>
|
||||
/// Override the prefab when the given SourceHashToOverride is requested
|
||||
/// Used in situations where the server assets do not exist in client builds
|
||||
/// </summary>
|
||||
Hash
|
||||
}
|
||||
|
||||
@@ -14,10 +27,10 @@ namespace Unity.Netcode
|
||||
/// Class that represents a NetworkPrefab
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class NetworkPrefab
|
||||
public class NetworkPrefab
|
||||
{
|
||||
/// <summary>
|
||||
/// The override setttings for this NetworkPrefab
|
||||
/// The override settings for this NetworkPrefab
|
||||
/// </summary>
|
||||
public NetworkPrefabOverride Override;
|
||||
|
||||
@@ -41,5 +54,168 @@ namespace Unity.Netcode
|
||||
/// The prefab to replace (override) the source prefab with
|
||||
/// </summary>
|
||||
public GameObject OverridingTargetPrefab;
|
||||
|
||||
public bool Equals(NetworkPrefab other)
|
||||
{
|
||||
return Override == other.Override &&
|
||||
Prefab == other.Prefab &&
|
||||
SourcePrefabToOverride == other.SourcePrefabToOverride &&
|
||||
SourceHashToOverride == other.SourceHashToOverride &&
|
||||
OverridingTargetPrefab == other.OverridingTargetPrefab;
|
||||
}
|
||||
|
||||
public uint SourcePrefabGlobalObjectIdHash
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Override)
|
||||
{
|
||||
case NetworkPrefabOverride.None:
|
||||
if (Prefab != null && Prefab.TryGetComponent(out NetworkObject no))
|
||||
{
|
||||
return no.GlobalObjectIdHash;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Prefab field isn't set or isn't a Network Object");
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
if (SourcePrefabToOverride != null && SourcePrefabToOverride.TryGetComponent(out no))
|
||||
{
|
||||
return no.GlobalObjectIdHash;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Source Prefab field isn't set or isn't a Network Object");
|
||||
case NetworkPrefabOverride.Hash:
|
||||
return SourceHashToOverride;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint TargetPrefabGlobalObjectIdHash
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Override)
|
||||
{
|
||||
case NetworkPrefabOverride.None:
|
||||
return 0;
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
case NetworkPrefabOverride.Hash:
|
||||
if (OverridingTargetPrefab != null && OverridingTargetPrefab.TryGetComponent(out NetworkObject no))
|
||||
{
|
||||
return no.GlobalObjectIdHash;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Target Prefab field isn't set or isn't a Network Object");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Validate(int index = -1)
|
||||
{
|
||||
NetworkObject networkObject;
|
||||
if (Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
if (Prefab == null)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {index})");
|
||||
return false;
|
||||
}
|
||||
|
||||
networkObject = Prefab.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{NetworkManager.PrefabDebugHelper(this)} is missing " +
|
||||
$"a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate source prefab override values first
|
||||
switch (Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
if (SourceHashToOverride == 0)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(SourceHashToOverride)} is zero " +
|
||||
"(entry will be ignored).");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
if (SourcePrefabToOverride == null)
|
||||
{
|
||||
// This is a leftover side-effect from NetworkManager's OnValidate. It's a usability
|
||||
// adjustment to automatically set the "Prefab" field as the source prefab when a user
|
||||
// swaps from the default Inspector to the override one.
|
||||
if (Prefab != null)
|
||||
{
|
||||
SourcePrefabToOverride = Prefab;
|
||||
}
|
||||
else if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(SourcePrefabToOverride)} is null (entry will be ignored).");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SourcePrefabToOverride.TryGetComponent(out networkObject))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({SourcePrefabToOverride.name}) " +
|
||||
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate target prefab override values next
|
||||
if (OverridingTargetPrefab == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(OverridingTargetPrefab)} is null!");
|
||||
}
|
||||
switch (Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {SourceHashToOverride} will be removed and ignored.");
|
||||
break;
|
||||
}
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({SourcePrefabToOverride.name}) will be removed and ignored.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{SourceHash: {SourceHashToOverride}, TargetHash: {TargetPrefabGlobalObjectIdHash}}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
297
Runtime/Configuration/NetworkPrefabs.cs
Normal file
297
Runtime/Configuration/NetworkPrefabs.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that represents the runtime aspect of network prefabs.
|
||||
/// This class contains processed prefabs from the NetworkPrefabsList, as
|
||||
/// well as additional modifications (additions and removals) made at runtime.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkPrefabs
|
||||
{
|
||||
/// <summary>
|
||||
/// Edit-time scripted object containing a list of NetworkPrefabs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field can be null if no prefabs are pre-configured.
|
||||
/// Runtime usages of <see cref="NetworkPrefabs"/> should not depend on this edit-time field for execution.
|
||||
/// </remarks>
|
||||
[SerializeField]
|
||||
public List<NetworkPrefabsList> NetworkPrefabsLists = new List<NetworkPrefabsList>();
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
|
||||
/// Generated at runtime and OnValidate
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
|
||||
|
||||
[NonSerialized]
|
||||
public Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
|
||||
|
||||
public IReadOnlyList<NetworkPrefab> Prefabs => m_Prefabs;
|
||||
|
||||
[NonSerialized]
|
||||
private List<NetworkPrefab> m_Prefabs = new List<NetworkPrefab>();
|
||||
|
||||
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
|
||||
{
|
||||
if (AddPrefabRegistration(networkPrefab))
|
||||
{
|
||||
m_Prefabs.Add(networkPrefab);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
|
||||
{
|
||||
m_Prefabs.Remove(networkPrefab);
|
||||
}
|
||||
|
||||
~NetworkPrefabs()
|
||||
{
|
||||
foreach (var list in NetworkPrefabsLists)
|
||||
{
|
||||
list.OnAdd -= AddTriggeredByNetworkPrefabList;
|
||||
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the <see cref="NetworkPrefabsList"/> if one is present for use during runtime execution,
|
||||
/// else processes <see cref="Prefabs"/>.
|
||||
/// </summary>
|
||||
public void Initialize(bool warnInvalid = true)
|
||||
{
|
||||
if (NetworkPrefabsLists.Count != 0 && m_Prefabs.Count > 0)
|
||||
{
|
||||
NetworkLog.LogWarning("Runtime Network Prefabs was not empty at initialization time. Network " +
|
||||
"Prefab registrations made before initialization will be replaced by NetworkPrefabsList.");
|
||||
m_Prefabs.Clear();
|
||||
}
|
||||
|
||||
foreach (var list in NetworkPrefabsLists)
|
||||
{
|
||||
list.OnAdd += AddTriggeredByNetworkPrefabList;
|
||||
list.OnRemove += RemoveTriggeredByNetworkPrefabList;
|
||||
}
|
||||
|
||||
NetworkPrefabOverrideLinks.Clear();
|
||||
OverrideToNetworkPrefab.Clear();
|
||||
|
||||
var prefabs = NetworkPrefabsLists.Count != 0 ? new List<NetworkPrefab>() : m_Prefabs;
|
||||
|
||||
if (NetworkPrefabsLists.Count != 0)
|
||||
{
|
||||
foreach (var list in NetworkPrefabsLists)
|
||||
{
|
||||
foreach (var networkPrefab in list.PrefabList)
|
||||
{
|
||||
prefabs.Add(networkPrefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Prefabs = new List<NetworkPrefab>();
|
||||
|
||||
List<NetworkPrefab> removeList = null;
|
||||
if (warnInvalid)
|
||||
{
|
||||
removeList = new List<NetworkPrefab>();
|
||||
}
|
||||
|
||||
foreach (var networkPrefab in prefabs)
|
||||
{
|
||||
if (AddPrefabRegistration(networkPrefab))
|
||||
{
|
||||
m_Prefabs.Add(networkPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
removeList?.Add(networkPrefab);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out anything that is invalid or not used
|
||||
if (removeList?.Count > 0)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
var sb = new StringBuilder("Removing invalid prefabs from Network Prefab registration: ");
|
||||
sb.Append(string.Join(", ", removeList));
|
||||
NetworkLog.LogWarning(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new NetworkPrefab instance to the list
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The framework does not synchronize this list between clients. Any runtime changes must be handled manually.
|
||||
///
|
||||
/// Any modifications made here are not persisted. Permanent configuration changes should be done
|
||||
/// through the <see cref="NetworkPrefabsList"/> scriptable object property.
|
||||
/// </remarks>
|
||||
public bool Add(NetworkPrefab networkPrefab)
|
||||
{
|
||||
if (AddPrefabRegistration(networkPrefab))
|
||||
{
|
||||
m_Prefabs.Add(networkPrefab);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a NetworkPrefab instance from the list
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The framework does not synchronize this list between clients. Any runtime changes must be handled manually.
|
||||
///
|
||||
/// Any modifications made here are not persisted. Permanent configuration changes should be done
|
||||
/// through the <see cref="NetworkPrefabsList"/> scriptable object property.
|
||||
/// </remarks>
|
||||
public void Remove(NetworkPrefab prefab)
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(prefab));
|
||||
}
|
||||
|
||||
m_Prefabs.Remove(prefab);
|
||||
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
|
||||
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a NetworkPrefab instance with matching <see cref="NetworkPrefab.Prefab"/> from the list
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The framework does not synchronize this list between clients. Any runtime changes must be handled manually.
|
||||
///
|
||||
/// Any modifications made here are not persisted. Permanent configuration changes should be done
|
||||
/// through the <see cref="NetworkPrefabsList"/> scriptable object property.
|
||||
/// </remarks>
|
||||
public void Remove(GameObject prefab)
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(prefab));
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_Prefabs.Count; i++)
|
||||
{
|
||||
if (m_Prefabs[i].Prefab == prefab)
|
||||
{
|
||||
Remove(m_Prefabs[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given GameObject is present as a prefab within the list
|
||||
/// </summary>
|
||||
/// <param name="prefab">The prefab to check</param>
|
||||
/// <returns>Whether or not the prefab exists</returns>
|
||||
public bool Contains(GameObject prefab)
|
||||
{
|
||||
for (int i = 0; i < m_Prefabs.Count; i++)
|
||||
{
|
||||
if (m_Prefabs[i].Prefab == prefab)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given NetworkPrefab is present within the list
|
||||
/// </summary>
|
||||
/// <param name="prefab">The prefab to check</param>
|
||||
/// <returns>Whether or not the prefab exists</returns>
|
||||
public bool Contains(NetworkPrefab prefab)
|
||||
{
|
||||
for (int i = 0; i < m_Prefabs.Count; i++)
|
||||
{
|
||||
if (m_Prefabs[i].Equals(prefab))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures <see cref="NetworkPrefabOverrideLinks"/> and <see cref="OverrideToNetworkPrefab"/> for the given <see cref="NetworkPrefab"/>
|
||||
/// </summary>
|
||||
private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
|
||||
{
|
||||
if (networkPrefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Safeguard validation check since this method is called from outside of NetworkConfig and we can't control what's passed in.
|
||||
if (!networkPrefab.Validate())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint source = networkPrefab.SourcePrefabGlobalObjectIdHash;
|
||||
uint target = networkPrefab.TargetPrefabGlobalObjectIdHash;
|
||||
|
||||
// Make sure the prefab isn't already registered.
|
||||
if (NetworkPrefabOverrideLinks.ContainsKey(source))
|
||||
{
|
||||
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
|
||||
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
|
||||
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we don't have an override configured, registration is simple!
|
||||
if (networkPrefab.Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make sure we don't have several overrides targeting the same prefab. Apparently we don't support that... shame.
|
||||
if (OverrideToNetworkPrefab.ContainsKey(target))
|
||||
{
|
||||
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
|
||||
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
|
||||
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {target}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
|
||||
OverrideToNetworkPrefab.Add(target, source);
|
||||
}
|
||||
break;
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
|
||||
OverrideToNetworkPrefab.Add(target, source);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Configuration/NetworkPrefabs.cs.meta
Normal file
11
Runtime/Configuration/NetworkPrefabs.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 230fc75f5639e46dc91734aa67d56a3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
95
Runtime/Configuration/NetworkPrefabsList.cs
Normal file
95
Runtime/Configuration/NetworkPrefabsList.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A ScriptableObject for holding a network prefabs list, which can be
|
||||
/// shared between multiple NetworkManagers.
|
||||
///
|
||||
/// When NetworkManagers hold references to this list, modifications to the
|
||||
/// list at runtime will be picked up by all NetworkManagers that reference it.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "NetworkPrefabsList", menuName = "Netcode/Network Prefabs List")]
|
||||
public class NetworkPrefabsList : ScriptableObject
|
||||
{
|
||||
internal delegate void OnAddDelegate(NetworkPrefab prefab);
|
||||
internal OnAddDelegate OnAdd;
|
||||
|
||||
internal delegate void OnRemoveDelegate(NetworkPrefab prefab);
|
||||
internal OnRemoveDelegate OnRemove;
|
||||
|
||||
[SerializeField]
|
||||
internal bool IsDefault;
|
||||
|
||||
[FormerlySerializedAs("Prefabs")]
|
||||
[SerializeField]
|
||||
internal List<NetworkPrefab> List = new List<NetworkPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// Read-only view into the prefabs list, enabling iterating and examining the list.
|
||||
/// Actually modifying the list should be done using <see cref="Add"/>
|
||||
/// and <see cref="Remove"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<NetworkPrefab> PrefabList => List;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a prefab to the prefab list. Performing this here will apply the operation to all
|
||||
/// <see cref="NetworkManager"/>s that reference this list.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
public void Add(NetworkPrefab prefab)
|
||||
{
|
||||
List.Add(prefab);
|
||||
OnAdd?.Invoke(prefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a prefab from the prefab list. Performing this here will apply the operation to all
|
||||
/// <see cref="NetworkManager"/>s that reference this list.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
public void Remove(NetworkPrefab prefab)
|
||||
{
|
||||
List.Remove(prefab);
|
||||
OnRemove?.Invoke(prefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given GameObject is present as a prefab within the list
|
||||
/// </summary>
|
||||
/// <param name="prefab">The prefab to check</param>
|
||||
/// <returns>Whether or not the prefab exists</returns>
|
||||
public bool Contains(GameObject prefab)
|
||||
{
|
||||
for (int i = 0; i < List.Count; i++)
|
||||
{
|
||||
if (List[i].Prefab == prefab)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given NetworkPrefab is present within the list
|
||||
/// </summary>
|
||||
/// <param name="prefab">The prefab to check</param>
|
||||
/// <returns>Whether or not the prefab exists</returns>
|
||||
public bool Contains(NetworkPrefab prefab)
|
||||
{
|
||||
for (int i = 0; i < List.Count; i++)
|
||||
{
|
||||
if (List[i].Equals(prefab))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Configuration/NetworkPrefabsList.cs.meta
Normal file
11
Runtime/Configuration/NetworkPrefabsList.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e651dbb3fbac04af2b8f5abf007ddc23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -21,6 +21,7 @@ namespace Unity.Netcode
|
||||
Client = 2
|
||||
}
|
||||
|
||||
|
||||
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
||||
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
||||
|
||||
@@ -235,14 +236,42 @@ namespace Unity.Netcode
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||
{
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
if (clientRpcParams.Send.TargetClientIds != null)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
client.Key,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
targetClientId,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
}
|
||||
}
|
||||
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
{
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
targetClientId,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
|
||||
while (observerEnumerator.MoveNext())
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
observerEnumerator.Current,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -258,7 +287,18 @@ namespace Unity.Netcode
|
||||
/// 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;
|
||||
public NetworkManager NetworkManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (NetworkObject?.NetworkManager != null)
|
||||
{
|
||||
return NetworkObject?.NetworkManager;
|
||||
}
|
||||
|
||||
return NetworkManager.Singleton;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
||||
@@ -274,18 +314,18 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected bool IsServer { get; private set; }
|
||||
public bool IsServer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient { get; private set; }
|
||||
public bool IsClient { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost { get; private set; }
|
||||
public bool IsHost { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
@@ -307,23 +347,29 @@ namespace Unity.Netcode
|
||||
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>
|
||||
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
try
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// ShutdownInProgress check:
|
||||
@@ -436,14 +482,41 @@ namespace Unity.Netcode
|
||||
IsSpawned = true;
|
||||
InitializeVariables();
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkSpawn();
|
||||
}
|
||||
|
||||
internal void VisibleOnNetworkSpawn()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnNetworkSpawn();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
|
||||
InitializeVariables();
|
||||
if (IsServer)
|
||||
{
|
||||
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
|
||||
// NetworkList, we need to mark the object as free of updates.
|
||||
// This should happen for all objects on the machine triggering the spawn.
|
||||
PostNetworkVariableWrite(true);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkDespawn()
|
||||
{
|
||||
IsSpawned = false;
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkDespawn();
|
||||
try
|
||||
{
|
||||
OnNetworkDespawn();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -497,11 +570,11 @@ namespace Unity.Netcode
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<FieldInfo>();
|
||||
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
|
||||
}
|
||||
|
||||
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
||||
@@ -576,12 +649,24 @@ namespace Unity.Netcode
|
||||
NetworkVariableIndexesToResetSet.Clear();
|
||||
}
|
||||
|
||||
internal void PostNetworkVariableWrite()
|
||||
internal void PostNetworkVariableWrite(bool forced = false)
|
||||
{
|
||||
// mark any variables we wrote as no longer dirty
|
||||
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
|
||||
if (forced)
|
||||
{
|
||||
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
|
||||
// Mark every variable as no longer dirty. We just spawned the object and whatever the game code did
|
||||
// during OnNetworkSpawn has been sent and needs to be cleared
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[i].ResetDirty();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// mark any variables we wrote as no longer dirty
|
||||
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
MarkVariablesDirty(false);
|
||||
@@ -645,7 +730,7 @@ namespace Unity.Netcode
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
message.Serialize(tmpWriter);
|
||||
message.Serialize(tmpWriter, message.Version);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -678,6 +763,14 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
|
||||
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
|
||||
/// by the number of bytes written for that specific field.
|
||||
/// </remarks>
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
@@ -687,27 +780,47 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
|
||||
|
||||
if (canClientRead)
|
||||
if (NetworkVariableFields[j].CanClientRead(targetClientId))
|
||||
{
|
||||
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);
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
var writePos = writer.Position;
|
||||
// Note: This value can't be packed because we don't know how large it will be in advance
|
||||
// we reserve space for it, then write the data, then come back and fill in the space
|
||||
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
|
||||
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
|
||||
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
|
||||
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
|
||||
{
|
||||
NetworkVariableFields[j].WriteField(writer);
|
||||
}
|
||||
}
|
||||
else
|
||||
else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
/// <summary>
|
||||
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
|
||||
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
|
||||
/// by the number of bytes written for that specific field.
|
||||
/// </remarks>
|
||||
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
@@ -716,13 +829,23 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
reader.ReadValueSafe(out ushort varSize);
|
||||
if (varSize == 0)
|
||||
var varSize = (ushort)0;
|
||||
var readStartPos = 0;
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
reader.ReadValueSafe(out varSize);
|
||||
if (varSize == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
readStartPos = reader.Position;
|
||||
}
|
||||
else // If the client cannot read this field, then skip it
|
||||
if (!NetworkVariableFields[j].CanClientRead(clientId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var readStartPos = reader.Position;
|
||||
NetworkVariableFields[j].ReadField(reader);
|
||||
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
@@ -759,6 +882,138 @@ namespace Unity.Netcode
|
||||
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method if your derived NetworkBehaviour requires custom synchronization data.
|
||||
/// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours
|
||||
/// and will increase the payload size for client synchronization and dynamically spawned
|
||||
/// <see cref="NetworkObject"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When serializing (writing) this will be invoked during the client synchronization period and
|
||||
/// when spawning new NetworkObjects.
|
||||
/// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated
|
||||
/// NetworkObject being spawned.
|
||||
/// </remarks>
|
||||
/// <param name="serializer">The serializer to use to read and write the data.</param>
|
||||
/// <typeparam name="T">
|
||||
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
|
||||
/// is in read mode or write mode.
|
||||
/// </typeparam>
|
||||
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
|
||||
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This includes try-catch blocks to recover from exceptions that might occur and continue to
|
||||
/// synchronize any remaining NetworkBehaviours.
|
||||
/// </remarks>
|
||||
/// <returns>true if it wrote synchronization data and false if it did not</returns>
|
||||
internal bool Synchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsWriter)
|
||||
{
|
||||
// Get the writer to handle seeking and determining how many bytes were written
|
||||
var writer = serializer.GetFastBufferWriter();
|
||||
// Save our position before we attempt to write anything so we can seek back to it (i.e. error occurs)
|
||||
var positionBeforeWrite = writer.Position;
|
||||
writer.WriteValueSafe(NetworkBehaviourId);
|
||||
|
||||
// Save our position where we will write the final size being written so we can skip over it in the
|
||||
// event an exception occurs when deserializing.
|
||||
var sizePosition = writer.Position;
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
|
||||
// Save our position before synchronizing to determine how much was written
|
||||
var positionBeforeSynchronize = writer.Position;
|
||||
var threwException = false;
|
||||
try
|
||||
{
|
||||
OnSynchronize(ref serializer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
threwException = true;
|
||||
if (NetworkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{name} threw an exception during synchronization serialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
var finalPosition = writer.Position;
|
||||
|
||||
// If we wrote nothing then skip writing anything for this NetworkBehaviour
|
||||
if (finalPosition == positionBeforeSynchronize || threwException)
|
||||
{
|
||||
writer.Seek(positionBeforeWrite);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write the number of bytes serialized to handle exceptions on the deserialization side
|
||||
var bytesWritten = finalPosition - positionBeforeSynchronize;
|
||||
writer.Seek(sizePosition);
|
||||
writer.WriteValueSafe((ushort)bytesWritten);
|
||||
writer.Seek(finalPosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var reader = serializer.GetFastBufferReader();
|
||||
// We will always read the expected byte count
|
||||
reader.ReadValueSafe(out ushort expectedBytesToRead);
|
||||
|
||||
// Save our position before we begin synchronization deserialization
|
||||
var positionBeforeSynchronize = reader.Position;
|
||||
var synchronizationError = false;
|
||||
try
|
||||
{
|
||||
// Invoke synchronization
|
||||
OnSynchronize(ref serializer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (NetworkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{name} threw an exception during synchronization deserialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
synchronizationError = true;
|
||||
}
|
||||
|
||||
var totalBytesRead = reader.Position - positionBeforeSynchronize;
|
||||
if (totalBytesRead != expectedBytesToRead)
|
||||
{
|
||||
if (NetworkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
|
||||
}
|
||||
synchronizationError = true;
|
||||
}
|
||||
|
||||
// Skip over the entry if deserialization fails
|
||||
if (synchronizationError)
|
||||
{
|
||||
var skipToPosition = positionBeforeSynchronize + expectedBytesToRead;
|
||||
reader.Seek(skipToPosition);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to.
|
||||
/// NOTE: If you override this, you will want to always invoke this base class version of this
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
try
|
||||
{
|
||||
// NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point
|
||||
// trying to process them, even if they were previously marked as dirty.
|
||||
m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null);
|
||||
|
||||
if (networkManager.IsServer)
|
||||
{
|
||||
foreach (var dirtyObj in m_DirtyNetworkObjects)
|
||||
@@ -38,10 +42,6 @@ namespace Unity.Netcode
|
||||
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
var client = networkManager.ConnectedClientsList[i];
|
||||
if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
|
||||
{
|
||||
@@ -72,13 +72,27 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var dirtyObj in m_DirtyNetworkObjects)
|
||||
{
|
||||
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
var behaviour = dirtyObj.ChildNetworkBehaviours[k];
|
||||
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (behaviour.NetworkVariableFields[i].IsDirty() &&
|
||||
!behaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
||||
{
|
||||
behaviour.NetworkVariableIndexesToResetSet.Add(i);
|
||||
behaviour.NetworkVariableIndexesToReset.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var dirtyobj in m_DirtyNetworkObjects)
|
||||
{
|
||||
for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
dirtyobj.PostNetworkVariableWrite();
|
||||
}
|
||||
m_DirtyNetworkObjects.Clear();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// A component used to identify that a GameObject in the network
|
||||
/// </summary>
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkObject), -99)]
|
||||
[AddComponentMenu("Netcode/Network Object", -99)]
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkObject : MonoBehaviour
|
||||
{
|
||||
@@ -129,7 +129,7 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to destroy this object if it's owner is destroyed.
|
||||
/// If false, the objects ownership will be given to the server.
|
||||
/// If true, the objects ownership will be given to the server.
|
||||
/// </summary>
|
||||
public bool DontDestroyWithOwner;
|
||||
|
||||
@@ -265,9 +265,8 @@ namespace Unity.Netcode
|
||||
throw new VisibilityChangeException("The object is already visible");
|
||||
}
|
||||
|
||||
NetworkManager.MarkObjectForShowingTo(this, clientId);
|
||||
Observers.Add(clientId);
|
||||
|
||||
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -351,26 +350,28 @@ namespace Unity.Netcode
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (!Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException("The object is already hidden");
|
||||
}
|
||||
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
Observers.Remove(clientId);
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
if (!NetworkManager.RemoveObjectFromShowingTo(this, clientId))
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
DestroyGameObject = !IsSceneObject.Value
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
|
||||
if (!Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException("The object is already hidden");
|
||||
}
|
||||
Observers.Remove(clientId);
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
DestroyGameObject = !IsSceneObject.Value
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -435,7 +436,7 @@ namespace Unity.Netcode
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
|
||||
(IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
|
||||
(IsSceneObject == null || (IsSceneObject.Value != true)))
|
||||
{
|
||||
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
|
||||
}
|
||||
@@ -509,6 +510,7 @@ namespace Unity.Netcode
|
||||
/// <param name="destroy">(true) the <see cref="GameObject"/> will be destroyed (false) the <see cref="GameObject"/> will persist after being despawned</param>
|
||||
public void Despawn(bool destroy = true)
|
||||
{
|
||||
MarkVariablesDirty(false);
|
||||
NetworkManager.SpawnManager.DespawnObject(this, destroy);
|
||||
}
|
||||
|
||||
@@ -572,21 +574,21 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private bool m_IsReparented; // Did initial parent (came from the scene hierarchy) change at runtime?
|
||||
private ulong? m_LatestParent; // What is our last set parent NetworkObject's ID?
|
||||
private Transform m_CachedParent; // What is our last set parent Transform reference?
|
||||
private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent
|
||||
|
||||
internal void SetCachedParent(Transform parentTransform)
|
||||
{
|
||||
m_CachedParent = parentTransform;
|
||||
}
|
||||
|
||||
internal (bool IsReparented, ulong? LatestParent) GetNetworkParenting() => (m_IsReparented, m_LatestParent);
|
||||
internal ulong? GetNetworkParenting() => m_LatestParent;
|
||||
|
||||
internal void SetNetworkParenting(bool isReparented, ulong? latestParent)
|
||||
internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays)
|
||||
{
|
||||
m_IsReparented = isReparented;
|
||||
m_LatestParent = latestParent;
|
||||
m_CachedWorldPositionStays = worldPositionStays;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -597,7 +599,10 @@ namespace Unity.Netcode
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(Transform parent, bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
|
||||
var networkObject = parent.GetComponent<NetworkObject>();
|
||||
|
||||
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
|
||||
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -608,7 +613,37 @@ namespace Unity.Netcode
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(GameObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
|
||||
// If we are removing ourself from a parent
|
||||
if (parent == null)
|
||||
{
|
||||
return TrySetParent((NetworkObject)null, worldPositionStays);
|
||||
}
|
||||
|
||||
var networkObject = parent.GetComponent<NetworkObject>();
|
||||
|
||||
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
|
||||
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used when despawning the parent, we want to preserve the cached WorldPositionStays value
|
||||
/// </summary>
|
||||
internal bool TryRemoveParentCachedWorldPositionStays()
|
||||
{
|
||||
return TrySetParent((NetworkObject)null, m_CachedWorldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the parent of the NetworkObject's transform
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a more convenient way to remove the parent without having to cast the null value to either <see cref="GameObject"/> or <see cref="NetworkObject"/>
|
||||
/// </remarks>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryRemoveParent(bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent((NetworkObject)null, worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -639,17 +674,21 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent != null && !parent.IsSpawned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_CachedWorldPositionStays = worldPositionStays;
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
return false;
|
||||
transform.SetParent(null, worldPositionStays);
|
||||
}
|
||||
|
||||
if (!parent.IsSpawned)
|
||||
else
|
||||
{
|
||||
return false;
|
||||
transform.SetParent(parent.transform, worldPositionStays);
|
||||
}
|
||||
|
||||
transform.SetParent(parent.transform, worldPositionStays);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -685,12 +724,11 @@ namespace Unity.Netcode
|
||||
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
|
||||
return;
|
||||
}
|
||||
|
||||
var removeParent = false;
|
||||
var parentTransform = transform.parent;
|
||||
if (parentTransform != null)
|
||||
{
|
||||
var parentObject = transform.parent.GetComponent<NetworkObject>();
|
||||
if (parentObject == null)
|
||||
if (!transform.parent.TryGetComponent<NetworkObject>(out var parentObject))
|
||||
{
|
||||
transform.parent = m_CachedParent;
|
||||
Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent"));
|
||||
@@ -709,19 +747,31 @@ namespace Unity.Netcode
|
||||
else
|
||||
{
|
||||
m_LatestParent = null;
|
||||
removeParent = m_CachedParent != null;
|
||||
}
|
||||
|
||||
m_IsReparented = true;
|
||||
ApplyNetworkParenting();
|
||||
ApplyNetworkParenting(removeParent);
|
||||
|
||||
var message = new ParentSyncMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
IsReparented = m_IsReparented,
|
||||
IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue,
|
||||
LatestParent = m_LatestParent
|
||||
LatestParent = m_LatestParent,
|
||||
RemoveParent = removeParent,
|
||||
WorldPositionStays = m_CachedWorldPositionStays,
|
||||
Position = m_CachedWorldPositionStays ? transform.position : transform.localPosition,
|
||||
Rotation = m_CachedWorldPositionStays ? transform.rotation : transform.localRotation,
|
||||
Scale = transform.localScale,
|
||||
};
|
||||
|
||||
// We need to preserve the m_CachedWorldPositionStays value until after we create the message
|
||||
// in order to assure any local space values changed/reset get applied properly. If our
|
||||
// parent is null then go ahead and reset the m_CachedWorldPositionStays the default value.
|
||||
if (parentTransform == null)
|
||||
{
|
||||
m_CachedWorldPositionStays = true;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
var maxCount = NetworkManager.ConnectedClientsIds.Count;
|
||||
@@ -749,42 +799,96 @@ namespace Unity.Netcode
|
||||
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
|
||||
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
|
||||
|
||||
internal bool ApplyNetworkParenting()
|
||||
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false)
|
||||
{
|
||||
if (!AutoObjectParentSync)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsSpawned)
|
||||
// SPECIAL CASE:
|
||||
// The ignoreNotSpawned is a special case scenario where a late joining client has joined
|
||||
// and loaded one or more scenes that contain nested in-scene placed NetworkObject children
|
||||
// yet the server's synchronization information does not indicate the NetworkObject in question
|
||||
// has a parent. Under this scenario, we want to remove the parent before spawning and setting
|
||||
// the transform values. This is the only scenario where the ignoreNotSpawned parameter is used.
|
||||
if (!IsSpawned && !ignoreNotSpawned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_IsReparented)
|
||||
// Handle the first in-scene placed NetworkObject parenting scenarios. Once the m_LatestParent
|
||||
// has been set, this will not be entered into again (i.e. the later code will be invoked and
|
||||
// users will get notifications when the parent changes).
|
||||
var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value;
|
||||
if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced)
|
||||
{
|
||||
return true;
|
||||
var parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
|
||||
|
||||
// If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component
|
||||
// attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting.
|
||||
// Note: We only start tracking parenting if the user removes the child from the standard GameObject
|
||||
// parent and then re-parents the child under a GameObject with a NetworkObject component attached.
|
||||
if (parentNetworkObject == null)
|
||||
{
|
||||
// If we are parented under a GameObject, go ahead and mark the world position stays as false
|
||||
// so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects)
|
||||
m_CachedWorldPositionStays = false;
|
||||
return true;
|
||||
}
|
||||
else // If the parent still isn't spawned add this to the orphaned children and return false
|
||||
if (!parentNetworkObject.IsSpawned)
|
||||
{
|
||||
OrphanChildren.Add(this);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we made it this far, go ahead and set the network parenting values
|
||||
// with the WorldPoisitonSays value set to false
|
||||
// Note: Since in-scene placed NetworkObjects are parented in the scene
|
||||
// the default "assumption" is that children are parenting local space
|
||||
// relative.
|
||||
SetNetworkParenting(parentNetworkObject.NetworkObjectId, false);
|
||||
|
||||
// Set the cached parent
|
||||
m_CachedParent = parentNetworkObject.transform;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_LatestParent == null || !m_LatestParent.HasValue)
|
||||
// If we are removing the parent or our latest parent is not set, then remove the parent
|
||||
// removeParent is only set when:
|
||||
// - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed
|
||||
// - The client-side when handling a ParentSyncMessage
|
||||
// When clients are synchronizing only the m_LatestParent.HasValue will not have a value if there is no parent
|
||||
// or a parent was removed prior to the client connecting (i.e. in-scene placed NetworkObjects)
|
||||
if (removeParent || !m_LatestParent.HasValue)
|
||||
{
|
||||
m_CachedParent = null;
|
||||
transform.parent = null;
|
||||
|
||||
// We must use Transform.SetParent when taking WorldPositionStays into
|
||||
// consideration, otherwise just setting transform.parent = null defaults
|
||||
// to WorldPositionStays which can cause scaling issues if the parent's
|
||||
// scale is not the default (Vetctor3.one) value.
|
||||
transform.SetParent(null, m_CachedWorldPositionStays);
|
||||
InvokeBehaviourOnNetworkObjectParentChanged(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
|
||||
// If we have a latest parent id but it hasn't been spawned yet, then add this instance to the orphanChildren
|
||||
// HashSet and return false (i.e. parenting not applied yet)
|
||||
if (m_LatestParent.HasValue && !NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
|
||||
{
|
||||
OrphanChildren.Add(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we made it here, then parent this instance under the parentObject
|
||||
var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value];
|
||||
|
||||
m_CachedParent = parentObject.transform;
|
||||
transform.parent = parentObject.transform;
|
||||
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
|
||||
|
||||
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
|
||||
return true;
|
||||
@@ -821,6 +925,13 @@ namespace Unity.Netcode
|
||||
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].VisibleOnNetworkSpawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkDespawn()
|
||||
@@ -898,13 +1009,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
|
||||
/// <summary>
|
||||
/// Only invoked during first synchronization of a NetworkObject (late join or newly spawned)
|
||||
/// </summary>
|
||||
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var behaviour = ChildNetworkBehaviours[i];
|
||||
behaviour.InitializeVariables();
|
||||
behaviour.SetNetworkVariableData(reader);
|
||||
behaviour.SetNetworkVariableData(reader, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -941,7 +1056,20 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?");
|
||||
NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client.");
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
var currentKnownChildren = new System.Text.StringBuilder();
|
||||
currentKnownChildren.Append($"Known child {nameof(NetworkBehaviour)}s:");
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var childNetworkBehaviour = ChildNetworkBehaviours[i];
|
||||
currentKnownChildren.Append($" [{i}] {childNetworkBehaviour.__getTypeName()}");
|
||||
currentKnownChildren.Append(i < ChildNetworkBehaviours.Count - 1 ? "," : ".");
|
||||
}
|
||||
NetworkLog.LogInfo(currentKnownChildren.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -952,20 +1080,43 @@ namespace Unity.Netcode
|
||||
|
||||
internal struct SceneObject
|
||||
{
|
||||
public struct HeaderData : INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
public uint Hash;
|
||||
private byte m_BitField;
|
||||
public uint Hash;
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
|
||||
public bool IsPlayerObject;
|
||||
public bool HasParent;
|
||||
public bool IsSceneObject;
|
||||
public bool HasTransform;
|
||||
public bool IsReparented;
|
||||
public bool IsPlayerObject
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 0);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 0, value);
|
||||
}
|
||||
public bool HasParent
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 1);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 1, value);
|
||||
}
|
||||
public bool IsSceneObject
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 2);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 2, value);
|
||||
}
|
||||
public bool HasTransform
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 3);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 3, value);
|
||||
}
|
||||
|
||||
public HeaderData Header;
|
||||
public bool IsLatestParentSet
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 4);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 4, value);
|
||||
}
|
||||
|
||||
public bool WorldPositionStays
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 5);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 5, value);
|
||||
}
|
||||
|
||||
//If(Metadata.HasParent)
|
||||
public ulong ParentObjectId;
|
||||
@@ -975,12 +1126,12 @@ namespace Unity.Netcode
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
}
|
||||
|
||||
public TransformData Transform;
|
||||
|
||||
//If(Metadata.IsReparented)
|
||||
public bool IsLatestParentSet;
|
||||
|
||||
//If(IsLatestParentSet)
|
||||
public ulong? LatestParent;
|
||||
@@ -990,98 +1141,170 @@ namespace Unity.Netcode
|
||||
|
||||
public int NetworkSceneHandle;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
var writeSize = sizeof(HeaderData);
|
||||
writeSize += Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||
writeSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
writer.WriteValueSafe(m_BitField);
|
||||
writer.WriteValueSafe(Hash);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||
|
||||
if (HasParent)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, ParentObjectId);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var writeSize = 0;
|
||||
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!writer.TryBeginWrite(writeSize))
|
||||
{
|
||||
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
|
||||
writer.WriteValue(Header);
|
||||
|
||||
if (Header.HasParent)
|
||||
{
|
||||
writer.WriteValue(ParentObjectId);
|
||||
}
|
||||
|
||||
if (Header.HasTransform)
|
||||
if (HasTransform)
|
||||
{
|
||||
writer.WriteValue(Transform);
|
||||
}
|
||||
|
||||
if (Header.IsReparented)
|
||||
{
|
||||
writer.WriteValue(IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
writer.WriteValue((ulong)LatestParent);
|
||||
}
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
|
||||
// sizes for dynamically spawned NetworkObjects.
|
||||
if (Header.IsSceneObject)
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
|
||||
// this to locate their local instance of the in-scene placed NetworkObject instance.
|
||||
// Only written for in-scene placed NetworkObjects.
|
||||
if (IsSceneObject)
|
||||
{
|
||||
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||
}
|
||||
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
// Synchronize NetworkVariables and NetworkBehaviours
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);
|
||||
}
|
||||
|
||||
public unsafe void Deserialize(FastBufferReader reader)
|
||||
public void Deserialize(FastBufferReader reader)
|
||||
{
|
||||
if (!reader.TryBeginRead(sizeof(HeaderData)))
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
reader.ReadValue(out Header);
|
||||
var readSize = Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||
readSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
reader.ReadValueSafe(out m_BitField);
|
||||
reader.ReadValueSafe(out Hash);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
if (HasParent)
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
|
||||
if (Header.HasParent)
|
||||
{
|
||||
reader.ReadValue(out ParentObjectId);
|
||||
}
|
||||
|
||||
if (Header.HasTransform)
|
||||
{
|
||||
reader.ReadValue(out Transform);
|
||||
}
|
||||
|
||||
if (Header.IsReparented)
|
||||
{
|
||||
reader.ReadValue(out IsLatestParentSet);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ParentObjectId);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// Client-side NetworkSceneManagers use this to locate their local instance of the
|
||||
// NetworkObject instance.
|
||||
if (Header.IsSceneObject)
|
||||
var readSize = 0;
|
||||
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
readSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
// Try to begin reading the remaining bytes
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
{
|
||||
reader.ReadValueSafe(out NetworkSceneHandle);
|
||||
throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer");
|
||||
}
|
||||
|
||||
if (HasTransform)
|
||||
{
|
||||
reader.ReadValue(out Transform);
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
|
||||
// this to locate their local instance of the in-scene placed NetworkObject instance.
|
||||
// Only read for in-scene placed NetworkObjects
|
||||
if (IsSceneObject)
|
||||
{
|
||||
reader.ReadValue(out NetworkSceneHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void PostNetworkVariableWrite()
|
||||
{
|
||||
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles synchronizing NetworkVariables and custom synchronization data for NetworkBehaviours.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is where we determine how much data is written after the associated NetworkObject in order to recover
|
||||
/// from a failed instantiated NetworkObject without completely disrupting client synchronization.
|
||||
/// </remarks>
|
||||
internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer, ulong targetClientId = 0) where T : IReaderWriter
|
||||
{
|
||||
if (serializer.IsWriter)
|
||||
{
|
||||
var writer = serializer.GetFastBufferWriter();
|
||||
var positionBeforeSynchronizing = writer.Position;
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
var sizeToSkipCalculationPosition = writer.Position;
|
||||
|
||||
// Synchronize NetworkVariables
|
||||
WriteNetworkVariableData(writer, targetClientId);
|
||||
// Reserve the NetworkBehaviour synchronization count position
|
||||
var networkBehaviourCountPosition = writer.Position;
|
||||
writer.WriteValueSafe((byte)0);
|
||||
|
||||
// Parse through all NetworkBehaviours and any that return true
|
||||
// had additional synchronization data written.
|
||||
// (See notes for reading/deserialization below)
|
||||
var synchronizationCount = (byte)0;
|
||||
foreach (var childBehaviour in ChildNetworkBehaviours)
|
||||
{
|
||||
if (childBehaviour.Synchronize(ref serializer))
|
||||
{
|
||||
synchronizationCount++;
|
||||
}
|
||||
}
|
||||
|
||||
var currentPosition = writer.Position;
|
||||
// Write the total number of bytes written for NetworkVariable and NetworkBehaviour
|
||||
// synchronization.
|
||||
writer.Seek(positionBeforeSynchronizing);
|
||||
// We want the size of everything after our size to skip calculation position
|
||||
var size = (ushort)(currentPosition - sizeToSkipCalculationPosition);
|
||||
writer.WriteValueSafe(size);
|
||||
// Write the number of NetworkBehaviours synchronized
|
||||
writer.Seek(networkBehaviourCountPosition);
|
||||
writer.WriteValueSafe(synchronizationCount);
|
||||
// seek back to the position after writing NetworkVariable and NetworkBehaviour
|
||||
// synchronization data.
|
||||
writer.Seek(currentPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reader = serializer.GetFastBufferReader();
|
||||
|
||||
reader.ReadValueSafe(out ushort sizeOfSynchronizationData);
|
||||
var seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData;
|
||||
// Apply the network variable synchronization data
|
||||
SetNetworkVariableData(reader, targetClientId);
|
||||
// Read the number of NetworkBehaviours to synchronize
|
||||
reader.ReadValueSafe(out byte numberSynchronized);
|
||||
var networkBehaviourId = (ushort)0;
|
||||
|
||||
// If a NetworkBehaviour writes synchronization data, it will first
|
||||
// write its NetworkBehaviourId so when deserializing the client-side
|
||||
// can find the right NetworkBehaviour to deserialize the synchronization data.
|
||||
for (int i = 0; i < numberSynchronized; i++)
|
||||
{
|
||||
serializer.SerializeValue(ref networkBehaviourId);
|
||||
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
|
||||
networkBehaviour.Synchronize(ref serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1090,14 +1313,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
var obj = new SceneObject
|
||||
{
|
||||
Header = new SceneObject.HeaderData
|
||||
{
|
||||
IsPlayerObject = IsPlayerObject,
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
OwnerClientId = OwnerClientId,
|
||||
IsSceneObject = IsSceneObject ?? true,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
},
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
OwnerClientId = OwnerClientId,
|
||||
IsPlayerObject = IsPlayerObject,
|
||||
IsSceneObject = IsSceneObject ?? true,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
OwnerObject = this,
|
||||
TargetClientId = targetClientId
|
||||
};
|
||||
@@ -1107,27 +1327,21 @@ namespace Unity.Netcode
|
||||
if (!AlwaysReplicateAsRoot && transform.parent != null)
|
||||
{
|
||||
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (parentNetworkObject)
|
||||
{
|
||||
obj.Header.HasParent = true;
|
||||
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
|
||||
}
|
||||
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
|
||||
{
|
||||
obj.Header.HasTransform = true;
|
||||
obj.Transform = new SceneObject.TransformData
|
||||
// In-scene placed NetworkObjects parented under GameObjects with no NetworkObject
|
||||
// should set the has parent flag and preserve the world position stays value
|
||||
if (parentNetworkObject == null && obj.IsSceneObject)
|
||||
{
|
||||
Position = transform.position,
|
||||
Rotation = transform.rotation
|
||||
};
|
||||
obj.HasParent = true;
|
||||
obj.WorldPositionStays = m_CachedWorldPositionStays;
|
||||
}
|
||||
}
|
||||
|
||||
var (isReparented, latestParent) = GetNetworkParenting();
|
||||
obj.Header.IsReparented = isReparented;
|
||||
if (isReparented)
|
||||
if (parentNetworkObject != null)
|
||||
{
|
||||
obj.HasParent = true;
|
||||
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
|
||||
obj.WorldPositionStays = m_CachedWorldPositionStays;
|
||||
var latestParent = GetNetworkParenting();
|
||||
var isLatestParentSet = latestParent != null && latestParent.HasValue;
|
||||
obj.IsLatestParentSet = isLatestParentSet;
|
||||
if (isLatestParentSet)
|
||||
@@ -1136,6 +1350,43 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
|
||||
{
|
||||
obj.HasTransform = true;
|
||||
|
||||
// We start with the default AutoObjectParentSync values to determine which transform space we will
|
||||
// be synchronizing clients with.
|
||||
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
|
||||
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
|
||||
|
||||
// If auto object synchronization is turned off
|
||||
if (!AutoObjectParentSync)
|
||||
{
|
||||
// We always synchronize position and rotation world space relative
|
||||
syncRotationPositionLocalSpaceRelative = false;
|
||||
// Scale is special, it synchronizes local space relative if it has a
|
||||
// parent since applying the world space scale under a parent with scale
|
||||
// will result in the improper scale for the child
|
||||
syncScaleLocalSpaceRelative = obj.HasParent;
|
||||
}
|
||||
|
||||
|
||||
obj.Transform = new SceneObject.TransformData
|
||||
{
|
||||
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
|
||||
// values as opposed world space values.
|
||||
Position = syncRotationPositionLocalSpaceRelative ? transform.localPosition : transform.position,
|
||||
Rotation = syncRotationPositionLocalSpaceRelative ? transform.localRotation : transform.rotation,
|
||||
|
||||
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can
|
||||
// impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale
|
||||
// which can be thought of as "world space scale".
|
||||
// More information:
|
||||
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
|
||||
Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale,
|
||||
};
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1144,54 +1395,47 @@ namespace Unity.Netcode
|
||||
/// when the client is approved or during a scene transition
|
||||
/// </summary>
|
||||
/// <param name="sceneObject">Deserialized scene object data</param>
|
||||
/// <param name="variableData">reader for the NetworkVariable data</param>
|
||||
/// <param name="reader">FastBufferReader for the NetworkVariable data</param>
|
||||
/// <param name="networkManager">NetworkManager instance</param>
|
||||
/// <returns>optional to use NetworkObject deserialized</returns>
|
||||
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager)
|
||||
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager)
|
||||
{
|
||||
Vector3? position = null;
|
||||
Quaternion? rotation = null;
|
||||
ulong? parentNetworkId = null;
|
||||
int? networkSceneHandle = null;
|
||||
|
||||
if (sceneObject.Header.HasTransform)
|
||||
{
|
||||
position = sceneObject.Transform.Position;
|
||||
rotation = sceneObject.Transform.Rotation;
|
||||
}
|
||||
|
||||
if (sceneObject.Header.HasParent)
|
||||
{
|
||||
parentNetworkId = sceneObject.ParentObjectId;
|
||||
}
|
||||
|
||||
if (sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
networkSceneHandle = sceneObject.NetworkSceneHandle;
|
||||
}
|
||||
|
||||
//Attempt to create a local NetworkObject
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(
|
||||
sceneObject.Header.IsSceneObject, sceneObject.Header.Hash,
|
||||
sceneObject.Header.OwnerClientId, parentNetworkId, networkSceneHandle, position, rotation, sceneObject.Header.IsReparented);
|
||||
|
||||
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
// Log the error that the NetworkObject failed to construct
|
||||
Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Header.Hash}.");
|
||||
if (networkManager.LogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}.");
|
||||
}
|
||||
|
||||
// If we failed to load this NetworkObject, then skip past the network variable data
|
||||
variableData.ReadValueSafe(out ushort varSize);
|
||||
variableData.Seek(variableData.Position + varSize);
|
||||
try
|
||||
{
|
||||
// If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data
|
||||
reader.ReadValueSafe(out ushort networkBehaviourSynchronizationDataLength);
|
||||
reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
|
||||
// We have nothing left to do here.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Spawn the NetworkObject(
|
||||
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false);
|
||||
// This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning
|
||||
// in order to be able to determine which NetworkVariables the client will be allowed to read.
|
||||
networkObject.OwnerClientId = sceneObject.OwnerClientId;
|
||||
|
||||
// Synchronize NetworkBehaviours
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
|
||||
|
||||
// Spawn the NetworkObject
|
||||
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false);
|
||||
|
||||
return networkObject;
|
||||
}
|
||||
@@ -1211,9 +1455,9 @@ namespace Unity.Netcode
|
||||
var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash);
|
||||
return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash;
|
||||
}
|
||||
else if (NetworkManager.NetworkConfig.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash))
|
||||
if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.TryGetValue(GlobalObjectIdHash, out uint hash))
|
||||
{
|
||||
return NetworkManager.NetworkConfig.OverrideToNetworkPrefab[GlobalObjectIdHash];
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
248
Runtime/Hashing/XXHash.cs
Normal file
248
Runtime/Hashing/XXHash.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal static class XXHash
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe uint Hash32(byte* input, int length, uint seed = 0)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
const uint prime1 = 2654435761u;
|
||||
const uint prime2 = 2246822519u;
|
||||
const uint prime3 = 3266489917u;
|
||||
const uint prime4 = 0668265263u;
|
||||
const uint prime5 = 0374761393u;
|
||||
|
||||
uint hash = seed + prime5;
|
||||
|
||||
if (length >= 16)
|
||||
{
|
||||
uint val0 = seed + prime1 + prime2;
|
||||
uint val1 = seed + prime2;
|
||||
uint val2 = seed + 0;
|
||||
uint val3 = seed - prime1;
|
||||
|
||||
int count = length >> 4;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var pos0 = *(uint*)(input + 0);
|
||||
var pos1 = *(uint*)(input + 4);
|
||||
var pos2 = *(uint*)(input + 8);
|
||||
var pos3 = *(uint*)(input + 12);
|
||||
|
||||
val0 += pos0 * prime2;
|
||||
val0 = (val0 << 13) | (val0 >> (32 - 13));
|
||||
val0 *= prime1;
|
||||
|
||||
val1 += pos1 * prime2;
|
||||
val1 = (val1 << 13) | (val1 >> (32 - 13));
|
||||
val1 *= prime1;
|
||||
|
||||
val2 += pos2 * prime2;
|
||||
val2 = (val2 << 13) | (val2 >> (32 - 13));
|
||||
val2 *= prime1;
|
||||
|
||||
val3 += pos3 * prime2;
|
||||
val3 = (val3 << 13) | (val3 >> (32 - 13));
|
||||
val3 *= prime1;
|
||||
|
||||
input += 16;
|
||||
}
|
||||
|
||||
hash = ((val0 << 01) | (val0 >> (32 - 01))) +
|
||||
((val1 << 07) | (val1 >> (32 - 07))) +
|
||||
((val2 << 12) | (val2 >> (32 - 12))) +
|
||||
((val3 << 18) | (val3 >> (32 - 18)));
|
||||
}
|
||||
|
||||
hash += (uint)length;
|
||||
|
||||
length &= 15;
|
||||
while (length >= 4)
|
||||
{
|
||||
hash += *(uint*)input * prime3;
|
||||
hash = ((hash << 17) | (hash >> (32 - 17))) * prime4;
|
||||
input += 4;
|
||||
length -= 4;
|
||||
}
|
||||
while (length > 0)
|
||||
{
|
||||
hash += *input * prime5;
|
||||
hash = ((hash << 11) | (hash >> (32 - 11))) * prime1;
|
||||
++input;
|
||||
--length;
|
||||
}
|
||||
|
||||
hash ^= hash >> 15;
|
||||
hash *= prime2;
|
||||
hash ^= hash >> 13;
|
||||
hash *= prime3;
|
||||
hash ^= hash >> 16;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ulong Hash64(byte* input, int length, uint seed = 0)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
const ulong prime1 = 11400714785074694791ul;
|
||||
const ulong prime2 = 14029467366897019727ul;
|
||||
const ulong prime3 = 01609587929392839161ul;
|
||||
const ulong prime4 = 09650029242287828579ul;
|
||||
const ulong prime5 = 02870177450012600261ul;
|
||||
|
||||
ulong hash = seed + prime5;
|
||||
|
||||
if (length >= 32)
|
||||
{
|
||||
ulong val0 = seed + prime1 + prime2;
|
||||
ulong val1 = seed + prime2;
|
||||
ulong val2 = seed + 0;
|
||||
ulong val3 = seed - prime1;
|
||||
|
||||
int count = length >> 5;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var pos0 = *(ulong*)(input + 0);
|
||||
var pos1 = *(ulong*)(input + 8);
|
||||
var pos2 = *(ulong*)(input + 16);
|
||||
var pos3 = *(ulong*)(input + 24);
|
||||
|
||||
val0 += pos0 * prime2;
|
||||
val0 = (val0 << 31) | (val0 >> (64 - 31));
|
||||
val0 *= prime1;
|
||||
|
||||
val1 += pos1 * prime2;
|
||||
val1 = (val1 << 31) | (val1 >> (64 - 31));
|
||||
val1 *= prime1;
|
||||
|
||||
val2 += pos2 * prime2;
|
||||
val2 = (val2 << 31) | (val2 >> (64 - 31));
|
||||
val2 *= prime1;
|
||||
|
||||
val3 += pos3 * prime2;
|
||||
val3 = (val3 << 31) | (val3 >> (64 - 31));
|
||||
val3 *= prime1;
|
||||
|
||||
input += 32;
|
||||
}
|
||||
|
||||
hash = ((val0 << 01) | (val0 >> (64 - 01))) +
|
||||
((val1 << 07) | (val1 >> (64 - 07))) +
|
||||
((val2 << 12) | (val2 >> (64 - 12))) +
|
||||
((val3 << 18) | (val3 >> (64 - 18)));
|
||||
|
||||
val0 *= prime2;
|
||||
val0 = (val0 << 31) | (val0 >> (64 - 31));
|
||||
val0 *= prime1;
|
||||
hash ^= val0;
|
||||
hash = hash * prime1 + prime4;
|
||||
|
||||
val1 *= prime2;
|
||||
val1 = (val1 << 31) | (val1 >> (64 - 31));
|
||||
val1 *= prime1;
|
||||
hash ^= val1;
|
||||
hash = hash * prime1 + prime4;
|
||||
|
||||
val2 *= prime2;
|
||||
val2 = (val2 << 31) | (val2 >> (64 - 31));
|
||||
val2 *= prime1;
|
||||
hash ^= val2;
|
||||
hash = hash * prime1 + prime4;
|
||||
|
||||
val3 *= prime2;
|
||||
val3 = (val3 << 31) | (val3 >> (64 - 31));
|
||||
val3 *= prime1;
|
||||
hash ^= val3;
|
||||
hash = hash * prime1 + prime4;
|
||||
}
|
||||
|
||||
hash += (ulong)length;
|
||||
|
||||
length &= 31;
|
||||
while (length >= 8)
|
||||
{
|
||||
ulong lane = *(ulong*)input * prime2;
|
||||
lane = ((lane << 31) | (lane >> (64 - 31))) * prime1;
|
||||
hash ^= lane;
|
||||
hash = ((hash << 27) | (hash >> (64 - 27))) * prime1 + prime4;
|
||||
input += 8;
|
||||
length -= 8;
|
||||
}
|
||||
if (length >= 4)
|
||||
{
|
||||
hash ^= *(uint*)input * prime1;
|
||||
hash = ((hash << 23) | (hash >> (64 - 23))) * prime2 + prime3;
|
||||
input += 4;
|
||||
length -= 4;
|
||||
}
|
||||
while (length > 0)
|
||||
{
|
||||
hash ^= *input * prime5;
|
||||
hash = ((hash << 11) | (hash >> (64 - 11))) * prime1;
|
||||
++input;
|
||||
--length;
|
||||
}
|
||||
|
||||
hash ^= hash >> 33;
|
||||
hash *= prime2;
|
||||
hash ^= hash >> 29;
|
||||
hash *= prime3;
|
||||
hash ^= hash >> 32;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Hash32(this byte[] buffer)
|
||||
{
|
||||
int length = buffer.Length;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pointer = buffer)
|
||||
{
|
||||
return Hash32(pointer, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Hash32(this string text) => Hash32(Encoding.UTF8.GetBytes(text));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Hash32(this Type type) => Hash32(type.FullName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Hash32<T>() => Hash32(typeof(T));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64(this byte[] buffer)
|
||||
{
|
||||
int length = buffer.Length;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pointer = buffer)
|
||||
{
|
||||
return Hash64(pointer, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64(this string text) => Hash64(Encoding.UTF8.GetBytes(text));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64(this Type type) => Hash64(type.FullName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64<T>() => Hash64(typeof(T));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5aa7a49e9e694f148d810d34577546b
|
||||
guid: c3077af091aa443acbdea9d3e97727b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c61e8fe9a68a486fbbc3128d233ded2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf89ecbf6f9954c8ea6d0848b1e79d87
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,318 +0,0 @@
|
||||
// <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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,26 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal struct BatchHeader : INetworkSerializeByMemcpy
|
||||
{
|
||||
internal const ushort MagicValue = 0x1160;
|
||||
/// <summary>
|
||||
/// A magic number to detect corrupt messages.
|
||||
/// Always set to k_MagicValue
|
||||
/// </summary>
|
||||
public ushort Magic;
|
||||
|
||||
/// <summary>
|
||||
/// Total number of bytes in the batch.
|
||||
/// </summary>
|
||||
public int BatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the message to detect corrupt messages.
|
||||
/// </summary>
|
||||
public ulong BatchHash;
|
||||
|
||||
/// <summary>
|
||||
/// Total number of messages in the batch.
|
||||
/// </summary>
|
||||
public ushort BatchSize;
|
||||
public ushort BatchCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -68,9 +69,23 @@ namespace Unity.Netcode
|
||||
|
||||
if (clientIds == null)
|
||||
{
|
||||
throw new ArgumentNullException("You must pass in a valid clientId List");
|
||||
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
|
||||
}
|
||||
|
||||
if (m_NetworkManager.IsHost)
|
||||
{
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
{
|
||||
if (clientIds[i] == m_NetworkManager.LocalClientId)
|
||||
{
|
||||
InvokeUnnamedMessage(
|
||||
m_NetworkManager.LocalClientId,
|
||||
new FastBufferReader(messageBuffer, Allocator.None),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
var message = new UnnamedMessage
|
||||
{
|
||||
SendData = messageBuffer
|
||||
@@ -92,6 +107,18 @@ namespace Unity.Netcode
|
||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||
{
|
||||
if (m_NetworkManager.IsHost)
|
||||
{
|
||||
if (clientId == m_NetworkManager.LocalClientId)
|
||||
{
|
||||
InvokeUnnamedMessage(
|
||||
m_NetworkManager.LocalClientId,
|
||||
new FastBufferReader(messageBuffer, Allocator.None),
|
||||
0
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var message = new UnnamedMessage
|
||||
{
|
||||
SendData = messageBuffer
|
||||
@@ -220,6 +247,20 @@ namespace Unity.Netcode
|
||||
hash = XXHash.Hash64(messageName);
|
||||
break;
|
||||
}
|
||||
if (m_NetworkManager.IsHost)
|
||||
{
|
||||
if (clientId == m_NetworkManager.LocalClientId)
|
||||
{
|
||||
InvokeNamedMessage(
|
||||
hash,
|
||||
m_NetworkManager.LocalClientId,
|
||||
new FastBufferReader(messageStream, Allocator.None),
|
||||
0
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var message = new NamedMessage
|
||||
{
|
||||
@@ -251,7 +292,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (clientIds == null)
|
||||
{
|
||||
throw new ArgumentNullException("You must pass in a valid clientId List");
|
||||
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
|
||||
}
|
||||
|
||||
ulong hash = 0;
|
||||
@@ -264,6 +305,21 @@ namespace Unity.Netcode
|
||||
hash = XXHash.Hash64(messageName);
|
||||
break;
|
||||
}
|
||||
if (m_NetworkManager.IsHost)
|
||||
{
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
{
|
||||
if (clientIds[i] == m_NetworkManager.LocalClientId)
|
||||
{
|
||||
InvokeNamedMessage(
|
||||
hash,
|
||||
m_NetworkManager.LocalClientId,
|
||||
new FastBufferReader(messageStream, Allocator.None),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
var message = new NamedMessage
|
||||
{
|
||||
Hash = hash,
|
||||
|
||||
50
Runtime/Messaging/DisconnectReasonMessage.cs
Normal file
50
Runtime/Messaging/DisconnectReasonMessage.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct DisconnectReasonMessage : INetworkMessage
|
||||
{
|
||||
public string Reason;
|
||||
|
||||
public int Version => 0;
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
string reasonSent = Reason;
|
||||
if (reasonSent == null)
|
||||
{
|
||||
reasonSent = string.Empty;
|
||||
}
|
||||
|
||||
// Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message
|
||||
// itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion
|
||||
// on this side of things - we just have to make sure the receiving side knows what version we sent it,
|
||||
// since whoever has the higher version number is responsible for versioning and they may be the one
|
||||
// with the higher version number.
|
||||
BytePacker.WriteValueBitPacked(writer, Version);
|
||||
|
||||
if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent)))
|
||||
{
|
||||
writer.WriteValue(reasonSent);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValueSafe(string.Empty);
|
||||
NetworkLog.LogWarning(
|
||||
"Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string.");
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
// Since we don't get a ConnectionApprovedMessage, the version for this message is encded with the message
|
||||
// itself. This will override what we got from MessagingSystem... which will always be 0 here.
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out receivedMessageVersion);
|
||||
reader.ReadValueSafe(out Reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).DisconnectReason = Reason;
|
||||
}
|
||||
};
|
||||
}
|
||||
11
Runtime/Messaging/DisconnectReasonMessage.cs.meta
Normal file
11
Runtime/Messaging/DisconnectReasonMessage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7742516058394f96999464f3ea32c71
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -40,8 +40,9 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal interface INetworkMessage
|
||||
{
|
||||
void Serialize(FastBufferWriter writer);
|
||||
bool Deserialize(FastBufferReader reader, ref NetworkContext context);
|
||||
void Serialize(FastBufferWriter writer, int targetVersion);
|
||||
bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion);
|
||||
void Handle(ref NetworkContext context);
|
||||
int Version { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,26 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(this);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
reader.ReadValueSafe(out this);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ConnectionApprovedMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong OwnerClientId;
|
||||
public int NetworkTick;
|
||||
|
||||
@@ -13,14 +15,26 @@ namespace Unity.Netcode
|
||||
|
||||
private FastBufferReader m_ReceivedSceneObjectData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public NativeArray<MessageVersionData> MessageVersions;
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int)))
|
||||
// ============================================================
|
||||
// BEGIN FORBIDDEN SEGMENT
|
||||
// DO NOT CHANGE THIS HEADER. Everything added to this message
|
||||
// must go AFTER the message version header.
|
||||
// ============================================================
|
||||
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
|
||||
foreach (var messageVersion in MessageVersions)
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
|
||||
messageVersion.Serialize(writer);
|
||||
}
|
||||
writer.WriteValue(OwnerClientId);
|
||||
writer.WriteValue(NetworkTick);
|
||||
// ============================================================
|
||||
// END FORBIDDEN SEGMENT
|
||||
// ============================================================
|
||||
|
||||
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
||||
|
||||
uint sceneObjectCount = 0;
|
||||
if (SpawnedObjectsList != null)
|
||||
@@ -39,17 +53,19 @@ namespace Unity.Netcode
|
||||
++sceneObjectCount;
|
||||
}
|
||||
}
|
||||
|
||||
writer.Seek(pos);
|
||||
writer.WriteValue(sceneObjectCount);
|
||||
// Can't pack this value because its space is reserved, so it needs to always use all the reserved space.
|
||||
writer.WriteValueSafe(sceneObjectCount);
|
||||
writer.Seek(writer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(sceneObjectCount);
|
||||
writer.WriteValueSafe(sceneObjectCount);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
@@ -57,13 +73,36 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int)))
|
||||
// ============================================================
|
||||
// BEGIN FORBIDDEN SEGMENT
|
||||
// DO NOT CHANGE THIS HEADER. Everything added to this message
|
||||
// must go AFTER the message version header.
|
||||
// ============================================================
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out int length);
|
||||
var messageHashesInOrder = new NativeArray<uint>(length, Allocator.Temp);
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
|
||||
}
|
||||
var messageVersion = new MessageVersionData();
|
||||
messageVersion.Deserialize(reader);
|
||||
networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
|
||||
messageHashesInOrder[i] = messageVersion.Hash;
|
||||
|
||||
reader.ReadValue(out OwnerClientId);
|
||||
reader.ReadValue(out NetworkTick);
|
||||
// Update the received version since this message will always be passed version 0, due to the map not
|
||||
// being initialized until just now.
|
||||
var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
|
||||
if (messageType == typeof(ConnectionApprovedMessage))
|
||||
{
|
||||
receivedMessageVersion = messageVersion.Version;
|
||||
}
|
||||
}
|
||||
networkManager.MessagingSystem.SetServerMessageOrder(messageHashesInOrder);
|
||||
messageHashesInOrder.Dispose();
|
||||
// ============================================================
|
||||
// END FORBIDDEN SEGMENT
|
||||
// ============================================================
|
||||
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
||||
m_ReceivedSceneObjectData = reader;
|
||||
return true;
|
||||
}
|
||||
@@ -79,12 +118,13 @@ namespace Unity.Netcode
|
||||
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
|
||||
|
||||
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
|
||||
networkManager.IsApproved = true;
|
||||
|
||||
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
|
||||
if (!networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
networkManager.SpawnManager.DestroySceneObjects();
|
||||
m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount);
|
||||
m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount);
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ConnectionRequestMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong ConfigHash;
|
||||
|
||||
public byte[] ConnectionData;
|
||||
|
||||
public bool ShouldSendConnectionData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public NativeArray<MessageVersionData> MessageVersions;
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
// ============================================================
|
||||
// BEGIN FORBIDDEN SEGMENT
|
||||
// DO NOT CHANGE THIS HEADER. Everything added to this message
|
||||
// must go AFTER the message version header.
|
||||
// ============================================================
|
||||
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
|
||||
foreach (var messageVersion in MessageVersions)
|
||||
{
|
||||
messageVersion.Serialize(writer);
|
||||
}
|
||||
// ============================================================
|
||||
// END FORBIDDEN SEGMENT
|
||||
// ============================================================
|
||||
|
||||
if (ShouldSendConnectionData)
|
||||
{
|
||||
writer.WriteValueSafe(ConfigHash);
|
||||
@@ -21,7 +41,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsServer)
|
||||
@@ -29,6 +49,30 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// BEGIN FORBIDDEN SEGMENT
|
||||
// DO NOT CHANGE THIS HEADER. Everything added to this message
|
||||
// must go AFTER the message version header.
|
||||
// ============================================================
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out int length);
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
var messageVersion = new MessageVersionData();
|
||||
messageVersion.Deserialize(reader);
|
||||
networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
|
||||
|
||||
// Update the received version since this message will always be passed version 0, due to the map not
|
||||
// being initialized until just now.
|
||||
var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
|
||||
if (messageType == typeof(ConnectionRequestMessage))
|
||||
{
|
||||
receivedMessageVersion = messageVersion.Version;
|
||||
}
|
||||
}
|
||||
// ============================================================
|
||||
// END FORBIDDEN SEGMENT
|
||||
// ============================================================
|
||||
|
||||
if (networkManager.NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>()))
|
||||
|
||||
@@ -2,15 +2,17 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct CreateObjectMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public NetworkObject.SceneObject ObjectInfo;
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
ObjectInfo.Serialize(writer);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
@@ -21,7 +23,7 @@ namespace Unity.Netcode
|
||||
ObjectInfo.Deserialize(reader);
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
@@ -2,15 +2,18 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(this);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
writer.WriteValueSafe(DestroyGameObject);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
@@ -18,7 +21,8 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out this);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
reader.ReadValueSafe(out DestroyGameObject);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
|
||||
23
Runtime/Messaging/Messages/MessageMetadata.cs
Normal file
23
Runtime/Messaging/Messages/MessageMetadata.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Conveys a version number on a remote node for the given message (identified by its hash)
|
||||
/// </summary>
|
||||
internal struct MessageVersionData
|
||||
{
|
||||
public uint Hash;
|
||||
public int Version;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe(Hash);
|
||||
BytePacker.WriteValueBitPacked(writer, Version);
|
||||
}
|
||||
|
||||
public void Deserialize(FastBufferReader reader)
|
||||
{
|
||||
reader.ReadValueSafe(out Hash);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Messaging/Messages/MessageMetadata.cs.meta
Normal file
3
Runtime/Messaging/Messages/MessageMetadata.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 754d727b316b4263a2fa0d4c54fdad52
|
||||
timeCreated: 1666895514
|
||||
@@ -2,18 +2,20 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct NamedMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong Hash;
|
||||
public FastBufferWriter SendData;
|
||||
|
||||
private FastBufferReader m_ReceiveData;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(Hash);
|
||||
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
reader.ReadValueSafe(out Hash);
|
||||
m_ReceiveData = reader;
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal struct NetworkVariableDeltaMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
public ushort NetworkBehaviourIndex;
|
||||
|
||||
@@ -21,15 +23,15 @@ namespace Unity.Netcode
|
||||
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
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);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
|
||||
|
||||
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
@@ -38,7 +40,7 @@ namespace Unity.Netcode
|
||||
// This var does not belong to the currently iterating delivery group.
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
BytePacker.WriteValueBitPacked(writer, (ushort)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -62,11 +64,21 @@ namespace Unity.Netcode
|
||||
shouldWrite = false;
|
||||
}
|
||||
|
||||
// The object containing the behaviour we're about to process is about to be shown to this client
|
||||
// As a result, the client will get the fully serialized NetworkVariable and would be confused by
|
||||
// an extraneous delta
|
||||
if (NetworkBehaviour.NetworkManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
|
||||
NetworkBehaviour.NetworkManager.ObjectsToShowToClient[TargetClientId]
|
||||
.Contains(NetworkBehaviour.NetworkObject))
|
||||
{
|
||||
shouldWrite = false;
|
||||
}
|
||||
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, 0);
|
||||
BytePacker.WriteValueBitPacked(writer, (ushort)0);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -94,12 +106,6 @@ namespace Unity.Netcode
|
||||
networkVariable.WriteDelta(writer);
|
||||
}
|
||||
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
|
||||
}
|
||||
|
||||
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
||||
TargetClientId,
|
||||
NetworkBehaviour.NetworkObject,
|
||||
@@ -110,15 +116,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
|
||||
{
|
||||
throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
|
||||
reader.ReadValue(out NetworkObjectId);
|
||||
reader.ReadValue(out NetworkBehaviourIndex);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
|
||||
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides
|
||||
/// have the same message types in the same positions in
|
||||
/// - MessagingSystem.m_MessageHandlers
|
||||
/// - MessagingSystem.m_ReverseTypeMap
|
||||
/// even if one side has extra messages (compilation, version, patch, or platform differences, etc...)
|
||||
///
|
||||
/// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning
|
||||
/// of the mapping, to guarantee they can be exchanged before the two sides share their ordering
|
||||
/// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time
|
||||
/// </summary>
|
||||
internal struct OrderingMessage : INetworkMessage
|
||||
{
|
||||
public int Order;
|
||||
public uint Hash;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
|
||||
}
|
||||
|
||||
writer.WriteValue(Order);
|
||||
writer.WriteValue(Hash);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
|
||||
{
|
||||
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
|
||||
}
|
||||
|
||||
reader.ReadValue(out Order);
|
||||
reader.ReadValue(out Hash);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,65 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ParentSyncMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public ulong NetworkObjectId;
|
||||
|
||||
public bool IsReparented;
|
||||
private byte m_BitField;
|
||||
|
||||
public bool WorldPositionStays
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 0);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 0, value);
|
||||
}
|
||||
|
||||
//If(Metadata.IsReparented)
|
||||
public bool IsLatestParentSet;
|
||||
public bool IsLatestParentSet
|
||||
{
|
||||
get => ByteUtility.GetBit(m_BitField, 1);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 1, value);
|
||||
}
|
||||
|
||||
//If(IsLatestParentSet)
|
||||
public ulong? LatestParent;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
// Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
|
||||
public bool RemoveParent
|
||||
{
|
||||
writer.WriteValueSafe(NetworkObjectId);
|
||||
writer.WriteValueSafe(IsReparented);
|
||||
if (IsReparented)
|
||||
{
|
||||
writer.WriteValueSafe(IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
writer.WriteValueSafe((ulong)LatestParent);
|
||||
}
|
||||
}
|
||||
get => ByteUtility.GetBit(m_BitField, 2);
|
||||
set => ByteUtility.SetBit(ref m_BitField, 2, value);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
// These additional properties are used to synchronize clients with the current position,
|
||||
// rotation, and scale after parenting/de-parenting (world/local space relative). This
|
||||
// allows users to control the final child's transform values without having to have a
|
||||
// NetworkTransform component on the child. (i.e. picking something up)
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||
writer.WriteValueSafe(m_BitField);
|
||||
if (!RemoveParent)
|
||||
{
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Whether parenting or removing a parent, we always update the position, rotation, and scale
|
||||
writer.WriteValueSafe(Position);
|
||||
writer.WriteValueSafe(Rotation);
|
||||
writer.WriteValueSafe(Scale);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
@@ -34,24 +67,27 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out NetworkObjectId);
|
||||
reader.ReadValueSafe(out IsReparented);
|
||||
if (IsReparented)
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||
reader.ReadValueSafe(out m_BitField);
|
||||
if (!RemoveParent)
|
||||
{
|
||||
reader.ReadValueSafe(out IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
}
|
||||
|
||||
// Whether parenting or removing a parent, we always update the position, rotation, and scale
|
||||
reader.ReadValueSafe(out Position);
|
||||
reader.ReadValueSafe(out Rotation);
|
||||
reader.ReadValueSafe(out Scale);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -59,8 +95,22 @@ namespace Unity.Netcode
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
networkObject.SetNetworkParenting(IsReparented, LatestParent);
|
||||
networkObject.ApplyNetworkParenting();
|
||||
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
|
||||
networkObject.ApplyNetworkParenting(RemoveParent);
|
||||
|
||||
// We set all of the transform values after parenting as they are
|
||||
// the values of the server-side post-parenting transform values
|
||||
if (!WorldPositionStays)
|
||||
{
|
||||
networkObject.transform.localPosition = Position;
|
||||
networkObject.transform.localRotation = Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject.transform.position = Position;
|
||||
networkObject.transform.rotation = Rotation;
|
||||
}
|
||||
networkObject.transform.localScale = Scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,17 @@ namespace Unity.Netcode
|
||||
{
|
||||
public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<RpcMetadata>() + payload.Length))
|
||||
{
|
||||
throw new OverflowException("Not enough space in the buffer to store RPC data.");
|
||||
}
|
||||
|
||||
writer.WriteValue(metadata);
|
||||
writer.WriteBytes(payload.GetUnsafePtr(), payload.Length);
|
||||
BytePacker.WriteValueBitPacked(writer, metadata.NetworkObjectId);
|
||||
BytePacker.WriteValueBitPacked(writer, metadata.NetworkBehaviourId);
|
||||
BytePacker.WriteValueBitPacked(writer, metadata.NetworkRpcMethodId);
|
||||
writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length);
|
||||
}
|
||||
|
||||
public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload)
|
||||
{
|
||||
int metadataSize = FastBufferWriter.GetWriteSize<RpcMetadata>();
|
||||
if (!reader.TryBeginRead(metadataSize))
|
||||
{
|
||||
throw new InvalidOperationException("Not enough data in the buffer to read RPC meta.");
|
||||
}
|
||||
|
||||
reader.ReadValue(out metadata);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkRpcMethodId);
|
||||
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
|
||||
@@ -46,7 +39,7 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize);
|
||||
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||
@@ -92,17 +85,19 @@ namespace Unity.Netcode
|
||||
|
||||
internal struct ServerRpcMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public RpcMetadata Metadata;
|
||||
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||
}
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
}
|
||||
@@ -125,17 +120,19 @@ namespace Unity.Netcode
|
||||
|
||||
internal struct ClientRpcMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public RpcMetadata Metadata;
|
||||
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ namespace Unity.Netcode
|
||||
// like most of the other messages when we have some more time and can come back and refactor this.
|
||||
internal struct SceneEventMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public SceneEventData EventData;
|
||||
|
||||
private FastBufferReader m_ReceivedData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
EventData.Serialize(writer);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
m_ReceivedData = reader;
|
||||
return true;
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct ServerLogMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
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
|
||||
@@ -11,13 +13,13 @@ namespace Unity.Netcode
|
||||
public string Message;
|
||||
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(LogType);
|
||||
BytePacker.WriteValuePacked(writer, Message);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)
|
||||
|
||||
@@ -2,21 +2,23 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public int Tick;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteValueSafe(this);
|
||||
BytePacker.WriteValueBitPacked(writer, Tick);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
reader.ReadValueSafe(out this);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out Tick);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,17 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct UnnamedMessage : INetworkMessage
|
||||
{
|
||||
public int Version => 0;
|
||||
|
||||
public FastBufferWriter SendData;
|
||||
private FastBufferReader m_ReceivedData;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
|
||||
{
|
||||
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||
{
|
||||
m_ReceivedData = reader;
|
||||
return true;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
@@ -41,11 +42,12 @@ namespace Unity.Netcode
|
||||
{
|
||||
Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize);
|
||||
NetworkDelivery = delivery;
|
||||
BatchHeader = default;
|
||||
BatchHeader = new BatchHeader { Magic = BatchHeader.MagicValue };
|
||||
}
|
||||
}
|
||||
|
||||
internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system);
|
||||
internal delegate int VersionGetter();
|
||||
|
||||
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
|
||||
|
||||
@@ -56,6 +58,11 @@ namespace Unity.Netcode
|
||||
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
|
||||
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
|
||||
|
||||
// This is m_PerClientMessageVersion[clientId][messageType] = version
|
||||
private Dictionary<ulong, Dictionary<Type, int>> m_PerClientMessageVersions = new Dictionary<ulong, Dictionary<Type, int>>();
|
||||
private Dictionary<uint, Type> m_MessagesByHash = new Dictionary<uint, Type>();
|
||||
private Dictionary<Type, int> m_LocalVersions = new Dictionary<Type, int>();
|
||||
|
||||
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
|
||||
|
||||
private uint m_HighMessageType;
|
||||
@@ -74,12 +81,13 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
|
||||
|
||||
internal struct MessageWithHandler
|
||||
{
|
||||
public Type MessageType;
|
||||
public MessageHandler Handler;
|
||||
public VersionGetter GetVersion;
|
||||
}
|
||||
|
||||
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
|
||||
@@ -90,9 +98,8 @@ namespace Unity.Netcode
|
||||
// Those are the messages that must be delivered in order to allow re-ordering the others later
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" ||
|
||||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
|
||||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
|
||||
if (t.MessageType.FullName == typeof(ConnectionRequestMessage).FullName ||
|
||||
t.MessageType.FullName == typeof(ConnectionApprovedMessage).FullName)
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
@@ -100,9 +107,8 @@ namespace Unity.Netcode
|
||||
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
|
||||
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
|
||||
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
|
||||
if (t.MessageType.FullName != typeof(ConnectionRequestMessage).FullName &&
|
||||
t.MessageType.FullName != typeof(ConnectionApprovedMessage).FullName)
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
@@ -189,7 +195,25 @@ namespace Unity.Netcode
|
||||
|
||||
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
|
||||
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
|
||||
m_MessagesByHash[XXHash.Hash32(messageWithHandler.MessageType.FullName)] = messageWithHandler.MessageType;
|
||||
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
|
||||
m_LocalVersions[messageWithHandler.MessageType] = messageWithHandler.GetVersion();
|
||||
}
|
||||
|
||||
public int GetLocalVersion(Type messageType)
|
||||
{
|
||||
return m_LocalVersions[messageType];
|
||||
}
|
||||
|
||||
internal static string ByteArrayToString(byte[] ba, int offset, int count)
|
||||
{
|
||||
var hex = new StringBuilder(ba.Length * 2);
|
||||
for (int i = offset; i < offset + count; ++i)
|
||||
{
|
||||
hex.AppendFormat("{0:x2} ", ba[i]);
|
||||
}
|
||||
|
||||
return hex.ToString();
|
||||
}
|
||||
|
||||
internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float receiveTime)
|
||||
@@ -202,18 +226,38 @@ namespace Unity.Netcode
|
||||
new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
|
||||
if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
|
||||
{
|
||||
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
|
||||
NetworkLog.LogError("Received a packet too small to contain a BatchHeader. Ignoring it.");
|
||||
return;
|
||||
}
|
||||
|
||||
batchReader.ReadValue(out BatchHeader batchHeader);
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
if (batchHeader.Magic != BatchHeader.MagicValue)
|
||||
{
|
||||
m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length);
|
||||
NetworkLog.LogError($"Received a packet with an invalid Magic Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
|
||||
if (batchHeader.BatchSize != data.Count)
|
||||
{
|
||||
NetworkLog.LogError($"Received a packet with an invalid Batch Size Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Expected Size: {batchHeader.BatchSize}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var hash = XXHash.Hash64(batchReader.GetUnsafePtrAtCurrentPosition(), batchReader.Length - batchReader.Position);
|
||||
|
||||
if (hash != batchHeader.BatchHash)
|
||||
{
|
||||
NetworkLog.LogError($"Received a packet with an invalid Hash Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Received Hash: {batchHeader.BatchHash}, Calculated Hash: {hash}, Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
|
||||
}
|
||||
|
||||
for (var messageIdx = 0; messageIdx < batchHeader.BatchCount; ++messageIdx)
|
||||
{
|
||||
|
||||
var messageHeader = new MessageHeader();
|
||||
@@ -225,7 +269,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
|
||||
NetworkLog.LogError("Received a batch that didn't have enough data for all of its batches, ending early!");
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -233,7 +277,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
|
||||
{
|
||||
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
|
||||
NetworkLog.LogError("Received a message that claimed a size larger than the packet, ending early!");
|
||||
return;
|
||||
}
|
||||
m_IncomingMessageQueue.Add(new ReceiveQueueItem
|
||||
@@ -251,7 +295,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length);
|
||||
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,68 +314,53 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
|
||||
// This allows the server to tell the client which id it is using for which message and make sure the right
|
||||
// message is used when deserializing.
|
||||
internal void ReorderMessage(int desiredOrder, uint targetHash)
|
||||
internal Type GetMessageForHash(uint messageHash)
|
||||
{
|
||||
if (desiredOrder < 0)
|
||||
if (!m_MessagesByHash.ContainsKey(messageHash))
|
||||
{
|
||||
throw new ArgumentException("ReorderMessage desiredOrder must be positive");
|
||||
return null;
|
||||
}
|
||||
return m_MessagesByHash[messageHash];
|
||||
}
|
||||
|
||||
if (desiredOrder < m_ReverseTypeMap.Length &&
|
||||
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash)
|
||||
internal void SetVersion(ulong clientId, uint messageHash, int version)
|
||||
{
|
||||
if (!m_MessagesByHash.ContainsKey(messageHash))
|
||||
{
|
||||
// matching positions and hashes. All good.
|
||||
return;
|
||||
}
|
||||
var messageType = m_MessagesByHash[messageHash];
|
||||
|
||||
Debug.Log($"Unexpected hash for {desiredOrder}");
|
||||
|
||||
// Since the message at `desiredOrder` is not the expected one,
|
||||
// insert an empty placeholder and move the messages down
|
||||
var typesAsList = new List<Type>(m_ReverseTypeMap);
|
||||
|
||||
typesAsList.Insert(desiredOrder, null);
|
||||
var handlersAsList = new List<MessageHandler>(m_MessageHandlers);
|
||||
handlersAsList.Insert(desiredOrder, null);
|
||||
|
||||
// we added a dummy message, bump the end up
|
||||
m_HighMessageType++;
|
||||
|
||||
// Here, we rely on the server telling us about all messages, in order.
|
||||
// So, we know the handlers before desiredOrder are correct.
|
||||
// We start at desiredOrder to not shift them when we insert.
|
||||
int position = desiredOrder;
|
||||
bool found = false;
|
||||
while (position < typesAsList.Count)
|
||||
if (!m_PerClientMessageVersions.ContainsKey(clientId))
|
||||
{
|
||||
if (typesAsList[position] != null &&
|
||||
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
|
||||
m_PerClientMessageVersions[clientId] = new Dictionary<Type, int>();
|
||||
}
|
||||
|
||||
m_PerClientMessageVersions[clientId][messageType] = version;
|
||||
}
|
||||
|
||||
internal void SetServerMessageOrder(NativeArray<uint> messagesInIdOrder)
|
||||
{
|
||||
var oldHandlers = m_MessageHandlers;
|
||||
var oldTypes = m_MessageTypes;
|
||||
m_ReverseTypeMap = new Type[messagesInIdOrder.Length];
|
||||
m_MessageHandlers = new MessageHandler[messagesInIdOrder.Length];
|
||||
m_MessageTypes = new Dictionary<Type, uint>();
|
||||
|
||||
for (var i = 0; i < messagesInIdOrder.Length; ++i)
|
||||
{
|
||||
if (!m_MessagesByHash.ContainsKey(messagesInIdOrder[i]))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
position++;
|
||||
var messageType = m_MessagesByHash[messagesInIdOrder[i]];
|
||||
var oldId = oldTypes[messageType];
|
||||
var handler = oldHandlers[oldId];
|
||||
var newId = (uint)i;
|
||||
m_MessageTypes[messageType] = newId;
|
||||
m_MessageHandlers[newId] = handler;
|
||||
m_ReverseTypeMap[newId] = messageType;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
// Copy the handler and type to the right index
|
||||
|
||||
typesAsList[desiredOrder] = typesAsList[position];
|
||||
handlersAsList[desiredOrder] = handlersAsList[position];
|
||||
typesAsList.RemoveAt(position);
|
||||
handlersAsList.RemoveAt(position);
|
||||
|
||||
// we removed a copy after moving a message, reduce the high message index
|
||||
m_HighMessageType--;
|
||||
}
|
||||
|
||||
m_ReverseTypeMap = typesAsList.ToArray();
|
||||
m_MessageHandlers = handlersAsList.ToArray();
|
||||
}
|
||||
|
||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
|
||||
@@ -433,7 +462,7 @@ namespace Unity.Netcode
|
||||
m_SendQueues.Remove(clientId);
|
||||
}
|
||||
|
||||
private unsafe void CleanupDisconnectedClient(ulong clientId)
|
||||
private void CleanupDisconnectedClient(ulong clientId)
|
||||
{
|
||||
var queue = m_SendQueues[clientId];
|
||||
for (var i = 0; i < queue.Length; ++i)
|
||||
@@ -444,10 +473,67 @@ namespace Unity.Netcode
|
||||
queue.Dispose();
|
||||
}
|
||||
|
||||
internal void CleanupDisconnectedClients()
|
||||
{
|
||||
var removeList = new NativeList<ulong>(Allocator.Temp);
|
||||
foreach (var clientId in m_PerClientMessageVersions.Keys)
|
||||
{
|
||||
if (!m_SendQueues.ContainsKey(clientId))
|
||||
{
|
||||
removeList.Add(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var clientId in removeList)
|
||||
{
|
||||
m_PerClientMessageVersions.Remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public static int CreateMessageAndGetVersion<T>() where T : INetworkMessage, new()
|
||||
{
|
||||
return new T().Version;
|
||||
}
|
||||
|
||||
internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = false)
|
||||
{
|
||||
if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap))
|
||||
{
|
||||
if (forReceive)
|
||||
{
|
||||
Debug.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state.");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state.");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!versionMap.TryGetValue(type, out var messageVersion))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return messageVersion;
|
||||
}
|
||||
|
||||
public static void ReceiveMessage<T>(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new()
|
||||
{
|
||||
var message = new T();
|
||||
if (message.Deserialize(reader, ref context))
|
||||
var messageVersion = 0;
|
||||
// Special cases because these are the messages that carry the version info - thus the version info isn't
|
||||
// populated yet when we get these. The first part of these messages always has to be the version data
|
||||
// and can't change.
|
||||
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage))
|
||||
{
|
||||
messageVersion = system.GetMessageVersion(typeof(T), context.SenderId, true);
|
||||
if (messageVersion < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (message.Deserialize(reader, ref context, messageVersion))
|
||||
{
|
||||
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
@@ -485,16 +571,47 @@ namespace Unity.Netcode
|
||||
return 0;
|
||||
}
|
||||
|
||||
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
|
||||
var largestSerializedSize = 0;
|
||||
var sentMessageVersions = new NativeHashSet<int>(clientIds.Count, Allocator.Temp);
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
{
|
||||
var messageVersion = 0;
|
||||
// Special case because this is the message that carries the version info - thus the version info isn't
|
||||
// populated yet when we get this. The first part of this message always has to be the version data
|
||||
// and can't change.
|
||||
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
|
||||
{
|
||||
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
|
||||
if (messageVersion < 0)
|
||||
{
|
||||
// Client doesn't know this message exists, don't send it at all.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
if (sentMessageVersions.Contains(messageVersion))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
message.Serialize(tmpSerializer);
|
||||
sentMessageVersions.Add(messageVersion);
|
||||
|
||||
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds);
|
||||
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
|
||||
|
||||
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
|
||||
message.Serialize(tmpSerializer, messageVersion);
|
||||
|
||||
var size = SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds, messageVersion);
|
||||
largestSerializedSize = size > largestSerializedSize ? size : largestSerializedSize;
|
||||
}
|
||||
|
||||
sentMessageVersions.Dispose();
|
||||
|
||||
return largestSerializedSize;
|
||||
}
|
||||
|
||||
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds)
|
||||
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds, int messageVersionFilter)
|
||||
where TMessageType : INetworkMessage
|
||||
{
|
||||
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
|
||||
@@ -509,6 +626,25 @@ namespace Unity.Netcode
|
||||
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
{
|
||||
var messageVersion = 0;
|
||||
// Special case because this is the message that carries the version info - thus the version info isn't
|
||||
// populated yet when we get this. The first part of this message always has to be the version data
|
||||
// and can't change.
|
||||
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
|
||||
{
|
||||
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
|
||||
if (messageVersion < 0)
|
||||
{
|
||||
// Client doesn't know this message exists, don't send it at all.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (messageVersion != messageVersionFilter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var clientId = clientIds[i];
|
||||
|
||||
if (!CanSend(clientId, typeof(TMessageType), delivery))
|
||||
@@ -546,7 +682,7 @@ namespace Unity.Netcode
|
||||
|
||||
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
|
||||
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
|
||||
writeQueueItem.BatchHeader.BatchSize++;
|
||||
writeQueueItem.BatchHeader.BatchCount++;
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length);
|
||||
@@ -559,8 +695,22 @@ namespace Unity.Netcode
|
||||
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId)
|
||||
where TMessageType : INetworkMessage
|
||||
{
|
||||
var messageVersion = 0;
|
||||
// Special case because this is the message that carries the version info - thus the version info isn't
|
||||
// populated yet when we get this. The first part of this message always has to be the version data
|
||||
// and can't change.
|
||||
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
|
||||
{
|
||||
messageVersion = GetMessageVersion(typeof(TMessageType), clientId);
|
||||
if (messageVersion < 0)
|
||||
{
|
||||
// Client doesn't know this message exists, don't send it at all.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ulong* clientIds = stackalloc ulong[] { clientId };
|
||||
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
|
||||
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1), messageVersion);
|
||||
}
|
||||
|
||||
private struct PointerListWrapper<T> : IReadOnlyList<T>
|
||||
@@ -627,7 +777,7 @@ namespace Unity.Netcode
|
||||
for (var i = 0; i < sendQueueItem.Length; ++i)
|
||||
{
|
||||
ref var queueItem = ref sendQueueItem.ElementAt(i);
|
||||
if (queueItem.BatchHeader.BatchSize == 0)
|
||||
if (queueItem.BatchHeader.BatchCount == 0)
|
||||
{
|
||||
queueItem.Writer.Dispose();
|
||||
continue;
|
||||
@@ -635,23 +785,28 @@ namespace Unity.Netcode
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
|
||||
m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchCount, queueItem.Writer.Length, queueItem.NetworkDelivery);
|
||||
}
|
||||
|
||||
queueItem.Writer.Seek(0);
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
// Skipping the Verify and sneaking the write mark in because we know it's fine.
|
||||
queueItem.Writer.Handle->AllowedWriteMark = 2;
|
||||
queueItem.Writer.Handle->AllowedWriteMark = sizeof(BatchHeader);
|
||||
#endif
|
||||
queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(BatchHeader), queueItem.Writer.Length - sizeof(BatchHeader));
|
||||
|
||||
queueItem.BatchHeader.BatchSize = queueItem.Writer.Length;
|
||||
|
||||
queueItem.Writer.WriteValue(queueItem.BatchHeader);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
|
||||
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchCount, queueItem.Writer.Length, queueItem.NetworkDelivery);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -5,17 +5,14 @@ using Unity.Multiplayer.Tools;
|
||||
using Unity.Multiplayer.Tools.MetricTypes;
|
||||
using Unity.Multiplayer.Tools.NetStats;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class NetworkMetrics : INetworkMetrics
|
||||
{
|
||||
const ulong k_MaxMetricsPerFrame = 1000L;
|
||||
|
||||
static Dictionary<uint, string> s_SceneEventTypeNames;
|
||||
|
||||
static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
|
||||
private const ulong k_MaxMetricsPerFrame = 1000L;
|
||||
private static Dictionary<uint, string> s_SceneEventTypeNames;
|
||||
private static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
|
||||
|
||||
static NetworkMetrics()
|
||||
{
|
||||
@@ -531,7 +528,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetcodeObserver
|
||||
internal class NetcodeObserver
|
||||
{
|
||||
public static IMetricObserver Observer { get; } = MetricObserverFactory.Construct();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
class NetworkObjectProvider : INetworkObjectProvider
|
||||
internal class NetworkObjectProvider : INetworkObjectProvider
|
||||
{
|
||||
private readonly NetworkManager m_NetworkManager;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Unity.Netcode
|
||||
|
||||
public Object GetNetworkObject(ulong networkObjectId)
|
||||
{
|
||||
if(m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
|
||||
if (m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -11,7 +12,6 @@ namespace Unity.Netcode
|
||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
|
||||
|
||||
/// <summary>
|
||||
@@ -39,9 +39,13 @@ namespace Unity.Netcode
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
foreach (var value in values)
|
||||
// allow null IEnumerable<T> to mean "no values"
|
||||
if (values != null)
|
||||
{
|
||||
m_List.Add(value);
|
||||
foreach (var value in values)
|
||||
{
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +56,6 @@ namespace Unity.Netcode
|
||||
if (m_DirtyEvents.Length > 0)
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
m_ListAtLastReset.CopyFrom(m_List);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +68,13 @@ namespace Unity.Netcode
|
||||
|
||||
internal void MarkNetworkObjectDirty()
|
||||
{
|
||||
if (m_NetworkBehaviour == null)
|
||||
{
|
||||
Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " +
|
||||
"Are you modifying a NetworkList before the NetworkObject is spawned?");
|
||||
return;
|
||||
}
|
||||
|
||||
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
|
||||
}
|
||||
|
||||
@@ -127,26 +137,10 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
// The listAtLastReset mechanism was put in place to deal with duplicate adds
|
||||
// upon initial spawn. However, it causes issues with in-scene placed objects
|
||||
// due to difference in spawn order. In order to address this, we pick the right
|
||||
// list based on the type of object.
|
||||
bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false;
|
||||
if (isSceneObject)
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
|
||||
for (int i = 0; i < m_ListAtLastReset.Length; i++)
|
||||
{
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
{
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
|
||||
}
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +151,8 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out ushort count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -173,7 +168,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
m_List.Add(value);
|
||||
|
||||
if (OnListChanged != null)
|
||||
@@ -201,7 +197,8 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
|
||||
if (index < m_List.Length)
|
||||
{
|
||||
@@ -237,7 +234,8 @@ namespace Unity.Netcode
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
int index = m_List.IndexOf(value);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -299,7 +297,8 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
@@ -374,6 +373,12 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public void Add(T item)
|
||||
{
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
m_List.Add(item);
|
||||
|
||||
var listEvent = new NetworkListEvent<T>()
|
||||
@@ -389,6 +394,12 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
m_List.Clear();
|
||||
|
||||
var listEvent = new NetworkListEvent<T>()
|
||||
@@ -402,14 +413,20 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public bool Contains(T item)
|
||||
{
|
||||
int index = NativeArrayExtensions.IndexOf(m_List, item);
|
||||
int index = m_List.IndexOf(item);
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(T item)
|
||||
{
|
||||
int index = NativeArrayExtensions.IndexOf(m_List, item);
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
int index = m_List.IndexOf(item);
|
||||
if (index == -1)
|
||||
{
|
||||
return false;
|
||||
@@ -438,6 +455,12 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
if (index < m_List.Length)
|
||||
{
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
@@ -461,6 +484,12 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
m_List.RemoveAt(index);
|
||||
|
||||
var listEvent = new NetworkListEvent<T>()
|
||||
@@ -478,6 +507,12 @@ namespace Unity.Netcode
|
||||
get => m_List[index];
|
||||
set
|
||||
{
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
var previousValue = m_List[index];
|
||||
m_List[index] = value;
|
||||
|
||||
@@ -520,7 +555,6 @@ namespace Unity.Netcode
|
||||
public override void Dispose()
|
||||
{
|
||||
m_List.Dispose();
|
||||
m_ListAtLastReset.Dispose();
|
||||
m_DirtyEvents.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -10,7 +8,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
|
||||
public class NetworkVariable<T> : NetworkVariableBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
@@ -52,7 +50,7 @@ namespace Unity.Netcode
|
||||
set
|
||||
{
|
||||
// Compare bitwise
|
||||
if (ValueEquals(ref m_InternalValue, ref value))
|
||||
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -66,20 +64,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Compares two values of the same unmanaged type by underlying memory
|
||||
// Ignoring any overridden value checks
|
||||
// Size is fixed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe bool ValueEquals(ref T a, ref T b)
|
||||
{
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
|
||||
/// if there are subscribers to that event.
|
||||
@@ -115,7 +99,7 @@ namespace Unity.Netcode
|
||||
// would be stored in different fields
|
||||
|
||||
T previousValue = m_InternalValue;
|
||||
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
@@ -128,7 +112,7 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void ReadField(FastBufferReader reader)
|
||||
{
|
||||
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -17,6 +18,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private protected NetworkBehaviour m_NetworkBehaviour;
|
||||
|
||||
public NetworkBehaviour GetBehaviour()
|
||||
{
|
||||
return m_NetworkBehaviour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the NetworkVariable
|
||||
/// </summary>
|
||||
@@ -79,8 +85,15 @@ namespace Unity.Netcode
|
||||
public virtual void SetDirty(bool isDirty)
|
||||
{
|
||||
m_IsDirty = isDirty;
|
||||
if (m_IsDirty && m_NetworkBehaviour != null)
|
||||
|
||||
if (m_IsDirty)
|
||||
{
|
||||
if (m_NetworkBehaviour == null)
|
||||
{
|
||||
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
|
||||
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
|
||||
return;
|
||||
}
|
||||
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -18,7 +19,97 @@ namespace Unity.Netcode
|
||||
// Taking T as an in parameter like we do in other places would require making a copy
|
||||
// of it to pass it as a ref parameter.
|
||||
public void Write(FastBufferWriter writer, ref T value);
|
||||
public void Read(FastBufferReader reader, out T value);
|
||||
public void Read(FastBufferReader reader, ref T value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packing serializer for shorts
|
||||
/// </summary>
|
||||
internal class ShortSerializer : INetworkVariableSerializer<short>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref short value)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref short value)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packing serializer for shorts
|
||||
/// </summary>
|
||||
internal class UshortSerializer : INetworkVariableSerializer<ushort>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref ushort value)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref ushort value)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packing serializer for ints
|
||||
/// </summary>
|
||||
internal class IntSerializer : INetworkVariableSerializer<int>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref int value)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref int value)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packing serializer for ints
|
||||
/// </summary>
|
||||
internal class UintSerializer : INetworkVariableSerializer<uint>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref uint value)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref uint value)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packing serializer for longs
|
||||
/// </summary>
|
||||
internal class LongSerializer : INetworkVariableSerializer<long>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref long value)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref long value)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packing serializer for longs
|
||||
/// </summary>
|
||||
internal class UlongSerializer : INetworkVariableSerializer<ulong>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref ulong value)
|
||||
{
|
||||
BytePacker.WriteValueBitPacked(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref ulong value)
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,79 +126,80 @@ namespace Unity.Netcode
|
||||
{
|
||||
writer.WriteUnmanagedSafe(value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
public void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
reader.ReadUnmanagedSafe(out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do,
|
||||
/// but is implemented to get the data it needs using open instance delegates that are passed in
|
||||
/// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable
|
||||
/// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates
|
||||
/// circumvent that.)
|
||||
///
|
||||
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||
/// aren't known to actually contain those methods.
|
||||
/// Serializer for FixedStrings
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
internal delegate int GetLengthDelegate(ref T value);
|
||||
internal delegate void SetLengthDelegate(ref T value, int length);
|
||||
internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value);
|
||||
|
||||
internal GetLengthDelegate GetLength;
|
||||
internal SetLengthDelegate SetLength;
|
||||
internal GetUnsafePtrDelegate GetUnsafePtr;
|
||||
|
||||
public unsafe void Write(FastBufferWriter writer, ref T value)
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
int length = GetLength(ref value);
|
||||
byte* data = GetUnsafePtr(ref value);
|
||||
writer.WriteUnmanagedSafe(length);
|
||||
writer.WriteBytesSafe(data, length);
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
public unsafe void Read(FastBufferReader reader, out T value)
|
||||
public void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
value = new T();
|
||||
reader.ReadValueSafe(out int length);
|
||||
SetLength(ref value, length);
|
||||
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
|
||||
reader.ReadValueSafeInPlace(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for INetworkSerializable types, which does the same thing
|
||||
/// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method
|
||||
/// via open instance delegates passed in via reflection. This prevents needing T to meet any interface
|
||||
/// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read -
|
||||
/// reflection + Open Instance Delegates circumvent that.)
|
||||
///
|
||||
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||
/// aren't known to actually contain those methods.
|
||||
/// Serializer for unmanaged INetworkSerializable types
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
internal delegate void WriteValueDelegate(ref T value, BufferSerializer<BufferSerializerWriter> serializer);
|
||||
internal delegate void ReadValueDelegate(ref T value, BufferSerializer<BufferSerializerReader> serializer);
|
||||
|
||||
internal WriteValueDelegate WriteValue;
|
||||
internal ReadValueDelegate ReadValue;
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||
WriteValue(ref value, bufferSerializer);
|
||||
value.NetworkSerialize(bufferSerializer);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
public void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
value = new T();
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
ReadValue(ref value, bufferSerializer);
|
||||
value.NetworkSerialize(bufferSerializer);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it
|
||||
/// has to be null-aware
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class ManagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : class, INetworkSerializable, new()
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||
bool isNull = (value == null);
|
||||
bufferSerializer.SerializeValue(ref isNull);
|
||||
if (!isNull)
|
||||
{
|
||||
value.NetworkSerialize(bufferSerializer);
|
||||
}
|
||||
}
|
||||
public void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
bool isNull = false;
|
||||
bufferSerializer.SerializeValue(ref isNull);
|
||||
if (isNull)
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
value = new T();
|
||||
}
|
||||
value.NetworkSerialize(bufferSerializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +256,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
public void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||
{
|
||||
@@ -174,34 +266,115 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NetworkVariableSerializationTypes
|
||||
/// <summary>
|
||||
/// This class contains initialization functions for various different types used in NetworkVariables.
|
||||
/// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP)
|
||||
/// and do not need to be called manually.
|
||||
///
|
||||
/// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker
|
||||
/// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without
|
||||
/// a serializer registered will fall back to using the delegates in <see cref="UserNetworkVariableSerialization{T}"/>.
|
||||
/// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt
|
||||
/// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't
|
||||
/// typically need to be performed manually.)
|
||||
/// </summary>
|
||||
public static class NetworkVariableSerializationTypes
|
||||
{
|
||||
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type>
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoadMethod]
|
||||
#endif
|
||||
internal static void InitializeIntegerSerialization()
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(char),
|
||||
typeof(decimal),
|
||||
typeof(double),
|
||||
typeof(float),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(Vector2),
|
||||
typeof(Vector3),
|
||||
typeof(Vector2Int),
|
||||
typeof(Vector3Int),
|
||||
typeof(Vector4),
|
||||
typeof(Quaternion),
|
||||
typeof(Color),
|
||||
typeof(Color32),
|
||||
typeof(Ray),
|
||||
typeof(Ray2D)
|
||||
};
|
||||
NetworkVariableSerialization<short>.Serializer = new ShortSerializer();
|
||||
NetworkVariableSerialization<short>.AreEqual = NetworkVariableSerialization<short>.ValueEquals;
|
||||
NetworkVariableSerialization<ushort>.Serializer = new UshortSerializer();
|
||||
NetworkVariableSerialization<ushort>.AreEqual = NetworkVariableSerialization<ushort>.ValueEquals;
|
||||
NetworkVariableSerialization<int>.Serializer = new IntSerializer();
|
||||
NetworkVariableSerialization<int>.AreEqual = NetworkVariableSerialization<int>.ValueEquals;
|
||||
NetworkVariableSerialization<uint>.Serializer = new UintSerializer();
|
||||
NetworkVariableSerialization<uint>.AreEqual = NetworkVariableSerialization<uint>.ValueEquals;
|
||||
NetworkVariableSerialization<long>.Serializer = new LongSerializer();
|
||||
NetworkVariableSerialization<long>.AreEqual = NetworkVariableSerialization<long>.ValueEquals;
|
||||
NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer();
|
||||
NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableSerialization<ulong>.ValueEquals;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged
|
||||
{
|
||||
NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
|
||||
/// NetworkSerialize
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeSerializer_UnmanagedINetworkSerializable<T>() where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
NetworkVariableSerialization<T>.Serializer = new UnmanagedNetworkSerializableSerializer<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a managed type that implements INetworkSerializable and will be serialized through a call to
|
||||
/// NetworkSerialize
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeSerializer_ManagedINetworkSerializable<T>() where T : class, INetworkSerializable, new()
|
||||
{
|
||||
NetworkVariableSerialization<T>.Serializer = new ManagedNetworkSerializableSerializer<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
|
||||
/// serializers
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeSerializer_FixedString<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
NetworkVariableSerialization<T>.Serializer = new FixedStringSerializer<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a managed type that will be checked for equality using T.Equals()
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeEqualityChecker_ManagedIEquatable<T>() where T : class, IEquatable<T>
|
||||
{
|
||||
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an unmanaged type that will be checked for equality using T.Equals()
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeEqualityChecker_UnmanagedIEquatable<T>() where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEquals;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
|
||||
/// equal if they are bitwise equivalent in memory
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeEqualityChecker_UnmanagedValueEquals<T>() where T : unmanaged
|
||||
{
|
||||
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ValueEquals;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a managed type that will be checked for equality using the == operator
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public static void InitializeEqualityChecker_ManagedClassEquals<T>() where T : class
|
||||
{
|
||||
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ClassEquals;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -212,56 +385,59 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
|
||||
[Serializable]
|
||||
public static class NetworkVariableSerialization<T> where T : unmanaged
|
||||
public static class NetworkVariableSerialization<T>
|
||||
{
|
||||
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer();
|
||||
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
|
||||
|
||||
private static INetworkVariableSerializer<T> GetSerializer()
|
||||
internal delegate bool EqualsDelegate(ref T a, ref T b);
|
||||
internal static EqualsDelegate AreEqual;
|
||||
|
||||
// Compares two values of the same unmanaged type by underlying memory
|
||||
// Ignoring any overridden value checks
|
||||
// Size is fixed
|
||||
internal static unsafe bool ValueEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged
|
||||
{
|
||||
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T)))
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0;
|
||||
}
|
||||
|
||||
internal static bool EqualityEqualsObject<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
|
||||
{
|
||||
if (a == null)
|
||||
{
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
if (typeof(Enum).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
return b == null;
|
||||
}
|
||||
|
||||
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T)))
|
||||
if (b == null)
|
||||
{
|
||||
// Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods -
|
||||
// one for an instance of the generic method taking BufferSerializerWriter as T,
|
||||
// one for an instance of the generic method taking BufferSerializerReader as T
|
||||
var writeMethod = (NetworkSerializableSerializer<T>.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter)));
|
||||
var readMethod = (NetworkSerializableSerializer<T>.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader)));
|
||||
return new NetworkSerializableSerializer<T> { WriteValue = writeMethod, ReadValue = readMethod };
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList<byte>).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
// Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed
|
||||
// with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr()
|
||||
var getLength = (FixedStringSerializer<T>.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList<byte>.Length)));
|
||||
var setLength = (FixedStringSerializer<T>.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList<byte>.Length)));
|
||||
var getUnsafePtr = (FixedStringSerializer<T>.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr)));
|
||||
return new FixedStringSerializer<T> { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr };
|
||||
}
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
return new FallbackSerializer<T>();
|
||||
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
|
||||
{
|
||||
return a == b;
|
||||
}
|
||||
|
||||
internal static void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
s_Serializer.Write(writer, ref value);
|
||||
Serializer.Write(writer, ref value);
|
||||
}
|
||||
|
||||
internal static void Read(FastBufferReader reader, out T value)
|
||||
internal static void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
s_Serializer.Read(reader, out value);
|
||||
Serializer.Read(reader, ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
@@ -10,25 +9,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal interface ISceneManagerHandler
|
||||
{
|
||||
// Generic action to call when a scene is finished loading/unloading
|
||||
struct SceneEventAction
|
||||
{
|
||||
internal uint SceneEventId;
|
||||
internal Action<uint> EventAction;
|
||||
/// <summary>
|
||||
/// Used server-side for integration testing in order to
|
||||
/// invoke the SceneEventProgress once done loading
|
||||
/// </summary>
|
||||
internal Action Completed;
|
||||
internal void Invoke()
|
||||
{
|
||||
Completed?.Invoke();
|
||||
EventAction.Invoke(SceneEventId);
|
||||
}
|
||||
}
|
||||
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
|
||||
|
||||
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction);
|
||||
|
||||
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction);
|
||||
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,17 +338,17 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
||||
{
|
||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
|
||||
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
|
||||
sceneEventProgress.SetAsyncOperation(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var operation = SceneManager.UnloadSceneAsync(scene);
|
||||
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
|
||||
sceneEventProgress.SetAsyncOperation(operation);
|
||||
return operation;
|
||||
}
|
||||
}
|
||||
@@ -862,16 +862,6 @@ namespace Unity.Netcode
|
||||
|
||||
SceneEventProgressTracking.Add(sceneEventProgress.Guid, sceneEventProgress);
|
||||
|
||||
if (!isUnloading)
|
||||
{
|
||||
// The Condition: While a scene is asynchronously loaded in single loading scene mode, if any new NetworkObjects are spawned
|
||||
// they need to be moved into the do not destroy temporary scene
|
||||
// When it is set: Just before starting the asynchronous loading call
|
||||
// When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do
|
||||
// not destroy temporary scene are moved into the active scene
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
}
|
||||
|
||||
m_IsSceneEventActive = true;
|
||||
|
||||
// Set our callback delegate handler for completion
|
||||
@@ -887,12 +877,14 @@ namespace Unity.Netcode
|
||||
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var sceneEventData = BeginSceneEvent();
|
||||
var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true);
|
||||
var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false);
|
||||
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
|
||||
sceneEventData.SceneHash = sceneEventProgress.SceneHash;
|
||||
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
|
||||
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
|
||||
sceneEventData.ClientsCompleted = clientsThatCompleted;
|
||||
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
|
||||
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
|
||||
sceneEventData.ClientsTimedOut = clientsThatTimedOut;
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -913,8 +905,8 @@ namespace Unity.Netcode
|
||||
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
LoadSceneMode = sceneEventProgress.LoadSceneMode,
|
||||
ClientsThatCompleted = sceneEventProgress.DoneClients,
|
||||
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
|
||||
ClientsThatCompleted = clientsThatCompleted,
|
||||
ClientsThatTimedOut = clientsThatTimedOut,
|
||||
});
|
||||
|
||||
if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted)
|
||||
@@ -969,18 +961,9 @@ namespace Unity.Netcode
|
||||
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
|
||||
|
||||
ScenesLoaded.Remove(scene.handle);
|
||||
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded };
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction);
|
||||
|
||||
// If integration testing, IntegrationTestSceneHandler returns null
|
||||
if (sceneUnload == null)
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||
}
|
||||
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
|
||||
|
||||
// Notify local server that a scene is going to be unloaded
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1024,9 +1007,10 @@ namespace Unity.Netcode
|
||||
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
|
||||
}
|
||||
m_IsSceneEventActive = true;
|
||||
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle],
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress);
|
||||
|
||||
ScenesLoaded.Remove(sceneHandle);
|
||||
|
||||
@@ -1070,7 +1054,7 @@ namespace Unity.Netcode
|
||||
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,8 +1103,10 @@ namespace Unity.Netcode
|
||||
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
|
||||
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
|
||||
{
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation;
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress);
|
||||
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
||||
}
|
||||
}
|
||||
@@ -1166,6 +1152,13 @@ namespace Unity.Netcode
|
||||
|
||||
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
||||
{
|
||||
// The Condition: While a scene is asynchronously loaded in single loading scene mode, if any new NetworkObjects are spawned
|
||||
// they need to be moved into the do not destroy temporary scene
|
||||
// When it is set: Just before starting the asynchronous loading call
|
||||
// When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do
|
||||
// not destroy temporary scene are moved into the active scene
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
|
||||
// Destroy current scene objects before switching.
|
||||
m_NetworkManager.SpawnManager.ServerDestroySpawnedSceneObjects();
|
||||
|
||||
@@ -1180,18 +1173,9 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// Now start loading the scene
|
||||
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded };
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
|
||||
// If integration testing, IntegrationTestSceneHandler returns null
|
||||
if (sceneLoad == null)
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||
}
|
||||
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
||||
// Notify the local server that a scene loading event has begun
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
@@ -1355,9 +1339,10 @@ namespace Unity.Netcode
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
|
||||
}
|
||||
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded });
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress);
|
||||
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
@@ -1453,6 +1438,9 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
|
||||
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||
|
||||
// Set the server's scene's handle so the client can build a look up table
|
||||
sceneEventData.SceneHandle = scene.handle;
|
||||
|
||||
@@ -1488,7 +1476,7 @@ namespace Unity.Netcode
|
||||
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
|
||||
}
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
@@ -1662,8 +1650,10 @@ namespace Unity.Netcode
|
||||
if (!shouldPassThrough)
|
||||
{
|
||||
// If not, then load the scene
|
||||
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization });
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = ClientLoadedSynchronization;
|
||||
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
||||
|
||||
// Notify local client that a scene load has begun
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1880,7 +1870,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
|
||||
}
|
||||
EndSceneEvent(sceneEventId);
|
||||
break;
|
||||
@@ -1889,7 +1879,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
|
||||
}
|
||||
// Notify the local server that the client has finished unloading a scene
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -2025,7 +2015,11 @@ namespace Unity.Netcode
|
||||
ScenePlacedObjects.Clear();
|
||||
}
|
||||
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
|
||||
#else
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
#endif
|
||||
|
||||
// Just add every NetworkObject found that isn't already in the list
|
||||
// With additive scenes, we can have multiple in-scene placed NetworkObjects with the same GlobalObjectIdHash value
|
||||
|
||||
@@ -243,13 +243,38 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Add(sobj);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by parents before children
|
||||
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
|
||||
|
||||
// Sort by INetworkPrefabInstanceHandler implementation before the
|
||||
// NetworkObjects spawned by the implementation
|
||||
m_NetworkObjectsSync.Sort(SortNetworkObjects);
|
||||
|
||||
// This is useful to know what NetworkObjects a client is going to be synchronized with
|
||||
// as well as the order in which they will be deserialized
|
||||
if (m_NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
|
||||
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
|
||||
foreach (var networkObject in m_NetworkObjectsSync)
|
||||
{
|
||||
messageBuilder.Append($"{networkObject.name}");
|
||||
}
|
||||
NetworkLog.LogInfo(messageBuilder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddDespawnedInSceneNetworkObjects()
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Clear();
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
|
||||
// Find all active and non-active in-scene placed NetworkObjects
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager);
|
||||
#else
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
|
||||
|
||||
#endif
|
||||
foreach (var sobj in inSceneNetworkObjects)
|
||||
{
|
||||
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
|
||||
@@ -323,6 +348,32 @@ namespace Unity.Netcode
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the synchronization order of the NetworkObjects to be serialized
|
||||
/// by parents before children.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only handles late joining players. Spawning and nesting several children
|
||||
/// dynamically is still handled by the orphaned child list when deserialized out of
|
||||
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
|
||||
/// dropped and re-sent but child object is received and processed)
|
||||
/// </remarks>
|
||||
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
|
||||
{
|
||||
// If the first has a parent, move the first down
|
||||
if (first.transform.parent != null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else // If the second has a parent and the first does not, then move the first up
|
||||
if (second.transform.parent != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Client and Server Side:
|
||||
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
|
||||
@@ -334,7 +385,7 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(SceneEventType);
|
||||
|
||||
// Write the scene loading mode
|
||||
writer.WriteValueSafe(LoadSceneMode);
|
||||
writer.WriteValueSafe((byte)LoadSceneMode);
|
||||
|
||||
// Write the scene event progress Guid
|
||||
if (SceneEventType != SceneEventType.Synchronize)
|
||||
@@ -398,27 +449,26 @@ namespace Unity.Netcode
|
||||
int totalBytes = 0;
|
||||
|
||||
// Write the number of NetworkObjects we are serializing
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count());
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync.Count);
|
||||
|
||||
// Serialize all NetworkObjects that are spawned
|
||||
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
|
||||
for (var i = 0; i < m_NetworkObjectsSync.Count; ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
|
||||
sceneObject.Serialize(writer);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Write the number of despawned in-scene placed NetworkObjects
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count());
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
|
||||
// Write the scene handle and GlobalObjectIdHash value
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i)
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
@@ -452,8 +502,6 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
|
||||
{
|
||||
// Write our server relative scene handle for the NetworkObject being serialized
|
||||
writer.WriteValueSafe(keyValuePairBySceneHandle.Key);
|
||||
// Serialize the NetworkObject
|
||||
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId);
|
||||
sceneObject.Serialize(writer);
|
||||
@@ -462,6 +510,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Write the number of despawned in-scene placed NetworkObjects
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
|
||||
// Write the scene handle and GlobalObjectIdHash value
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
|
||||
{
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
}
|
||||
|
||||
var tailPosition = writer.Position;
|
||||
// Reposition to our count position to the head before we wrote our object count
|
||||
writer.Seek(headPosition);
|
||||
@@ -479,7 +536,8 @@ namespace Unity.Netcode
|
||||
internal void Deserialize(FastBufferReader reader)
|
||||
{
|
||||
reader.ReadValueSafe(out SceneEventType);
|
||||
reader.ReadValueSafe(out LoadSceneMode);
|
||||
reader.ReadValueSafe(out byte loadSceneMode);
|
||||
LoadSceneMode = (LoadSceneMode)loadSceneMode;
|
||||
|
||||
if (SceneEventType != SceneEventType.Synchronize)
|
||||
{
|
||||
@@ -570,15 +628,19 @@ namespace Unity.Netcode
|
||||
|
||||
for (ushort i = 0; i < newObjectsCount; i++)
|
||||
{
|
||||
InternalBuffer.ReadValueSafe(out int sceneHandle);
|
||||
// Set our relative scene to the NetworkObject
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneHandle);
|
||||
|
||||
// Deserialize the NetworkObject
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
sceneObject.Deserialize(InternalBuffer);
|
||||
|
||||
if (sceneObject.IsSceneObject)
|
||||
{
|
||||
// Set our relative scene to the NetworkObject
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
|
||||
}
|
||||
|
||||
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
|
||||
}
|
||||
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
|
||||
DeserializeDespawnedInScenePlacedNetworkObjects();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -600,7 +662,11 @@ namespace Unity.Netcode
|
||||
|
||||
if (networkObjectsToRemove.Length > 0)
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsSortMode.InstanceID);
|
||||
#else
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
#endif
|
||||
var networkObjectIdToNetworkObject = new Dictionary<ulong, NetworkObject>();
|
||||
foreach (var networkObject in networkObjects)
|
||||
{
|
||||
@@ -701,6 +767,90 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For synchronizing any despawned in-scene placed NetworkObjects that were
|
||||
/// despawned by the server during synchronization or scene loading
|
||||
/// </summary>
|
||||
private void DeserializeDespawnedInScenePlacedNetworkObjects()
|
||||
{
|
||||
// Process all de-spawned in-scene NetworkObjects for this network session
|
||||
m_DespawnedInSceneObjects.Clear();
|
||||
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
|
||||
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
|
||||
|
||||
for (int i = 0; i < despawnedObjectsCount; i++)
|
||||
{
|
||||
// We just need to get the scene
|
||||
InternalBuffer.ReadValueSafe(out int networkSceneHandle);
|
||||
InternalBuffer.ReadValueSafe(out uint globalObjectIdHash);
|
||||
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
|
||||
if (!sceneCache.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
|
||||
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
|
||||
{
|
||||
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
|
||||
|
||||
// Find all active and non-active in-scene placed NetworkObjects
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) =>
|
||||
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||
#else
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
|
||||
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||
#endif
|
||||
|
||||
|
||||
foreach (var inSceneObject in inSceneNetworkObjects)
|
||||
{
|
||||
if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash))
|
||||
{
|
||||
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
|
||||
}
|
||||
}
|
||||
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
|
||||
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else // Use the cached NetworkObjects if they exist
|
||||
{
|
||||
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
|
||||
}
|
||||
|
||||
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
|
||||
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
// Since this is a NetworkObject that was never spawned, we just need to send a notification
|
||||
// out that it was despawned so users can make adjustments
|
||||
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Side:
|
||||
/// During the processing of a server sent Event_Sync, this method will be called for each scene once
|
||||
@@ -713,93 +863,32 @@ namespace Unity.Netcode
|
||||
try
|
||||
{
|
||||
// Process all spawned NetworkObjects for this network session
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
|
||||
|
||||
|
||||
InternalBuffer.ReadValueSafe(out int newObjectsCount);
|
||||
for (int i = 0; i < newObjectsCount; i++)
|
||||
{
|
||||
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
|
||||
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
|
||||
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
|
||||
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
sceneObject.Deserialize(InternalBuffer);
|
||||
|
||||
// If the sceneObject is in-scene placed, then set the scene being synchronized
|
||||
if (sceneObject.IsSceneObject)
|
||||
{
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
|
||||
}
|
||||
var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager);
|
||||
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
|
||||
|
||||
// If we failed to deserialize the NetowrkObject then don't add null to the list
|
||||
if (spawnedNetworkObject != null)
|
||||
{
|
||||
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
||||
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
|
||||
{
|
||||
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process all de-spawned in-scene NetworkObjects for this network session
|
||||
m_DespawnedInSceneObjects.Clear();
|
||||
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
|
||||
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
|
||||
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
|
||||
DeserializeDespawnedInScenePlacedNetworkObjects();
|
||||
|
||||
for (int i = 0; i < despawnedObjectsCount; i++)
|
||||
{
|
||||
// We just need to get the scene
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
|
||||
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
|
||||
if (!sceneCache.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
|
||||
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
|
||||
{
|
||||
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
|
||||
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||
|
||||
foreach (var inSceneObject in inSceneNetworkObjects)
|
||||
{
|
||||
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
|
||||
}
|
||||
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
|
||||
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else // Use the cached NetworkObjects if they exist
|
||||
{
|
||||
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
|
||||
}
|
||||
|
||||
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
|
||||
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
// Since this is a NetworkObject that was never spawned, we just need to send a notification
|
||||
// out that it was despawned so users can make adjustments
|
||||
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -58,12 +58,13 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// List of clientIds of those clients that is done loading the scene.
|
||||
/// </summary>
|
||||
internal List<ulong> DoneClients { get; } = new List<ulong>();
|
||||
internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
|
||||
internal List<ulong> ClientsThatDisconnected = new List<ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// The local time when the scene event was "roughly started"
|
||||
/// This is when the current scene event will have timed out
|
||||
/// </summary>
|
||||
internal float TimeAtInitiation { get; }
|
||||
internal float WhenSceneEventHasTimedOut;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
||||
@@ -75,17 +76,15 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal OnCompletedDelegate OnComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
|
||||
/// </summary>
|
||||
internal bool IsCompleted { get; private set; }
|
||||
|
||||
internal bool TimedOut { get; private set; }
|
||||
internal Action<uint> OnSceneEventCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// If all clients are done loading the scene, at the moment of completed.
|
||||
/// This will make sure that we only have timed out if we never completed
|
||||
/// </summary>
|
||||
internal bool AreAllClientsDoneLoading { get; private set; }
|
||||
internal bool HasTimedOut()
|
||||
{
|
||||
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The hash value generated from the full scene path
|
||||
@@ -93,9 +92,10 @@ namespace Unity.Netcode
|
||||
internal uint SceneHash { get; set; }
|
||||
|
||||
internal Guid Guid { get; } = Guid.NewGuid();
|
||||
internal uint SceneEventId;
|
||||
|
||||
private Coroutine m_TimeOutCoroutine;
|
||||
private AsyncOperation m_SceneLoadOperation;
|
||||
private AsyncOperation m_AsyncOperation;
|
||||
|
||||
private NetworkManager m_NetworkManager { get; }
|
||||
|
||||
@@ -105,21 +105,85 @@ namespace Unity.Netcode
|
||||
|
||||
internal LoadSceneMode LoadSceneMode;
|
||||
|
||||
internal List<ulong> ClientsThatStartedSceneEvent;
|
||||
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
|
||||
{
|
||||
var clients = new List<ulong>();
|
||||
if (completedSceneEvent)
|
||||
{
|
||||
// If we are the host, then add the host-client to the list
|
||||
// of clients that completed if the AsyncOperation is done.
|
||||
if (m_NetworkManager.IsHost && m_AsyncOperation.isDone)
|
||||
{
|
||||
clients.Add(m_NetworkManager.LocalClientId);
|
||||
}
|
||||
|
||||
// Add all clients that completed the scene event
|
||||
foreach (var clientStatus in ClientsProcessingSceneEvent)
|
||||
{
|
||||
if (clientStatus.Value == completedSceneEvent)
|
||||
{
|
||||
clients.Add(clientStatus.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are the host, then add the host-client to the list
|
||||
// of clients that did not complete if the AsyncOperation is
|
||||
// not done.
|
||||
if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone)
|
||||
{
|
||||
clients.Add(m_NetworkManager.LocalClientId);
|
||||
}
|
||||
|
||||
// If we are getting the list of clients that have not completed the
|
||||
// scene event, then add any clients that disconnected during this
|
||||
// scene event.
|
||||
clients.AddRange(ClientsThatDisconnected);
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
||||
{
|
||||
if (status == SceneEventProgressStatus.Started)
|
||||
{
|
||||
// Track the clients that were connected when we started this event
|
||||
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
|
||||
m_NetworkManager = networkManager;
|
||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||
TimeAtInitiation = Time.realtimeSinceStartup;
|
||||
|
||||
if (networkManager.IsServer)
|
||||
{
|
||||
m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
|
||||
// Track the clients that were connected when we started this event
|
||||
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
|
||||
{
|
||||
// Ignore the host client
|
||||
if (NetworkManager.ServerClientId == connectedClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ClientsProcessingSceneEvent.Add(connectedClientId, false);
|
||||
}
|
||||
|
||||
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
|
||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||
}
|
||||
}
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the client from the clients processing the current scene event
|
||||
/// Add this client to the clients that disconnected list
|
||||
/// </summary>
|
||||
private void OnClientDisconnectCallback(ulong clientId)
|
||||
{
|
||||
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
|
||||
{
|
||||
ClientsThatDisconnected.Add(clientId);
|
||||
ClientsProcessingSceneEvent.Remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that checks to see if the scene event is complete every network tick period.
|
||||
/// This will handle completing the scene event when one or more client(s) disconnect(s)
|
||||
@@ -129,79 +193,107 @@ namespace Unity.Netcode
|
||||
internal IEnumerator TimeOutSceneEventProgress()
|
||||
{
|
||||
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
|
||||
while (!TimedOut && !IsCompleted)
|
||||
while (!HasTimedOut())
|
||||
{
|
||||
yield return waitForNetworkTick;
|
||||
|
||||
CheckCompletion();
|
||||
if (!IsCompleted)
|
||||
{
|
||||
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
|
||||
}
|
||||
TryFinishingSceneEventProgress();
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddClientAsDone(ulong clientId)
|
||||
{
|
||||
DoneClients.Add(clientId);
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
internal void RemoveClientAsDone(ulong clientId)
|
||||
{
|
||||
DoneClients.Remove(clientId);
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
|
||||
{
|
||||
m_SceneLoadOperation = sceneLoadOperation;
|
||||
m_SceneLoadOperation.completed += operation => CheckCompletion();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific)
|
||||
/// scene loading and unloading.
|
||||
///
|
||||
/// Note: During integration testing we must queue all scene loading and unloading requests for
|
||||
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
|
||||
/// conflicts when the <see cref="SceneManager.sceneLoaded"/> and <see cref="SceneManager.sceneUnloaded"/>
|
||||
/// events are triggered. The Completed action simulates the <see cref="AsyncOperation.completed"/> event.
|
||||
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
|
||||
/// Sets the client's scene event progress to finished/true
|
||||
/// </summary>
|
||||
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
internal void ClientFinishedSceneEvent(ulong clientId)
|
||||
{
|
||||
sceneEventAction.Completed = SetComplete;
|
||||
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
|
||||
{
|
||||
ClientsProcessingSceneEvent[clientId] = true;
|
||||
TryFinishingSceneEventProgress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the SceneEventProgress
|
||||
/// Determines if the scene event has finished for both
|
||||
/// client(s) and server.
|
||||
/// </summary>
|
||||
internal void SetComplete()
|
||||
/// <remarks>
|
||||
/// The server checks if all known clients processing this scene event
|
||||
/// have finished and then it returns its local AsyncOperation status.
|
||||
/// Clients finish when their AsyncOperation finishes.
|
||||
/// </remarks>
|
||||
private bool HasFinished()
|
||||
{
|
||||
IsCompleted = true;
|
||||
AreAllClientsDoneLoading = true;
|
||||
|
||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||
// If the network session is terminated/terminating then finish tracking
|
||||
// this scene event
|
||||
if (!IsNetworkSessionActive())
|
||||
{
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
return true;
|
||||
}
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
|
||||
internal void CheckCompletion()
|
||||
{
|
||||
try
|
||||
// Clients skip over this
|
||||
foreach (var clientStatus in ClientsProcessingSceneEvent)
|
||||
{
|
||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
|
||||
if (!clientStatus.Value)
|
||||
{
|
||||
SetComplete();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
// Return the local scene event's AsyncOperation status
|
||||
// Note: Integration tests process scene loading through a queue
|
||||
// and the AsyncOperation could not be assigned for several
|
||||
// network tick periods. Return false if that is the case.
|
||||
return m_AsyncOperation == null ? false : m_AsyncOperation.isDone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the AsyncOperation for the scene load/unload event
|
||||
/// </summary>
|
||||
internal void SetAsyncOperation(AsyncOperation asyncOperation)
|
||||
{
|
||||
m_AsyncOperation = asyncOperation;
|
||||
m_AsyncOperation.completed += new Action<AsyncOperation>(asyncOp2 =>
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
// Don't invoke the callback if the network session is disconnected
|
||||
// during a SceneEventProgress
|
||||
if (IsNetworkSessionActive())
|
||||
{
|
||||
OnSceneEventCompleted?.Invoke(SceneEventId);
|
||||
}
|
||||
|
||||
// Go ahead and try finishing even if the network session is terminated/terminating
|
||||
// as we might need to stop the coroutine
|
||||
TryFinishingSceneEventProgress();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
internal bool IsNetworkSessionActive()
|
||||
{
|
||||
return m_NetworkManager != null && m_NetworkManager.IsListening && !m_NetworkManager.ShutdownInProgress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will try to finish the current scene event in progress as long as
|
||||
/// all conditions are met.
|
||||
/// </summary>
|
||||
internal void TryFinishingSceneEventProgress()
|
||||
{
|
||||
if (HasFinished() || HasTimedOut())
|
||||
{
|
||||
// Don't attempt to finalize this scene event if we are no longer listening or a shutdown is in progress
|
||||
if (IsNetworkSessionActive())
|
||||
{
|
||||
OnComplete?.Invoke(this);
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
|
||||
}
|
||||
|
||||
if (m_TimeOutCoroutine != null)
|
||||
{
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,10 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
|
||||
///
|
||||
/// Implemented as a ref struct for two reasons:
|
||||
/// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
|
||||
/// 2. The BufferSerializer must always be passed by reference and can't be copied
|
||||
/// Implemented as a ref struct to help enforce the requirement that
|
||||
/// the BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
|
||||
///
|
||||
/// Ref structs help enforce both of those rules: they can't ref live the stack context in which they were
|
||||
/// created, and they're always passed by reference no matter what.
|
||||
///
|
||||
/// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't.
|
||||
/// BufferSerializer doesn't wrap FastBufferReader or FastBufferWriter directly because it can't.
|
||||
/// ref structs can't implement interfaces, and in order to be able to have two different implementations with
|
||||
/// the same interface (which allows us to avoid an "if(IsReader)" on every call), the thing directly wrapping
|
||||
/// the struct has to implement an interface. So IReaderWriter exists as the interface,
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Unity.Netcode
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializableInPlace(ref value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, float value)
|
||||
{
|
||||
WriteUInt32Packed(writer, ToUint(value));
|
||||
WriteValueBitPacked(writer, ToUint(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,7 +61,7 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, double value)
|
||||
{
|
||||
WriteUInt64Packed(writer, ToUlong(value));
|
||||
WriteValueBitPacked(writer, ToUlong(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,7 +98,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteUInt32Packed(writer, (ushort)Arithmetic.ZigZagEncode(value));
|
||||
public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an unsigned short (UInt16) as a varint to the buffer.
|
||||
@@ -109,7 +109,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteUInt32Packed(writer, value);
|
||||
public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteValueBitPacked(writer, value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a two-byte character as a varint to the buffer.
|
||||
@@ -120,7 +120,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="c">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteUInt32Packed(writer, c);
|
||||
public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteValueBitPacked(writer, c);
|
||||
|
||||
/// <summary>
|
||||
/// Write a signed int (Int32) as a ZigZag encoded varint to the buffer.
|
||||
@@ -128,7 +128,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteUInt32Packed(writer, (uint)Arithmetic.ZigZagEncode(value));
|
||||
public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an unsigned int (UInt32) to the buffer.
|
||||
@@ -136,7 +136,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteUInt32Packed(writer, value);
|
||||
public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteValueBitPacked(writer, value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an unsigned long (UInt64) to the buffer.
|
||||
@@ -144,7 +144,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteUInt64Packed(writer, value);
|
||||
public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteValueBitPacked(writer, value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a signed long (Int64) as a ZigZag encoded varint to the buffer.
|
||||
@@ -152,7 +152,7 @@ namespace Unity.Netcode
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">Value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteUInt64Packed(writer, Arithmetic.ZigZagEncode(value));
|
||||
public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, value);
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method that writes two packed Vector3 from the ray to the buffer
|
||||
@@ -282,231 +282,183 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0)
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const ushort BitPackedUshortMax = (1 << 15) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked short
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const short BitPackedShortMax = (1 << 14) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked ushort
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const short BitPackedShortMin = -(1 << 14);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0)
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const uint BitPackedUintMax = (1 << 30) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked int
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const int BitPackedIntMax = (1 << 29) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked int
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const int BitPackedIntMin = -(1 << 29);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0)
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const ulong BitPackedULongMax = (1L << 61) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked long
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const long BitPackedLongMax = (1L << 60) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked long
|
||||
/// Obsolete value that no longer carries meaning. Do not use.
|
||||
/// </summary>
|
||||
public const long BitPackedLongMin = -(1L << 60);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
|
||||
/// The first bit indicates whether the value is 1 byte or 2.
|
||||
/// The sign bit takes up another bit.
|
||||
/// That leaves 14 bits for the value.
|
||||
/// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds.
|
||||
/// In release builds builds the exception is not thrown and the value is truncated by losing its two
|
||||
/// most significant bits after zig-zag encoding.
|
||||
/// Writes a 16-bit signed short to the buffer in a bit-encoded packed format.
|
||||
/// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
|
||||
/// are still able to be compressed.
|
||||
/// The first two bits indicate whether the value is 1, 2, or 3 bytes.
|
||||
/// If the value uses 14 bits or less, the remaining 14 bits contain the value.
|
||||
/// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed
|
||||
/// by the original unmodified 16-bit value in the next 2 bytes.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, (ushort)Arithmetic.ZigZagEncode(value));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format.
|
||||
/// The first bit indicates whether the value is 1 byte or 2.
|
||||
/// That leaves 15 bits for the value.
|
||||
/// A value greater than 2^15-1 will throw an exception in editor and development builds.
|
||||
/// In release builds builds the exception is not thrown and the value is truncated by losing its
|
||||
/// most significant bit.
|
||||
/// Writes a 16-bit unsigned short to the buffer in a bit-encoded packed format.
|
||||
/// The first two bits indicate whether the value is 1, 2, or 3 bytes.
|
||||
/// If the value uses 14 bits or less, the remaining 14 bits contain the value.
|
||||
/// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed
|
||||
/// by the original unmodified 16-bit value in the next 2 bytes.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= BitPackedUshortMax)
|
||||
if (value > (1 << 14) - 1)
|
||||
{
|
||||
throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (value <= 0b0111_1111)
|
||||
{
|
||||
if (!writer.TryBeginWriteInternal(1))
|
||||
if (!writer.TryBeginWriteInternal(3))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WriteByte((byte)(value << 1));
|
||||
writer.WriteByte(3);
|
||||
writer.WriteValue(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!writer.TryBeginWriteInternal(2))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WriteValue((ushort)((value << 1) | 0b1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 29-bit signed int to the buffer in a bit-encoded packed format.
|
||||
/// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
|
||||
/// The sign bit takes up another bit.
|
||||
/// That leaves 29 bits for the value.
|
||||
/// A value greater than 2^29-1 or less than -2^29 will throw an exception in editor and development builds.
|
||||
/// In release builds builds the exception is not thrown and the value is truncated by losing its three
|
||||
/// most significant bits after zig-zag encoding.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, (uint)Arithmetic.ZigZagEncode(value));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 30-bit unsigned int to the buffer in a bit-encoded packed format.
|
||||
/// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
|
||||
/// That leaves 30 bits for the value.
|
||||
/// A value greater than 2^30-1 will throw an exception in editor and development builds.
|
||||
/// In release builds builds the exception is not thrown and the value is truncated by losing its two
|
||||
/// most significant bits.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value > BitPackedUintMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked uints must be <= 30 bits");
|
||||
}
|
||||
#endif
|
||||
value <<= 2;
|
||||
var numBytes = BitCounter.GetUsedByteCount(value);
|
||||
if (!writer.TryBeginWriteInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
|
||||
writer.WritePartialValue(value | (ushort)(numBytes), numBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 60-bit signed long to the buffer in a bit-encoded packed format.
|
||||
/// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
|
||||
/// The sign bit takes up another bit.
|
||||
/// That leaves 60 bits for the value.
|
||||
/// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds.
|
||||
/// In release builds builds the exception is not thrown and the value is truncated by losing its four
|
||||
/// most significant bits after zig-zag encoding.
|
||||
/// Writes a 32-bit signed int to the buffer in a bit-encoded packed format.
|
||||
/// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
|
||||
/// are still able to be compressed.
|
||||
/// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes.
|
||||
/// If the value uses 29 bits or less, the remaining 29 bits contain the value.
|
||||
/// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed
|
||||
/// by the original unmodified 32-bit value in the next 4 bytes.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, Arithmetic.ZigZagEncode(value));
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, (uint)Arithmetic.ZigZagEncode(value));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format.
|
||||
/// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
|
||||
/// That leaves 31 bits for the value.
|
||||
/// A value greater than 2^61-1 will throw an exception in editor and development builds.
|
||||
/// In release builds builds the exception is not thrown and the value is truncated by losing its three
|
||||
/// most significant bits.
|
||||
/// Writes a 32-bit unsigned int to the buffer in a bit-encoded packed format.
|
||||
/// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes.
|
||||
/// If the value uses 29 bits or less, the remaining 29 bits contain the value.
|
||||
/// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed
|
||||
/// by the original unmodified 32-bit value in the next 4 bytes.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value > BitPackedULongMax)
|
||||
if (value > (1 << 29) - 1)
|
||||
{
|
||||
throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
|
||||
if (!writer.TryBeginWriteInternal(5))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WriteByte(5);
|
||||
writer.WriteValue(value);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
value <<= 3;
|
||||
var numBytes = BitCounter.GetUsedByteCount(value);
|
||||
if (!writer.TryBeginWriteInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
|
||||
writer.WritePartialValue(value | (uint)(numBytes), numBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 64-bit signed long to the buffer in a bit-encoded packed format.
|
||||
/// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
|
||||
/// are still able to be compressed.
|
||||
/// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes.
|
||||
/// If the value uses 60 bits or less, the remaining 60 bits contain the value.
|
||||
/// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed
|
||||
/// by the original unmodified 64-bit value in the next 8 bytes.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, Arithmetic.ZigZagEncode(value));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 64-bit unsigned long to the buffer in a bit-encoded packed format.
|
||||
/// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes.
|
||||
/// If the value uses 60 bits or less, the remaining 60 bits contain the value.
|
||||
/// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed
|
||||
/// by the original unmodified 64-bit value in the next 8 bytes.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to</param>
|
||||
/// <param name="value">The value to pack</param>
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
|
||||
{
|
||||
if (value > (1L << 60) - 1)
|
||||
{
|
||||
if (!writer.TryBeginWriteInternal(9))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WriteByte(9);
|
||||
writer.WriteValue(value);
|
||||
return;
|
||||
}
|
||||
|
||||
value <<= 4;
|
||||
var numBytes = BitCounter.GetUsedByteCount(value);
|
||||
if (!writer.TryBeginWriteInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WritePartialValue(value | (uint)(numBytes), numBytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
private static void WriteUInt64Packed(FastBufferWriter writer, ulong value)
|
||||
{
|
||||
if (value <= 240)
|
||||
{
|
||||
writer.WriteByteSafe((byte)value);
|
||||
return;
|
||||
}
|
||||
if (value <= 2287)
|
||||
{
|
||||
writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
|
||||
writer.WriteByteSafe((byte)(value - 240));
|
||||
return;
|
||||
}
|
||||
var writeBytes = BitCounter.GetUsedByteCount(value);
|
||||
|
||||
if (!writer.TryBeginWriteInternal(writeBytes + 1))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WriteByte((byte)(247 + writeBytes));
|
||||
writer.WritePartialValue(value, writeBytes);
|
||||
}
|
||||
|
||||
// Looks like the same code as WriteUInt64Packed?
|
||||
// It's actually different because it will call the more efficient 32-bit version
|
||||
// of BytewiseUtility.GetUsedByteCount().
|
||||
private static void WriteUInt32Packed(FastBufferWriter writer, uint value)
|
||||
{
|
||||
if (value <= 240)
|
||||
{
|
||||
writer.WriteByteSafe((byte)value);
|
||||
return;
|
||||
}
|
||||
if (value <= 2287)
|
||||
{
|
||||
writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
|
||||
writer.WriteByteSafe((byte)(value - 240));
|
||||
return;
|
||||
}
|
||||
var writeBytes = BitCounter.GetUsedByteCount(value);
|
||||
|
||||
if (!writer.TryBeginWriteInternal(writeBytes + 1))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
writer.WriteByte((byte)(247 + writeBytes));
|
||||
writer.WritePartialValue(value, writeBytes);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe uint ToUint<T>(T value) where T : unmanaged
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public static class ByteUnpacker
|
||||
{
|
||||
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -58,7 +57,7 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out float value)
|
||||
{
|
||||
ReadUInt32Packed(reader, out uint asUInt);
|
||||
ReadValueBitPacked(reader, out uint asUInt);
|
||||
value = ToSingle(asUInt);
|
||||
}
|
||||
|
||||
@@ -70,7 +69,7 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out double value)
|
||||
{
|
||||
ReadUInt64Packed(reader, out ulong asULong);
|
||||
ReadValueBitPacked(reader, out ulong asULong);
|
||||
value = ToDouble(asULong);
|
||||
}
|
||||
|
||||
@@ -109,11 +108,7 @@ namespace Unity.Netcode
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">Value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out short value)
|
||||
{
|
||||
ReadUInt32Packed(reader, out uint readValue);
|
||||
value = (short)Arithmetic.ZigZagDecode(readValue);
|
||||
}
|
||||
public static void ReadValuePacked(FastBufferReader reader, out short value) => ReadValueBitPacked(reader, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read an unsigned short (UInt16) as a varint from the stream.
|
||||
@@ -121,11 +116,7 @@ namespace Unity.Netcode
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">Value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out ushort value)
|
||||
{
|
||||
ReadUInt32Packed(reader, out uint readValue);
|
||||
value = (ushort)readValue;
|
||||
}
|
||||
public static void ReadValuePacked(FastBufferReader reader, out ushort value) => ReadValueBitPacked(reader, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a two-byte character as a varint from the stream.
|
||||
@@ -135,7 +126,7 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out char c)
|
||||
{
|
||||
ReadUInt32Packed(reader, out uint readValue);
|
||||
ReadValueBitPacked(reader, out ushort readValue);
|
||||
c = (char)readValue;
|
||||
}
|
||||
|
||||
@@ -145,11 +136,7 @@ namespace Unity.Netcode
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">Value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out int value)
|
||||
{
|
||||
ReadUInt32Packed(reader, out uint readValue);
|
||||
value = (int)Arithmetic.ZigZagDecode(readValue);
|
||||
}
|
||||
public static void ReadValuePacked(FastBufferReader reader, out int value) => ReadValueBitPacked(reader, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read an unsigned int (UInt32) from the stream.
|
||||
@@ -157,7 +144,7 @@ namespace Unity.Netcode
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">Value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadUInt32Packed(reader, out value);
|
||||
public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadValueBitPacked(reader, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read an unsigned long (UInt64) from the stream.
|
||||
@@ -165,7 +152,7 @@ namespace Unity.Netcode
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">Value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadUInt64Packed(reader, out value);
|
||||
public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadValueBitPacked(reader, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a signed long (Int64) as a ZigZag encoded varint from the stream.
|
||||
@@ -175,8 +162,7 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReadValuePacked(FastBufferReader reader, out long value)
|
||||
{
|
||||
ReadUInt64Packed(reader, out ulong readValue);
|
||||
value = Arithmetic.ZigZagDecode(readValue);
|
||||
ReadValueBitPacked(reader, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -341,7 +327,9 @@ namespace Unity.Netcode
|
||||
ushort returnValue = 0;
|
||||
byte* ptr = ((byte*)&returnValue);
|
||||
byte* data = reader.GetUnsafePtrAtCurrentPosition();
|
||||
int numBytes = (data[0] & 0b1) + 1;
|
||||
// Mask out the first two bits - they contain the total byte count
|
||||
// (1, 2, or 3)
|
||||
int numBytes = (data[0] & 0b11);
|
||||
if (!reader.TryBeginReadInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
@@ -350,17 +338,23 @@ namespace Unity.Netcode
|
||||
switch (numBytes)
|
||||
{
|
||||
case 1:
|
||||
*ptr = *data;
|
||||
ptr[0] = data[0];
|
||||
break;
|
||||
case 2:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
break;
|
||||
case 3:
|
||||
// First byte contains no data, it's just a marker. The data is in the remaining two bytes.
|
||||
ptr[0] = data[1];
|
||||
ptr[1] = data[2];
|
||||
value = returnValue;
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException("Could not read bit-packed value: impossible byte count");
|
||||
}
|
||||
|
||||
value = (ushort)(returnValue >> 1);
|
||||
value = (ushort)(returnValue >> 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -386,7 +380,8 @@ namespace Unity.Netcode
|
||||
uint returnValue = 0;
|
||||
byte* ptr = ((byte*)&returnValue);
|
||||
byte* data = reader.GetUnsafePtrAtCurrentPosition();
|
||||
int numBytes = (data[0] & 0b11) + 1;
|
||||
// Mask out the first three bits - they contain the total byte count (1-5)
|
||||
int numBytes = (data[0] & 0b111);
|
||||
if (!reader.TryBeginReadInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
@@ -395,26 +390,34 @@ namespace Unity.Netcode
|
||||
switch (numBytes)
|
||||
{
|
||||
case 1:
|
||||
*ptr = *data;
|
||||
ptr[0] = data[0];
|
||||
break;
|
||||
case 2:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
break;
|
||||
case 3:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
break;
|
||||
case 4:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
*(ptr + 3) = *(data + 3);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
ptr[3] = data[3];
|
||||
break;
|
||||
case 5:
|
||||
// First byte contains no data, it's just a marker. The data is in the remaining two bytes.
|
||||
ptr[0] = data[1];
|
||||
ptr[1] = data[2];
|
||||
ptr[2] = data[3];
|
||||
ptr[3] = data[4];
|
||||
value = returnValue;
|
||||
return;
|
||||
}
|
||||
|
||||
value = returnValue >> 2;
|
||||
value = returnValue >> 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -440,7 +443,8 @@ namespace Unity.Netcode
|
||||
ulong returnValue = 0;
|
||||
byte* ptr = ((byte*)&returnValue);
|
||||
byte* data = reader.GetUnsafePtrAtCurrentPosition();
|
||||
int numBytes = (data[0] & 0b111) + 1;
|
||||
// Mask out the first four bits - they contain the total byte count (1-9)
|
||||
int numBytes = (data[0] & 0b1111);
|
||||
if (!reader.TryBeginReadInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
@@ -449,109 +453,74 @@ namespace Unity.Netcode
|
||||
switch (numBytes)
|
||||
{
|
||||
case 1:
|
||||
*ptr = *data;
|
||||
ptr[0] = data[0];
|
||||
break;
|
||||
case 2:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
break;
|
||||
case 3:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
break;
|
||||
case 4:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
*(ptr + 3) = *(data + 3);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
ptr[3] = data[3];
|
||||
break;
|
||||
case 5:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
*(ptr + 3) = *(data + 3);
|
||||
*(ptr + 4) = *(data + 4);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
ptr[3] = data[3];
|
||||
ptr[4] = data[4];
|
||||
break;
|
||||
case 6:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
*(ptr + 3) = *(data + 3);
|
||||
*(ptr + 4) = *(data + 4);
|
||||
*(ptr + 5) = *(data + 5);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
ptr[3] = data[3];
|
||||
ptr[4] = data[4];
|
||||
ptr[5] = data[5];
|
||||
break;
|
||||
case 7:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
*(ptr + 3) = *(data + 3);
|
||||
*(ptr + 4) = *(data + 4);
|
||||
*(ptr + 5) = *(data + 5);
|
||||
*(ptr + 6) = *(data + 6);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
ptr[3] = data[3];
|
||||
ptr[4] = data[4];
|
||||
ptr[5] = data[5];
|
||||
ptr[6] = data[6];
|
||||
break;
|
||||
case 8:
|
||||
*ptr = *data;
|
||||
*(ptr + 1) = *(data + 1);
|
||||
*(ptr + 2) = *(data + 2);
|
||||
*(ptr + 3) = *(data + 3);
|
||||
*(ptr + 4) = *(data + 4);
|
||||
*(ptr + 5) = *(data + 5);
|
||||
*(ptr + 6) = *(data + 6);
|
||||
*(ptr + 7) = *(data + 7);
|
||||
ptr[0] = data[0];
|
||||
ptr[1] = data[1];
|
||||
ptr[2] = data[2];
|
||||
ptr[3] = data[3];
|
||||
ptr[4] = data[4];
|
||||
ptr[5] = data[5];
|
||||
ptr[6] = data[6];
|
||||
ptr[7] = data[7];
|
||||
break;
|
||||
case 9:
|
||||
// First byte contains no data, it's just a marker. The data is in the remaining two bytes.
|
||||
ptr[0] = data[1];
|
||||
ptr[1] = data[2];
|
||||
ptr[2] = data[3];
|
||||
ptr[3] = data[4];
|
||||
ptr[4] = data[5];
|
||||
ptr[5] = data[6];
|
||||
ptr[6] = data[7];
|
||||
ptr[7] = data[8];
|
||||
value = returnValue;
|
||||
return;
|
||||
}
|
||||
|
||||
value = returnValue >> 3;
|
||||
value = returnValue >> 4;
|
||||
}
|
||||
#endif
|
||||
private static void ReadUInt64Packed(FastBufferReader reader, out ulong value)
|
||||
{
|
||||
reader.ReadByteSafe(out byte firstByte);
|
||||
if (firstByte <= 240)
|
||||
{
|
||||
value = firstByte;
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstByte <= 248)
|
||||
{
|
||||
reader.ReadByteSafe(out byte secondByte);
|
||||
value = 240UL + ((firstByte - 241UL) << 8) + secondByte;
|
||||
return;
|
||||
}
|
||||
|
||||
var numBytes = firstByte - 247;
|
||||
if (!reader.TryBeginReadInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
}
|
||||
reader.ReadPartialValue(out value, numBytes);
|
||||
}
|
||||
|
||||
private static void ReadUInt32Packed(FastBufferReader reader, out uint value)
|
||||
{
|
||||
reader.ReadByteSafe(out byte firstByte);
|
||||
if (firstByte <= 240)
|
||||
{
|
||||
value = firstByte;
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstByte <= 248)
|
||||
{
|
||||
reader.ReadByteSafe(out byte secondByte);
|
||||
value = 240U + ((firstByte - 241U) << 8) + secondByte;
|
||||
return;
|
||||
}
|
||||
|
||||
var numBytes = firstByte - 247;
|
||||
if (!reader.TryBeginReadInternal(numBytes))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
}
|
||||
reader.ReadPartialValue(out value, numBytes);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe float ToSingle<T>(T value) where T : unmanaged
|
||||
|
||||
58
Runtime/Serialization/ByteUtility.cs
Normal file
58
Runtime/Serialization/ByteUtility.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class ByteUtility
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static unsafe byte ToByte(bool b) => *(byte*)&b;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool GetBit(byte bitField, ushort bitPosition)
|
||||
{
|
||||
return (bitField & (1 << bitPosition)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void SetBit(ref byte bitField, ushort bitPosition, bool value)
|
||||
{
|
||||
bitField = (byte)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool GetBit(ushort bitField, ushort bitPosition)
|
||||
{
|
||||
return (bitField & (1 << bitPosition)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void SetBit(ref ushort bitField, ushort bitPosition, bool value)
|
||||
{
|
||||
bitField = (ushort)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool GetBit(uint bitField, ushort bitPosition)
|
||||
{
|
||||
return (bitField & (1 << bitPosition)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void SetBit(ref uint bitField, ushort bitPosition, bool value)
|
||||
{
|
||||
bitField = (uint)((bitField & ~(1 << bitPosition)) | ((uint)ToByte(value) << bitPosition));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool GetBit(ulong bitField, ushort bitPosition)
|
||||
{
|
||||
return (bitField & (ulong)(1 << bitPosition)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void SetBit(ref ulong bitField, ushort bitPosition, bool value)
|
||||
{
|
||||
bitField = ((bitField & (ulong)~(1 << bitPosition)) | ((ulong)ToByte(value) << bitPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Serialization/ByteUtility.cs.meta
Normal file
3
Runtime/Serialization/ByteUtility.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25bb0dd7157c423b8cfe0ecf06e15ae5
|
||||
timeCreated: 1666711082
|
||||
@@ -65,7 +65,7 @@ namespace Unity.Netcode
|
||||
ReaderHandle* readerHandle = null;
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), internalAllocator);
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle), UnsafeUtility.AlignOf<byte>(), internalAllocator);
|
||||
readerHandle->BufferPointer = buffer;
|
||||
readerHandle->Position = offset;
|
||||
}
|
||||
@@ -461,6 +461,19 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an INetworkSerializable in-place, without constructing a new one
|
||||
/// Note that this will NOT check for null before calling NetworkSerialize
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value">INetworkSerializable instance</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public void ReadNetworkSerializableInPlace<T>(ref T value) where T : INetworkSerializable
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(this));
|
||||
value.NetworkSerialize(bufferSerializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string
|
||||
/// NOTE: ALLOCATES
|
||||
@@ -1310,5 +1323,24 @@ namespace Unity.Netcode
|
||||
value.Length = length;
|
||||
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a FixedString value.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValueSafeInPlace<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
ReadUnmanagedSafe(out int length);
|
||||
value.Length = length;
|
||||
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,16 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
|
||||
/// <returns>This returns the <see cref="GameObject"/> that the <see cref="NetworkObject"/> is attached to and is referenced by the <see cref="NetworkObjectReference"/> passed in as a parameter</returns>
|
||||
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject;
|
||||
public static implicit operator GameObject(NetworkObjectReference networkObjectRef)
|
||||
{
|
||||
var networkObject = Resolve(networkObjectRef);
|
||||
if (networkObject != null)
|
||||
{
|
||||
return networkObject.gameObject;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="GameObject"/> to <see cref="NetworkObject"/>.
|
||||
|
||||
@@ -119,8 +119,7 @@ namespace Unity.Netcode
|
||||
// Now we register all
|
||||
foreach (var gameObject in networkPrefabOverrides)
|
||||
{
|
||||
var targetNetworkObject = gameObject.GetComponent<NetworkObject>();
|
||||
if (targetNetworkObject != null)
|
||||
if (gameObject.TryGetComponent<NetworkObject>(out var targetNetworkObject))
|
||||
{
|
||||
if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash))
|
||||
{
|
||||
|
||||
@@ -213,11 +213,11 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
networkObject.OwnerClientId = NetworkManager.ServerClientId;
|
||||
|
||||
// Server removes the entry and takes over ownership before notifying
|
||||
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
|
||||
|
||||
networkObject.OwnerClientId = NetworkManager.ServerClientId;
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
@@ -270,6 +270,9 @@ namespace Unity.Netcode
|
||||
|
||||
networkObject.OwnerClientId = clientId;
|
||||
|
||||
networkObject.MarkVariablesDirty(true);
|
||||
NetworkManager.BehaviourUpdater.AddForUpdate(networkObject);
|
||||
|
||||
// Server adds entries for all client ownership
|
||||
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
|
||||
|
||||
@@ -278,23 +281,26 @@ namespace Unity.Netcode
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
OwnerClientId = networkObject.OwnerClientId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
||||
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
if (networkObject.IsNetworkVisibleTo(client.Value.ClientId))
|
||||
{
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId);
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash))
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Hash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab))
|
||||
if (NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Hash, out var networkPrefab))
|
||||
{
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
@@ -309,69 +315,54 @@ namespace Unity.Netcode
|
||||
|
||||
return false;
|
||||
}
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle);
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Hash, sceneObject.NetworkSceneHandle);
|
||||
return networkObject != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should only run on the client
|
||||
/// Creates a local NetowrkObject to be spawned.
|
||||
/// </summary>
|
||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||
/// <remarks>
|
||||
/// For most cases this is client-side only, with the exception of when the server
|
||||
/// is spawning a player.
|
||||
/// </remarks>
|
||||
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
|
||||
{
|
||||
NetworkObject parentNetworkObject = null;
|
||||
NetworkObject networkObject = null;
|
||||
var globalObjectIdHash = sceneObject.Hash;
|
||||
var position = sceneObject.HasTransform ? sceneObject.Transform.Position : default;
|
||||
var rotation = sceneObject.HasTransform ? sceneObject.Transform.Rotation : default;
|
||||
var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default;
|
||||
var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default;
|
||||
var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays;
|
||||
var isSpawnedByPrefabHandler = false;
|
||||
|
||||
if (parentNetworkId != null && !isReparented)
|
||||
{
|
||||
if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject))
|
||||
{
|
||||
parentNetworkObject = networkObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
|
||||
// If scene management is disabled or the NetworkObject was dynamically spawned
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
|
||||
{
|
||||
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
{
|
||||
// Let the handler spawn the NetworkObject
|
||||
var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity));
|
||||
|
||||
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation);
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
{
|
||||
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
||||
}
|
||||
|
||||
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||
}
|
||||
|
||||
return networkObject;
|
||||
isSpawnedByPrefabHandler = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash
|
||||
GameObject networkPrefabReference = null;
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
|
||||
if (NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
switch (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].Override)
|
||||
switch (NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].Override)
|
||||
{
|
||||
default:
|
||||
case NetworkPrefabOverride.None:
|
||||
networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].Prefab;
|
||||
networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].Prefab;
|
||||
break;
|
||||
case NetworkPrefabOverride.Hash:
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab;
|
||||
networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -383,31 +374,18 @@ namespace Unity.Netcode
|
||||
{
|
||||
NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash
|
||||
var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent<NetworkObject>();
|
||||
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
else
|
||||
{
|
||||
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
||||
// Create prefab instance
|
||||
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
}
|
||||
|
||||
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||
}
|
||||
|
||||
return networkObject;
|
||||
}
|
||||
}
|
||||
else
|
||||
else // Get the in-scene placed NetworkObject
|
||||
{
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle);
|
||||
networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, sceneObject.NetworkSceneHandle);
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -415,17 +393,89 @@ namespace Unity.Netcode
|
||||
{
|
||||
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
// Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
|
||||
// NetworkBehaviours will have their OnNetworkSpawn method invoked
|
||||
if (networkObject != null && !networkObject.gameObject.activeInHierarchy)
|
||||
{
|
||||
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
||||
networkObject.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (networkObject != null)
|
||||
{
|
||||
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
|
||||
// This is a special case scenario where a late joining client has joined and loaded one or
|
||||
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
|
||||
// synchronization information does not indicate the NetworkObject in question has a parent.
|
||||
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
|
||||
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null)
|
||||
{
|
||||
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
|
||||
// include parenting, then we need to force the removal of that parent
|
||||
if (networkObject.transform.parent.GetComponent<NetworkObject>() != null)
|
||||
{
|
||||
// remove the parent
|
||||
networkObject.ApplyNetworkParenting(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return networkObject;
|
||||
// Set the transform unless we were spawned by a prefab handler
|
||||
// Note: prefab handlers are provided the position and rotation
|
||||
// but it is up to the user to set those values
|
||||
if (sceneObject.HasTransform && !isSpawnedByPrefabHandler)
|
||||
{
|
||||
// If world position stays is true or we have auto object parent synchronization disabled
|
||||
// then we want to apply the position and rotation values world space relative
|
||||
if (worldPositionStays || !networkObject.AutoObjectParentSync)
|
||||
{
|
||||
networkObject.transform.position = position;
|
||||
networkObject.transform.rotation = rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject.transform.localPosition = position;
|
||||
networkObject.transform.localRotation = rotation;
|
||||
}
|
||||
|
||||
// SPECIAL CASE:
|
||||
// Since players are created uniquely we don't apply scale because
|
||||
// the ConnectionApprovalResponse does not currently provide the
|
||||
// ability to specify scale. So, we just use the default scale of
|
||||
// the network prefab used to represent the player.
|
||||
// Note: not doing this would set the player's scale to zero since
|
||||
// that is the default value of Vector3.
|
||||
if (!sceneObject.IsPlayerObject)
|
||||
{
|
||||
// Since scale is always applied to local space scale, we do the transform
|
||||
// space logic during serialization such that it works out whether AutoObjectParentSync
|
||||
// is enabled or not (see NetworkObject.SceneObject)
|
||||
networkObject.transform.localScale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
if (sceneObject.HasParent)
|
||||
{
|
||||
// Go ahead and set network parenting properties, if the latest parent is not set then pass in null
|
||||
// (we always want to set worldPositionStays)
|
||||
ulong? parentId = null;
|
||||
if (sceneObject.IsLatestParentSet)
|
||||
{
|
||||
parentId = parentNetworkId;
|
||||
}
|
||||
networkObject.SetNetworkParenting(parentId, worldPositionStays);
|
||||
}
|
||||
|
||||
|
||||
// Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL
|
||||
// until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
|
||||
if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||
}
|
||||
}
|
||||
return networkObject;
|
||||
}
|
||||
|
||||
// Ran on both server and client
|
||||
@@ -454,8 +504,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// Ran on both server and client
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject,
|
||||
FastBufferReader variableData, bool destroyWithScene)
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene)
|
||||
{
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -467,9 +516,7 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException("Object is already spawned");
|
||||
}
|
||||
|
||||
networkObject.SetNetworkVariableData(variableData);
|
||||
|
||||
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
|
||||
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene);
|
||||
}
|
||||
|
||||
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
|
||||
@@ -545,7 +592,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
networkObject.SetCachedParent(networkObject.transform.parent);
|
||||
networkObject.ApplyNetworkParenting();
|
||||
NetworkObject.CheckOrphanChildren();
|
||||
|
||||
@@ -557,6 +603,11 @@ namespace Unity.Netcode
|
||||
var children = networkObject.GetComponentsInChildren<NetworkObject>();
|
||||
foreach (var childObject in children)
|
||||
{
|
||||
// Do not propagate the in-scene object setting if a child was dynamically spawned.
|
||||
if (childObject.IsSceneObject.HasValue && !childObject.IsSceneObject.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
childObject.IsSceneObject = sceneObject;
|
||||
}
|
||||
}
|
||||
@@ -575,8 +626,6 @@ namespace Unity.Netcode
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
||||
|
||||
networkObject.MarkVariablesDirty(true);
|
||||
}
|
||||
|
||||
internal ulong? GetSpawnParentId(NetworkObject networkObject)
|
||||
@@ -614,7 +663,11 @@ namespace Unity.Netcode
|
||||
// Makes scene objects ready to be reused
|
||||
internal void ServerResetShudownStateForSceneObjects()
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID).Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
|
||||
#else
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
|
||||
#endif
|
||||
foreach (var sobj in networkObjects)
|
||||
{
|
||||
sobj.IsSpawned = false;
|
||||
@@ -645,7 +698,11 @@ namespace Unity.Netcode
|
||||
|
||||
internal void DespawnAndDestroyNetworkObjects()
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
|
||||
#else
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < networkObjects.Length; i++)
|
||||
{
|
||||
@@ -675,7 +732,11 @@ namespace Unity.Netcode
|
||||
|
||||
internal void DestroySceneObjects()
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
|
||||
#else
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < networkObjects.Length; i++)
|
||||
{
|
||||
@@ -702,7 +763,11 @@ namespace Unity.Netcode
|
||||
|
||||
internal void ServerSpawnSceneObjectsOnStartSweep()
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
|
||||
#else
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
|
||||
#endif
|
||||
var networkObjectsToSpawn = new List<NetworkObject>();
|
||||
|
||||
for (int i = 0; i < networkObjects.Length; i++)
|
||||
@@ -745,15 +810,26 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// If we are shutting down the NetworkManager, then ignore resetting the parent
|
||||
if (!NetworkManager.ShutdownInProgress)
|
||||
// and only attempt to remove the child's parent on the server-side
|
||||
if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer)
|
||||
{
|
||||
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
|
||||
foreach (var spawnedNetObj in SpawnedObjectsList)
|
||||
{
|
||||
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
|
||||
if (isReparented && latestParent == networkObject.NetworkObjectId)
|
||||
var latestParent = spawnedNetObj.GetNetworkParenting();
|
||||
if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId)
|
||||
{
|
||||
spawnedNetObj.gameObject.transform.parent = null;
|
||||
// Try to remove the parent using the cached WorldPositioNStays value
|
||||
// Note: WorldPositionStays will still default to true if this was an
|
||||
// in-scene placed NetworkObject and parenting was predefined in the
|
||||
// scene via the editor.
|
||||
if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays())
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Unity.Netcode
|
||||
public double TickOffset => m_CachedTickOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>
|
||||
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>.
|
||||
/// </summary>
|
||||
public double Time => m_TimeSec;
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace Unity.Netcode
|
||||
public float TimeAsFloat => (float)m_TimeSec;
|
||||
|
||||
/// <summary>
|
||||
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedTime"/>
|
||||
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedUnscaledTime"/>.
|
||||
/// </summary>
|
||||
public double FixedTime => m_CachedTick * m_TickInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fixed delta time. This value is based on the <see cref="TickRate"/> and stays constant.
|
||||
/// Similar to <see cref="Time.fixedDeltaTime"/> There is no equivalent to <see cref="Time.deltaTime"/>
|
||||
/// Similar to <see cref="Time.fixedUnscaledTime"/> There is no equivalent to <see cref="Time.deltaTime"/>.
|
||||
/// </summary>
|
||||
public float FixedDeltaTime => (float)m_TickInterval;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.deltaTime or similar.
|
||||
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar.
|
||||
/// </summary>
|
||||
/// <param name="deltaTimeSec">The amount of time to advance. The delta time which passed since Advance was last called.</param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -7,6 +7,7 @@ using UnityEngine.Networking;
|
||||
|
||||
namespace Unity.Netcode.Transports.UNET
|
||||
{
|
||||
[AddComponentMenu("Netcode/UNet Transport")]
|
||||
public class UNetTransport : NetworkTransport
|
||||
{
|
||||
public enum SendMode
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using Unity.Networking.Transport;
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
#endif
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
@@ -25,7 +29,11 @@ namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
fixed (byte* dataPtr = m_Data)
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
reader.ReadBytesUnsafe(dataPtr, reader.Length);
|
||||
#else
|
||||
reader.ReadBytes(dataPtr, reader.Length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +70,11 @@ namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
fixed (byte* dataPtr = m_Data)
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
reader.ReadBytesUnsafe(dataPtr + m_Offset + m_Length, reader.Length);
|
||||
#else
|
||||
reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,30 @@ namespace Unity.Netcode.Transports.UTP
|
||||
/// <summary>Queue for batched messages meant to be sent through UTP.</summary>
|
||||
/// <remarks>
|
||||
/// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched
|
||||
/// messages, call <see cref="FillWriter"> with the <see cref="DataStreamWriter"/> obtained from
|
||||
/// <see cref="NetworkDriver.BeginSend"/>. This will fill the writer with as many messages as
|
||||
/// possible. If the send is successful, call <see cref="Consume"/> to remove the data from the
|
||||
/// queue.
|
||||
/// messages, call <see cref="FillWriterWithMessages"/> or <see cref="FillWriterWithBytes"/>
|
||||
/// with the <see cref="DataStreamWriter"/> obtained from <see cref="NetworkDriver.BeginSend"/>.
|
||||
/// This will fill the writer with as many messages/bytes as possible. If the send is
|
||||
/// successful, call <see cref="Consume"/> to remove the data from the queue.
|
||||
///
|
||||
/// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to
|
||||
/// read messages sent with this queue.
|
||||
/// </remarks>
|
||||
internal struct BatchedSendQueue : IDisposable
|
||||
{
|
||||
private NativeArray<byte> m_Data;
|
||||
// Note that we're using NativeList basically like a growable NativeArray, where the length
|
||||
// of the list is the capacity of our array. (We can't use the capacity of the list as our
|
||||
// queue capacity because NativeList may elect to set it higher than what we'd set it to
|
||||
// with SetCapacity, which breaks the logic of our code.)
|
||||
private NativeList<byte> m_Data;
|
||||
private NativeArray<int> m_HeadTailIndices;
|
||||
private int m_MaximumCapacity;
|
||||
private int m_MinimumCapacity;
|
||||
|
||||
/// <summary>Overhead that is added to each message in the queue.</summary>
|
||||
public const int PerMessageOverhead = sizeof(int);
|
||||
|
||||
internal const int MinimumMinimumCapacity = 4096;
|
||||
|
||||
// Indices into m_HeadTailIndicies.
|
||||
private const int k_HeadInternalIndex = 0;
|
||||
private const int k_TailInternalIndex = 1;
|
||||
@@ -43,18 +51,33 @@ namespace Unity.Netcode.Transports.UTP
|
||||
}
|
||||
|
||||
public int Length => TailIndex - HeadIndex;
|
||||
|
||||
public int Capacity => m_Data.Length;
|
||||
public bool IsEmpty => HeadIndex == TailIndex;
|
||||
|
||||
public bool IsCreated => m_Data.IsCreated;
|
||||
|
||||
/// <summary>Construct a new empty send queue.</summary>
|
||||
/// <param name="capacity">Maximum capacity of the send queue.</param>
|
||||
public BatchedSendQueue(int capacity)
|
||||
{
|
||||
m_Data = new NativeArray<byte>(capacity, Allocator.Persistent);
|
||||
// Make sure the maximum capacity will be even.
|
||||
m_MaximumCapacity = capacity + (capacity & 1);
|
||||
|
||||
// We pick the minimum capacity such that if we keep doubling it, we'll eventually hit
|
||||
// the maximum capacity exactly. The alternative would be to use capacities that are
|
||||
// powers of 2, but this can lead to over-allocating quite a bit of memory (especially
|
||||
// since we expect maximum capacities to be in the megabytes range). The approach taken
|
||||
// here avoids this issue, at the cost of not having allocations of nice round sizes.
|
||||
m_MinimumCapacity = m_MaximumCapacity;
|
||||
while (m_MinimumCapacity / 2 >= MinimumMinimumCapacity)
|
||||
{
|
||||
m_MinimumCapacity /= 2;
|
||||
}
|
||||
|
||||
m_Data = new NativeList<byte>(m_MinimumCapacity, Allocator.Persistent);
|
||||
m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent);
|
||||
|
||||
m_Data.ResizeUninitialized(m_MinimumCapacity);
|
||||
|
||||
HeadIndex = 0;
|
||||
TailIndex = 0;
|
||||
}
|
||||
@@ -68,18 +91,28 @@ namespace Unity.Netcode.Transports.UTP
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Write a raw buffer to a DataStreamWriter.</summary>
|
||||
private unsafe void WriteBytes(ref DataStreamWriter writer, byte* data, int length)
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
writer.WriteBytesUnsafe(data, length);
|
||||
#else
|
||||
writer.WriteBytes(data, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Append data at the tail of the queue. No safety checks.</summary>
|
||||
private void AppendDataAtTail(ArraySegment<byte> data)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex);
|
||||
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex);
|
||||
|
||||
writer.WriteInt(data.Count);
|
||||
|
||||
fixed (byte* dataPtr = data.Array)
|
||||
{
|
||||
writer.WriteBytes(dataPtr + data.Offset, data.Count);
|
||||
WriteBytes(ref writer, dataPtr + data.Offset, data.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,16 +133,16 @@ namespace Unity.Netcode.Transports.UTP
|
||||
}
|
||||
|
||||
// Check if there's enough room after the current tail index.
|
||||
if (m_Data.Length - TailIndex >= sizeof(int) + message.Count)
|
||||
if (Capacity - TailIndex >= sizeof(int) + message.Count)
|
||||
{
|
||||
AppendDataAtTail(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if there would be enough room if we moved data at the beginning of m_Data.
|
||||
if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count)
|
||||
// Move the data at the beginning of of m_Data. Either it will leave enough space for
|
||||
// the message, or we'll grow m_Data and will want the data at the beginning anyway.
|
||||
if (HeadIndex > 0 && Length > 0)
|
||||
{
|
||||
// Move the data back at the beginning of m_Data.
|
||||
unsafe
|
||||
{
|
||||
UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
|
||||
@@ -117,12 +150,38 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
TailIndex = Length;
|
||||
HeadIndex = 0;
|
||||
}
|
||||
|
||||
// If there's enough space left at the end for the message, now is a good time to trim
|
||||
// the capacity of m_Data if it got very large. We define "very large" here as having
|
||||
// more than 75% of m_Data unused after adding the new message.
|
||||
if (Capacity - TailIndex >= sizeof(int) + message.Count)
|
||||
{
|
||||
AppendDataAtTail(message);
|
||||
|
||||
while (TailIndex < Capacity / 4 && Capacity > m_MinimumCapacity)
|
||||
{
|
||||
m_Data.ResizeUninitialized(Capacity / 2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// If we get here we need to grow m_Data until the data fits (or it's too large).
|
||||
while (Capacity - TailIndex < sizeof(int) + message.Count)
|
||||
{
|
||||
// Can't grow m_Data anymore. Message simply won't fit.
|
||||
if (Capacity * 2 > m_MaximumCapacity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Data.ResizeUninitialized(Capacity * 2);
|
||||
}
|
||||
|
||||
// If we get here we know there's now enough room for the message.
|
||||
AppendDataAtTail(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -149,12 +208,12 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
unsafe
|
||||
{
|
||||
var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
|
||||
var reader = new DataStreamReader(m_Data.AsArray());
|
||||
|
||||
var writerAvailable = writer.Capacity;
|
||||
var readerOffset = 0;
|
||||
var readerOffset = HeadIndex;
|
||||
|
||||
while (readerOffset < Length)
|
||||
while (readerOffset < TailIndex)
|
||||
{
|
||||
reader.SeekSet(readerOffset);
|
||||
var messageLength = reader.ReadInt();
|
||||
@@ -167,8 +226,8 @@ namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
writer.WriteInt(messageLength);
|
||||
|
||||
var messageOffset = HeadIndex + reader.GetBytesRead();
|
||||
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
|
||||
var messageOffset = reader.GetBytesRead();
|
||||
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
|
||||
|
||||
writerAvailable -= sizeof(int) + messageLength;
|
||||
readerOffset += sizeof(int) + messageLength;
|
||||
@@ -205,7 +264,7 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
unsafe
|
||||
{
|
||||
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
|
||||
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
|
||||
}
|
||||
|
||||
return copyLength;
|
||||
@@ -219,10 +278,14 @@ namespace Unity.Netcode.Transports.UTP
|
||||
/// <param name="size">Number of bytes to consume from the queue.</param>
|
||||
public void Consume(int size)
|
||||
{
|
||||
// Adjust the head/tail indices such that we consume the given size.
|
||||
if (size >= Length)
|
||||
{
|
||||
HeadIndex = 0;
|
||||
TailIndex = 0;
|
||||
|
||||
// This is a no-op if m_Data is already at minimum capacity.
|
||||
m_Data.ResizeUninitialized(m_MinimumCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,25 +4,24 @@ using AOT;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Networking.Transport;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
[BurstCompile]
|
||||
internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage
|
||||
{
|
||||
static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
|
||||
static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
|
||||
static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
|
||||
private static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> s_ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
|
||||
private static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> s_SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
|
||||
private static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> s_InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
|
||||
|
||||
public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer,
|
||||
int staticInstanceBufferLength,
|
||||
NetworkSettings settings)
|
||||
{
|
||||
return new NetworkPipelineStage(
|
||||
ReceiveFunction,
|
||||
SendFunction,
|
||||
InitializeConnectionFunction,
|
||||
s_ReceiveFunction,
|
||||
s_SendFunction,
|
||||
s_InitializeConnectionFunction,
|
||||
ReceiveCapacity: 0,
|
||||
SendCapacity: 0,
|
||||
HeaderCapacity: 0,
|
||||
|
||||
188
Runtime/Transports/UTP/SecretsLoaderHelper.cs
Normal file
188
Runtime/Transports/UTP/SecretsLoaderHelper.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
/// <summary>
|
||||
/// Component to add to a NetworkManager if you want the certificates to be loaded from files.
|
||||
/// Mostly helpful to ease development and testing, especially with self-signed certificates
|
||||
///
|
||||
/// Shipping code should make the calls to
|
||||
/// - SetServerSecrets
|
||||
/// - SetClientSecrets
|
||||
/// directly, instead of relying on this.
|
||||
/// </summary>
|
||||
public class SecretsLoaderHelper : MonoBehaviour
|
||||
{
|
||||
internal struct ServerSecrets
|
||||
{
|
||||
public string ServerPrivate;
|
||||
public string ServerCertificate;
|
||||
};
|
||||
|
||||
internal struct ClientSecrets
|
||||
{
|
||||
public string ServerCommonName;
|
||||
public string ClientCertificate;
|
||||
};
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var serverSecrets = new ServerSecrets();
|
||||
|
||||
try
|
||||
{
|
||||
serverSecrets.ServerCertificate = ServerCertificate;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
serverSecrets.ServerPrivate = ServerPrivate;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
var clientSecrets = new ClientSecrets();
|
||||
try
|
||||
{
|
||||
clientSecrets.ClientCertificate = ClientCA;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clientSecrets.ServerCommonName = ServerCommonName;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
var unityTransportComponent = GetComponent<UnityTransport>();
|
||||
|
||||
if (unityTransportComponent == null)
|
||||
{
|
||||
Debug.LogError($"You need to select the UnityTransport protocol, in the NetworkManager, in order for the SecretsLoaderHelper component to be useful.");
|
||||
return;
|
||||
}
|
||||
|
||||
unityTransportComponent.SetServerSecrets(serverSecrets.ServerCertificate, serverSecrets.ServerPrivate);
|
||||
unityTransportComponent.SetClientSecrets(clientSecrets.ServerCommonName, clientSecrets.ClientCertificate);
|
||||
}
|
||||
|
||||
[Tooltip("Hostname")]
|
||||
[SerializeField]
|
||||
private string m_ServerCommonName = "localhost";
|
||||
|
||||
/// <summary>Common name of the server (typically its hostname).</summary>
|
||||
public string ServerCommonName
|
||||
{
|
||||
get => m_ServerCommonName;
|
||||
set => m_ServerCommonName = value;
|
||||
}
|
||||
|
||||
[Tooltip("Client CA filepath. Useful with self-signed certificates")]
|
||||
[SerializeField]
|
||||
private string m_ClientCAFilePath = ""; // "Assets/Secure/myGameClientCA.pem"
|
||||
|
||||
/// <summary>Client CA filepath. Useful with self-signed certificates</summary>
|
||||
public string ClientCAFilePath
|
||||
{
|
||||
get => m_ClientCAFilePath;
|
||||
set => m_ClientCAFilePath = value;
|
||||
}
|
||||
|
||||
[Tooltip("Client CA Override. Only useful for development with self-signed certificates. Certificate content, for platforms that lack file access (WebGL)")]
|
||||
[SerializeField]
|
||||
private string m_ClientCAOverride = "";
|
||||
|
||||
/// <summary>
|
||||
/// Client CA Override. Only useful for development with self-signed certificates.
|
||||
/// Certificate content, for platforms that lack file access (WebGL)
|
||||
/// </summary>
|
||||
public string ClientCAOverride
|
||||
{
|
||||
get => m_ClientCAOverride;
|
||||
set => m_ClientCAOverride = value;
|
||||
}
|
||||
|
||||
[Tooltip("Server Certificate filepath")]
|
||||
[SerializeField]
|
||||
private string m_ServerCertificateFilePath = ""; // "Assets/Secure/myGameServerCertificate.pem"
|
||||
|
||||
/// <summary>Server Certificate filepath</summary>
|
||||
public string ServerCertificateFilePath
|
||||
{
|
||||
get => m_ServerCertificateFilePath;
|
||||
set => m_ServerCertificateFilePath = value;
|
||||
}
|
||||
|
||||
[Tooltip("Server Private Key filepath")]
|
||||
[SerializeField]
|
||||
private string m_ServerPrivateFilePath = ""; // "Assets/Secure/myGameServerPrivate.pem"
|
||||
|
||||
/// <summary>Server Private Key filepath</summary>
|
||||
public string ServerPrivateFilePath
|
||||
{
|
||||
get => m_ServerPrivateFilePath;
|
||||
set => m_ServerPrivate = value;
|
||||
}
|
||||
|
||||
private string m_ClientCA;
|
||||
|
||||
/// <summary>CA certificate used by the client.</summary>
|
||||
public string ClientCA
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ClientCAOverride != "")
|
||||
{
|
||||
return m_ClientCAOverride;
|
||||
}
|
||||
return ReadFile(m_ClientCAFilePath, "Client Certificate");
|
||||
}
|
||||
set => m_ClientCA = value;
|
||||
}
|
||||
|
||||
private string m_ServerCertificate;
|
||||
|
||||
/// <summary>Certificate used by the server.</summary>
|
||||
public string ServerCertificate
|
||||
{
|
||||
get => ReadFile(m_ServerCertificateFilePath, "Server Certificate");
|
||||
set => m_ServerCertificate = value;
|
||||
}
|
||||
|
||||
private string m_ServerPrivate;
|
||||
|
||||
/// <summary>Private key used by the server.</summary>
|
||||
public string ServerPrivate
|
||||
{
|
||||
get => ReadFile(m_ServerPrivateFilePath, "Server Key");
|
||||
set => m_ServerPrivate = value;
|
||||
}
|
||||
|
||||
private static string ReadFile(string path, string label)
|
||||
{
|
||||
if (path == null || path == "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var reader = new StreamReader(path);
|
||||
string fileContent = reader.ReadToEnd();
|
||||
Debug.Log((fileContent.Length > 1) ? ("Successfully loaded " + fileContent.Length + " byte(s) from " + label) : ("Could not read " + label + " file"));
|
||||
return fileContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Transports/UTP/SecretsLoaderHelper.cs.meta
Normal file
11
Runtime/Transports/UTP/SecretsLoaderHelper.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc1e7a8dc597cf24c95e4acf92c0edf5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,11 @@
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
"expression": "1.0.0-pre.7",
|
||||
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.transport",
|
||||
"expression": "2.0.0-exp",
|
||||
"define": "UTP_TRANSPORT_2_0_ABOVE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
#if UNITY_INCLUDE_TESTS
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
#endif // UNITY_EDITOR
|
||||
#if MULTIPLAYER_TOOLS
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
#endif // MULTIPLAYER_TOOLS
|
||||
#endif // UNITY_INCLUDE_TESTS
|
||||
|
||||
63
TestHelpers/Runtime/DebugNetworkHooks.cs
Normal file
63
TestHelpers/Runtime/DebugNetworkHooks.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
internal class DebugNetworkHooks : INetworkHooks
|
||||
{
|
||||
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
|
||||
{
|
||||
Debug.Log($"Sending message of type {typeof(T).Name} to {clientId} ({messageSizeBytes} bytes)");
|
||||
}
|
||||
|
||||
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||
{
|
||||
Debug.Log($"Receiving message of type {messageType.Name} from {senderId}");
|
||||
}
|
||||
|
||||
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
|
||||
{
|
||||
Debug.Log($"==> Sending a batch of {messageCount} messages ({batchSizeInBytes} bytes) to {clientId} ({delivery})");
|
||||
}
|
||||
|
||||
public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
|
||||
{
|
||||
Debug.Log($"<== Receiving a batch of {messageCount} messages ({batchSizeInBytes} bytes) from {senderId}");
|
||||
}
|
||||
|
||||
public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
Debug.Log($"Handling a message of type {typeof(T).Name}");
|
||||
}
|
||||
|
||||
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
3
TestHelpers/Runtime/DebugNetworkHooks.cs.meta
Normal file
3
TestHelpers/Runtime/DebugNetworkHooks.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5f8098e713443eeba116a25de551cf8
|
||||
timeCreated: 1666626883
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user