3 Commits
1.1.0 ... 1.4.0

Author SHA1 Message Date
Unity Technologies
b5abc3ff7c com.unity.netcode.gameobjects@1.4.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.4.0] - 2023-04-10

### Added

- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)
- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420)
- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420)
- Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388)
- Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388)
- Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388)
- Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388)
- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388)
- Added `HalfVector3` used for scale when half float precision is enabled. (#2388)
- Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388)
- Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388)
- Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388)
- Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388)
- Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388)
- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)

### Changed

- Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463)
- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388)
- Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388)
- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388)
- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)

### Fixed

- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)
- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441)
- Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426)
- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416)
- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416)
- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388)
- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
2023-04-10 00:00:00 +00:00
Unity Technologies
8060718e04 com.unity.netcode.gameobjects@1.3.1
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.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)
2023-03-27 00:00:00 +00:00
Unity Technologies
fe02ca682e com.unity.netcode.gameobjects@1.2.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.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 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)
2022-11-21 00:00:00 +00:00
217 changed files with 15377 additions and 4663 deletions

View File

@@ -1,4 +1,3 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
@@ -7,6 +6,118 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.4.0] - 2023-04-10
### Added
- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)
- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420)
- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420)
- Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388)
- Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388)
- Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388)
- Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388)
- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388)
- Added `HalfVector3` used for scale when half float precision is enabled. (#2388)
- Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388)
- Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388)
- Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388)
- Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388)
- Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388)
- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)
### Changed
- Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463)
- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388)
- Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388)
- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388)
- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)
### Fixed
- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)
- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441)
- Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426)
- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416)
- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416)
- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388)
- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
## [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 ## [1.1.0] - 2022-10-21
### Added ### Added
@@ -157,6 +268,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) - 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
- Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898) - 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 `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) - Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
@@ -210,10 +322,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
## [1.0.0-pre.6] - 2022-03-02 ## [1.0.0-pre.6] - 2022-03-02
### Added ### Added
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765) - 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) - Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)
### Fixed ### 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 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) - 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) - 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)
@@ -265,6 +379,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398) - Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
### Fixed ### 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 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 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) - 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)
@@ -279,6 +394,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Fixed network tick value sometimes being duplicated or skipped. (#1614) - Fixed network tick value sometimes being duplicated or skipped. (#1614)
### Changed ### 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) - 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) - 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) - NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)

View File

@@ -1,10 +1,15 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if UNITY_EDITOR #if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] [assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")] [assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] #endif // UNITY_EDITOR
#endif
#if UNITY_INCLUDE_TESTS
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] [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

159
Components/HalfVector3.cs Normal file
View File

@@ -0,0 +1,159 @@
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// Half float precision <see cref="Vector3"/>.
/// </summary>
/// <remarks>
/// The Vector3T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
/// a half float type.
/// </remarks>
public struct HalfVector3 : INetworkSerializable
{
internal const int Length = 3;
/// <summary>
/// The half float precision value of the x-axis as a <see cref="half"/>.
/// </summary>
public half X => Axis.x;
/// <summary>
/// The half float precision value of the y-axis as a <see cref="half"/>.
/// </summary>
public half Y => Axis.y;
/// <summary>
/// The half float precision value of the z-axis as a <see cref="half"/>.
/// </summary>
public half Z => Axis.x;
/// <summary>
/// Used to store the half float precision values as a <see cref="half3"/>
/// </summary>
public half3 Axis;
/// <summary>
/// Determine which axis will be synchronized during serialization
/// </summary>
public bool3 AxisToSynchronize;
private void SerializeWrite(FastBufferWriter writer)
{
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
writer.WriteUnmanagedSafe(Axis[i]);
}
}
}
private void SerializeRead(FastBufferReader reader)
{
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
var axisValue = Axis[i];
reader.ReadUnmanagedSafe(out axisValue);
Axis[i] = axisValue;
}
}
}
/// <summary>
/// The serialization implementation of <see cref="INetworkSerializable"/>.
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsReader)
{
SerializeRead(serializer.GetFastBufferReader());
}
else
{
SerializeWrite(serializer.GetFastBufferWriter());
}
}
/// <summary>
/// Gets the full precision value as a <see cref="Vector3"/>.
/// </summary>
/// <returns>a <see cref="Vector3"/> as the full precision value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3()
{
Vector3 fullPrecision = Vector3.zero;
Vector3 fullConversion = math.float3(Axis);
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
fullPrecision[i] = fullConversion[i];
}
}
return fullPrecision;
}
/// <summary>
/// Converts a full precision <see cref="Vector3"/> to half precision and updates the current instance.
/// </summary>
/// <param name="vector3">The <see cref="Vector3"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Vector3 vector3)
{
var half3Full = math.half3(vector3);
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
Axis[i] = half3Full[i];
}
}
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
/// <param name="vector3AxisToSynchronize">The axis to synchronize.</param>
public HalfVector3(Vector3 vector3, bool3 axisToSynchronize)
{
Axis = half3.zero;
AxisToSynchronize = axisToSynchronize;
UpdateFrom(ref vector3);
}
/// <summary>
/// Constructor that defaults to all axis being synchronized.
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
public HalfVector3(Vector3 vector3) : this(vector3, math.bool3(true))
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="axisToSynchronize">The axis to synchronize.</param>
public HalfVector3(float x, float y, float z, bool3 axisToSynchronize) : this(new Vector3(x, y, z), axisToSynchronize)
{
}
/// <summary>
/// Constructor that defaults to all axis being synchronized.
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
public HalfVector3(float x, float y, float z) : this(new Vector3(x, y, z), math.bool3(true))
{
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee guid: b0e371533eaeac446b16b10886f64f84
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

137
Components/HalfVector4.cs Normal file
View File

@@ -0,0 +1,137 @@
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// Half Precision <see cref="Vector4"/> that can also be used to convert a <see cref="Quaternion"/> to half precision.
/// </summary>
/// <remarks>
/// The Vector4T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
/// a half float type.
/// </remarks>
public struct HalfVector4 : INetworkSerializable
{
internal const int Length = 4;
/// <summary>
/// The half float precision value of the x-axis as a <see cref="half"/>.
/// </summary>
public half X => Axis.x;
/// <summary>
/// The half float precision value of the y-axis as a <see cref="half"/>.
/// </summary>
public half Y => Axis.y;
/// <summary>
/// The half float precision value of the z-axis as a <see cref="half"/>.
/// </summary>
public half Z => Axis.z;
/// <summary>
/// The half float precision value of the w-axis as a <see cref="half"/>.
/// </summary>
public half W => Axis.w;
/// <summary>
/// Used to store the half float precision values as a <see cref="half4"/>
/// </summary>
public half4 Axis;
private void SerializeWrite(FastBufferWriter writer)
{
for (int i = 0; i < Length; i++)
{
writer.WriteUnmanagedSafe(Axis[i]);
}
}
private void SerializeRead(FastBufferReader reader)
{
for (int i = 0; i < Length; i++)
{
var axisValue = Axis[i];
reader.ReadUnmanagedSafe(out axisValue);
Axis[i] = axisValue;
}
}
/// <summary>
/// The serialization implementation of <see cref="INetworkSerializable"/>.
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsReader)
{
SerializeRead(serializer.GetFastBufferReader());
}
else
{
SerializeWrite(serializer.GetFastBufferWriter());
}
}
/// <summary>
/// Converts this instance to a full precision <see cref="Vector4"/>.
/// </summary>
/// <returns>A <see cref="Vector4"/> as the full precision value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()
{
return math.float4(Axis);
}
/// <summary>
/// Converts this instance to a full precision <see cref="Quaternion"/>.
/// </summary>
/// <returns>A <see cref="Quaternion"/> as the full precision value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Quaternion ToQuaternion()
{
return math.quaternion(Axis);
}
/// <summary>
/// Converts a full precision <see cref="Vector4"/> to half precision and updates the current instance.
/// </summary>
/// <param name="vector4">The <see cref="Vector4"/> to convert and update this instance with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Vector4 vector4)
{
Axis = math.half4(vector4);
}
/// <summary>
/// Converts a full precision <see cref="Vector4"/> to half precision and updates the current instance.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/> to convert and update this instance with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Quaternion quaternion)
{
Axis = math.half4(math.half(quaternion.x), math.half(quaternion.y), math.half(quaternion.z), math.half(quaternion.w));
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="vector4">The initial axial values (converted to half floats) when instantiated.</param>
public HalfVector4(Vector4 vector4)
{
Axis = default;
UpdateFrom(ref vector4);
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="w">The initial w axis (converted to half float) value when instantiated.</param>
public HalfVector4(float x, float y, float z, float w) : this(new Vector4(x, y, z, w))
{
}
}
}

View File

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

View File

@@ -312,20 +312,79 @@ namespace Unity.Netcode
/// </remarks> /// </remarks>
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion> public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
{ {
/// <summary>
/// Use <see cref="Quaternion.Slerp"/> when <see cref="true"/>.
/// Use <see cref="Quaternion.Lerp"/> when <see cref="false"/>
/// </summary>
/// <remarks>
/// When using half precision (due to the imprecision) using <see cref="Quaternion.Lerp"/> is
/// less processor intensive (i.e. precision is already "imprecise").
/// When using full precision (to maintain precision) using <see cref="Quaternion.Slerp"/> is
/// more processor intensive yet yields more precise results.
/// </remarks>
public bool IsSlerp;
/// <inheritdoc /> /// <inheritdoc />
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
{ {
// Disabling Extrapolation: if (IsSlerp)
// TODO: Add Jira Ticket {
return Quaternion.Slerp(start, end, time); return Quaternion.Slerp(start, end, time);
} }
else
{
return Quaternion.Lerp(start, end, time);
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
{ {
// Disabling Extrapolation: if (IsSlerp)
// TODO: Add Jira Ticket {
return Quaternion.Slerp(start, end, time); return Quaternion.Slerp(start, end, time);
} }
else
{
return Quaternion.Lerp(start, end, time);
}
}
}
/// <summary>
/// A <see cref="BufferedLinearInterpolator<T>"/> <see cref="Vector3"/> implementation.
/// </summary>
public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator<Vector3>
{
/// <summary>
/// Use <see cref="Vector3.Slerp"/> when <see cref="true"/>.
/// Use <see cref="Vector3.Lerp"/> when <see cref="false"/>
/// </summary>
public bool IsSlerp;
/// <inheritdoc />
protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time)
{
if (IsSlerp)
{
return Vector3.Slerp(start, end, time);
}
else
{
return Vector3.Lerp(start, end, time);
}
}
/// <inheritdoc />
protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time)
{
if (IsSlerp)
{
return Vector3.Slerp(start, end, time);
}
else
{
return Vector3.Lerp(start, end, time);
}
}
} }
} }

View File

@@ -23,11 +23,12 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
private void FlushMessages() private void FlushMessages()
{ {
foreach (var clientId in m_ClientsToSynchronize) foreach (var animationUpdate in m_SendAnimationUpdates)
{ {
m_NetworkAnimator.ServerSynchronizeNewPlayer(clientId); m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams);
} }
m_ClientsToSynchronize.Clear();
m_SendAnimationUpdates.Clear();
foreach (var sendEntry in m_SendParameterUpdates) foreach (var sendEntry in m_SendParameterUpdates)
{ {
@@ -64,14 +65,17 @@ namespace Unity.Netcode.Components
} }
// Everyone applies any parameters updated // Everyone applies any parameters updated
foreach (var parameterUpdate in m_ProcessParameterUpdates) for (int i = 0; i < m_ProcessParameterUpdates.Count; i++)
{ {
m_NetworkAnimator.UpdateParameters(parameterUpdate); var parameterUpdate = m_ProcessParameterUpdates[i];
m_NetworkAnimator.UpdateParameters(ref parameterUpdate);
} }
m_ProcessParameterUpdates.Clear(); m_ProcessParameterUpdates.Clear();
var isServerAuthority = m_NetworkAnimator.IsServerAuthoritative();
// Only owners check for Animator changes // owners when owner authoritative or the server when server authoritative are the only instances that
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative() || m_NetworkAnimator.IsServerAuthoritative() && m_NetworkAnimator.NetworkManager.IsServer) // checks for Animator changes
if ((!isServerAuthority && m_NetworkAnimator.IsOwner) || (isServerAuthority && m_NetworkAnimator.IsServer))
{ {
m_NetworkAnimator.CheckForAnimatorChanges(); m_NetworkAnimator.CheckForAnimatorChanges();
} }
@@ -80,20 +84,6 @@ namespace Unity.Netcode.Components
} }
} }
/// <summary>
/// Clients that need to be synchronized to the relative Animator
/// </summary>
private List<ulong> m_ClientsToSynchronize = new List<ulong>();
/// <summary>
/// When a new client is connected, they are added to the
/// m_ClientsToSynchronize list.
/// </summary>
internal void SynchronizeClient(ulong clientId)
{
m_ClientsToSynchronize.Add(clientId);
}
/// <summary> /// <summary>
/// A pending outgoing Animation update for (n) clients /// A pending outgoing Animation update for (n) clients
/// </summary> /// </summary>
@@ -157,13 +147,6 @@ namespace Unity.Netcode.Components
m_SendTriggerUpdates.Add(new TriggerUpdate() { AnimationTriggerMessage = animationTriggerMessage, SendToServer = true }); m_SendTriggerUpdates.Add(new TriggerUpdate() { AnimationTriggerMessage = animationTriggerMessage, SendToServer = true });
} }
private Queue<NetworkAnimator.AnimationMessage> m_AnimationMessageQueue = new Queue<NetworkAnimator.AnimationMessage>();
internal void AddAnimationMessageToProcessQueue(NetworkAnimator.AnimationMessage message)
{
m_AnimationMessageQueue.Enqueue(message);
}
internal void DeregisterUpdate() internal void DeregisterUpdate()
{ {
NetworkUpdateLoop.UnregisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); NetworkUpdateLoop.UnregisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate);
@@ -183,11 +166,11 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Animator")] [AddComponentMenu("Netcode/Network Animator")]
[RequireComponent(typeof(Animator))] [RequireComponent(typeof(Animator))]
public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
{ {
[Serializable] [Serializable]
internal class TransitionStateinfo internal class TransitionStateinfo
{ {
public bool IsCrossFadeExit;
public int Layer; public int Layer;
public int OriginatingState; public int OriginatingState;
public int DestinationState; public int DestinationState;
@@ -226,31 +209,12 @@ namespace Unity.Netcode.Components
} }
} }
/// <summary>
/// Creates the TransitionStateInfoList table
/// </summary>
private void BuildTransitionStateInfoList()
{
#if UNITY_EDITOR #if UNITY_EDITOR
if (UnityEditor.EditorApplication.isUpdating) private void ParseStateMachineStates(int layerIndex, ref AnimatorController animatorController, ref AnimatorStateMachine stateMachine)
{ {
return; for (int y = 0; y < stateMachine.states.Length; y++)
}
TransitionStateInfoList = new List<TransitionStateinfo>();
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
if (animatorController == null)
{ {
return; var animatorState = stateMachine.states[y].state;
}
for (int x = 0; x < animatorController.layers.Length; x++)
{
var layer = animatorController.layers[x];
for (int y = 0; y < layer.stateMachine.states.Length; y++)
{
var animatorState = layer.stateMachine.states[y].state;
var transitions = layer.stateMachine.GetStateMachineTransitions(layer.stateMachine);
for (int z = 0; z < animatorState.transitions.Length; z++) for (int z = 0; z < animatorState.transitions.Length; z++)
{ {
var transition = animatorState.transitions[z]; var transition = animatorState.transitions[z];
@@ -263,19 +227,32 @@ namespace Unity.Netcode.Components
foreach (var condition in transition.conditions) foreach (var condition in transition.conditions)
{ {
var parameterName = condition.parameter; var parameterName = condition.parameter;
var parameters = animatorController.parameters; var parameters = animatorController.parameters;
// Find the associated parameter for the condition
foreach (var parameter in parameters) foreach (var parameter in parameters)
{ {
// Only process the associated parameter(s)
if (parameter.name != parameterName)
{
continue;
}
switch (parameter.type) switch (parameter.type)
{ {
case AnimatorControllerParameterType.Trigger: case AnimatorControllerParameterType.Trigger:
{ {
// Match the condition with an existing trigger
if (parameterName == parameter.name) if (transition.destinationStateMachine != null)
{
var destinationStateMachine = transition.destinationStateMachine;
ParseStateMachineStates(layerIndex, ref animatorController, ref destinationStateMachine);
}
else if (transition.destinationState != null)
{ {
var transitionInfo = new TransitionStateinfo() var transitionInfo = new TransitionStateinfo()
{ {
Layer = x, Layer = layerIndex,
OriginatingState = animatorState.nameHash, OriginatingState = animatorState.nameHash,
DestinationState = transition.destinationState.nameHash, DestinationState = transition.destinationState.nameHash,
TransitionDuration = transition.duration, TransitionDuration = transition.duration,
@@ -284,6 +261,11 @@ namespace Unity.Netcode.Components
}; };
TransitionStateInfoList.Add(transitionInfo); TransitionStateInfoList.Add(transitionInfo);
} }
else
{
Debug.LogError($"[{name}][Conditional Transition for {animatorState.name}] Conditional triggered transition has neither a DestinationState nor a DestinationStateMachine! This transition is not likely to synchronize properly. " +
$"Please file a GitHub issue about this error with details about your Animator's setup.");
}
break; break;
} }
default: default:
@@ -295,6 +277,35 @@ namespace Unity.Netcode.Components
} }
} }
#endif #endif
/// <summary>
/// Creates the TransitionStateInfoList table
/// </summary>
private void BuildTransitionStateInfoList()
{
#if UNITY_EDITOR
if (UnityEditor.EditorApplication.isUpdating || UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
return;
}
if (m_Animator == null)
{
return;
}
TransitionStateInfoList = new List<TransitionStateinfo>();
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
if (animatorController == null)
{
return;
}
for (int x = 0; x < animatorController.layers.Length; x++)
{
var stateMachine = animatorController.layers[x].stateMachine;
ParseStateMachineStates(x, ref animatorController, ref stateMachine);
}
#endif
} }
public void OnAfterDeserialize() public void OnAfterDeserialize()
@@ -315,9 +326,19 @@ namespace Unity.Netcode.Components
internal float NormalizedTime; internal float NormalizedTime;
internal int Layer; internal int Layer;
internal float Weight; internal float Weight;
internal float Duration;
// For synchronizing transitions // For synchronizing transitions
internal bool Transition; internal bool Transition;
internal bool CrossFade;
// Flags for bool states
private const byte k_IsTransition = 0x01;
private const byte k_IsCrossFade = 0x02;
// Used to serialize the bool states
private byte m_StateFlags;
// The StateHash is where the transition starts // The StateHash is where the transition starts
// and the DestinationStateHash is the destination state // and the DestinationStateHash is the destination state
internal int DestinationStateHash; internal int DestinationStateHash;
@@ -327,65 +348,46 @@ namespace Unity.Netcode.Components
if (serializer.IsWriter) if (serializer.IsWriter)
{ {
var writer = serializer.GetFastBufferWriter(); var writer = serializer.GetFastBufferWriter();
var writeSize = FastBufferWriter.GetWriteSize(Transition); m_StateFlags = 0x00;
writeSize += FastBufferWriter.GetWriteSize(StateHash);
writeSize += FastBufferWriter.GetWriteSize(NormalizedTime);
writeSize += FastBufferWriter.GetWriteSize(Layer);
writeSize += FastBufferWriter.GetWriteSize(Weight);
if (Transition) if (Transition)
{ {
writeSize += FastBufferWriter.GetWriteSize(DestinationStateHash); m_StateFlags |= k_IsTransition;
} }
if (CrossFade)
if (!writer.TryBeginWrite(writeSize))
{ {
throw new OverflowException($"[{GetType().Name}] Could not serialize: Out of buffer space."); m_StateFlags |= k_IsCrossFade;
} }
serializer.SerializeValue(ref m_StateFlags);
writer.WriteValue(Transition); BytePacker.WriteValuePacked(writer, StateHash);
writer.WriteValue(StateHash); BytePacker.WriteValuePacked(writer, Layer);
writer.WriteValue(NormalizedTime);
writer.WriteValue(Layer);
writer.WriteValue(Weight);
if (Transition) if (Transition)
{ {
writer.WriteValue(DestinationStateHash); BytePacker.WriteValuePacked(writer, DestinationStateHash);
} }
} }
else else
{ {
var reader = serializer.GetFastBufferReader(); var reader = serializer.GetFastBufferReader();
// Begin reading the Transition flag serializer.SerializeValue(ref m_StateFlags);
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Transition))) Transition = (m_StateFlags & k_IsTransition) == k_IsTransition;
{ CrossFade = (m_StateFlags & k_IsCrossFade) == k_IsCrossFade;
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
reader.ReadValue(out Transition);
// Now determine what remains to be read ByteUnpacker.ReadValuePacked(reader, out StateHash);
var readSize = FastBufferWriter.GetWriteSize(StateHash); ByteUnpacker.ReadValuePacked(reader, out Layer);
readSize += FastBufferWriter.GetWriteSize(NormalizedTime);
readSize += FastBufferWriter.GetWriteSize(Layer);
readSize += FastBufferWriter.GetWriteSize(Weight);
if (Transition) if (Transition)
{ {
readSize += FastBufferWriter.GetWriteSize(DestinationStateHash); ByteUnpacker.ReadValuePacked(reader, out DestinationStateHash);
}
} }
// Now read the remaining information about this AnimationState serializer.SerializeValue(ref NormalizedTime);
if (!reader.TryBeginRead(readSize)) serializer.SerializeValue(ref Weight);
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
reader.ReadValue(out StateHash); // Cross fading includes the duration of the cross fade.
reader.ReadValue(out NormalizedTime); if (CrossFade)
reader.ReadValue(out Layer);
reader.ReadValue(out Weight);
if (Transition)
{ {
reader.ReadValue(out DestinationStateHash); serializer.SerializeValue(ref Duration);
}
} }
} }
} }
@@ -488,8 +490,17 @@ namespace Unity.Netcode.Components
private int[] m_AnimationHash; private int[] m_AnimationHash;
private float[] m_LayerWeights; private float[] m_LayerWeights;
private static byte[] s_EmptyArray = new byte[] { }; private static byte[] s_EmptyArray = new byte[] { };
private List<int> m_ParametersToUpdate;
private List<ulong> m_ClientSendList;
private ClientRpcParams m_ClientRpcParams;
private AnimationMessage m_AnimationMessage;
private NetworkAnimatorStateChangeHandler m_NetworkAnimatorStateChangeHandler; private NetworkAnimatorStateChangeHandler m_NetworkAnimatorStateChangeHandler;
/// <summary>
/// Used for integration test purposes
/// </summary>
internal List<AnimatorStateInfo> SynchronizationStateInfo;
private unsafe struct AnimatorParamCache private unsafe struct AnimatorParamCache
{ {
internal int Hash; internal int Hash;
@@ -519,79 +530,50 @@ namespace Unity.Netcode.Components
} }
} }
private void Cleanup() /// <summary>
/// Only things instantiated/created within OnNetworkSpawn should be
/// cleaned up here.
/// </summary>
private void SpawnCleanup()
{ {
if (m_NetworkAnimatorStateChangeHandler != null) if (m_NetworkAnimatorStateChangeHandler != null)
{ {
m_NetworkAnimatorStateChangeHandler.DeregisterUpdate(); m_NetworkAnimatorStateChangeHandler.DeregisterUpdate();
m_NetworkAnimatorStateChangeHandler = null; m_NetworkAnimatorStateChangeHandler = null;
} }
if (m_CachedNetworkManager != null)
{
m_CachedNetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback;
} }
public override void OnDestroy()
{
SpawnCleanup();
if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated) if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated)
{ {
m_CachedAnimatorParameters.Dispose(); m_CachedAnimatorParameters.Dispose();
} }
if (m_ParameterWriter.IsInitialized) if (m_ParameterWriter.IsInitialized)
{ {
m_ParameterWriter.Dispose(); m_ParameterWriter.Dispose();
} }
}
public override void OnDestroy()
{
Cleanup();
base.OnDestroy(); base.OnDestroy();
} }
private List<int> m_ParametersToUpdate; private void Awake()
private List<ulong> m_ClientSendList;
private ClientRpcParams m_ClientRpcParams;
private AnimationMessage m_AnimationMessage;
/// <summary>
/// Used for integration test to validate that the
/// AnimationMessage.AnimationStates remains the same
/// size as the layer count.
/// </summary>
internal AnimationMessage GetAnimationMessage()
{
return m_AnimationMessage;
}
// Only used in Cleanup
private NetworkManager m_CachedNetworkManager;
/// <inheritdoc/>
public override void OnNetworkSpawn()
{ {
int layers = m_Animator.layerCount; int layers = m_Animator.layerCount;
// Initializing the below arrays for everyone handles an issue // Initializing the below arrays for everyone handles an issue
// when running in owner authoritative mode and the owner changes. // when running in owner authoritative mode and the owner changes.
m_TransitionHash = new int[layers]; m_TransitionHash = new int[layers];
m_AnimationHash = new int[layers]; m_AnimationHash = new int[layers];
m_LayerWeights = new float[layers]; m_LayerWeights = new float[layers];
if (IsServer)
{
m_ClientSendList = new List<ulong>(128);
m_ClientRpcParams = new ClientRpcParams();
m_ClientRpcParams.Send = new ClientRpcSendParams();
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
// Cache the NetworkManager instance to remove the OnClientConnectedCallback subscription
m_CachedNetworkManager = NetworkManager;
NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
}
// We initialize the m_AnimationMessage for all instances in the event that // We initialize the m_AnimationMessage for all instances in the event that
// ownership or authority changes during runtime. // ownership or authority changes during runtime.
m_AnimationMessage = new AnimationMessage(); m_AnimationMessage = new AnimationMessage
m_AnimationMessage.AnimationStates = new List<AnimationState>(); {
AnimationStates = new List<AnimationState>()
};
// Store off our current layer weights and create our animation // Store off our current layer weights and create our animation
// state entries per layer. // state entries per layer.
@@ -612,17 +594,14 @@ namespace Unity.Netcode.Components
var parameters = m_Animator.parameters; var parameters = m_Animator.parameters;
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent); m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
m_ParametersToUpdate = new List<int>(parameters.Length); m_ParametersToUpdate = new List<int>(parameters.Length);
// Include all parameters including any controlled by an AnimationCurve as this could change during runtime.
// We ignore changes to any parameter controlled by an AnimationCurve when we are checking for changes in
// the Animator's parameters.
for (var i = 0; i < parameters.Length; i++) for (var i = 0; i < parameters.Length; i++)
{ {
var parameter = parameters[i]; var parameter = parameters[i];
if (m_Animator.IsParameterControlledByCurve(parameter.nameHash))
{
// we are ignoring parameters that are controlled by animation curves - syncing the layer
// states indirectly syncs the values that are driven by the animation curves
continue;
}
var cacheParam = new AnimatorParamCache var cacheParam = new AnimatorParamCache
{ {
Type = UnsafeUtility.EnumToInt(parameter.type), Type = UnsafeUtility.EnumToInt(parameter.type),
@@ -652,39 +631,82 @@ namespace Unity.Netcode.Components
m_CachedAnimatorParameters[i] = cacheParam; m_CachedAnimatorParameters[i] = cacheParam;
} }
}
/// <summary>
/// Used for integration test to validate that the
/// AnimationMessage.AnimationStates remains the same
/// size as the layer count.
/// </summary>
internal AnimationMessage GetAnimationMessage()
{
return m_AnimationMessage;
}
/// <inheritdoc/>
public override void OnNetworkSpawn()
{
// If there is no assigned Animator then generate a server network warning (logged locally and if applicable on the server-host side as well).
if (m_Animator == null)
{
NetworkLog.LogWarningServer($"[{gameObject.name}][{nameof(NetworkAnimator)}] {nameof(Animator)} is not assigned! Animation synchronization will not work for this instance!");
}
if (IsServer)
{
m_ClientSendList = new List<ulong>(128);
m_ClientRpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = m_ClientSendList
}
};
}
// Create a handler for state changes
m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this); m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this);
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
{ {
Cleanup(); SpawnCleanup();
} }
/// <summary> /// <summary>
/// Synchronizes newly joined players /// Wries all parameter and state information needed to initially synchronize a client
/// </summary> /// </summary>
internal void ServerSynchronizeNewPlayer(ulong playerId) private void WriteSynchronizationData<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{ {
m_ClientSendList.Clear(); // Parameter synchronization
m_ClientSendList.Add(playerId); {
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; // We include all parameters for the initial synchronization
// With synchronization we send all parameters
m_ParametersToUpdate.Clear(); m_ParametersToUpdate.Clear();
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{ {
m_ParametersToUpdate.Add(i); m_ParametersToUpdate.Add(i);
} }
SendParametersUpdate(m_ClientRpcParams); // Write, apply, and serialize
WriteParameters(ref m_ParameterWriter);
var parametersMessage = new ParametersUpdateMessage
{
Parameters = m_ParameterWriter.ToArray()
};
serializer.SerializeValue(ref parametersMessage);
}
// Animation state synchronization
{
// Reset the dirty count before synchronizing the newly connected client with all layers // Reset the dirty count before synchronizing the newly connected client with all layers
m_AnimationMessage.IsDirtyCount = 0; m_AnimationMessage.IsDirtyCount = 0;
for (int layer = 0; layer < m_Animator.layerCount; layer++) for (int layer = 0; layer < m_Animator.layerCount; layer++)
{ {
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer); var synchronizationStateInfo = m_Animator.GetCurrentAnimatorStateInfo(layer);
var stateHash = st.fullPathHash; SynchronizationStateInfo?.Add(synchronizationStateInfo);
var normalizedTime = st.normalizedTime; var stateHash = synchronizationStateInfo.fullPathHash;
var normalizedTime = synchronizationStateInfo.normalizedTime;
var isInTransition = m_Animator.IsInTransition(layer); var isInTransition = m_Animator.IsInTransition(layer);
// Grab one of the available AnimationState entries so we can fill it with the current // Grab one of the available AnimationState entries so we can fill it with the current
@@ -739,27 +761,125 @@ namespace Unity.Netcode.Components
} }
// Send all animation states // Send all animation states
m_AnimationMessage.IsDirtyCount = m_Animator.layerCount; m_AnimationMessage.IsDirtyCount = m_Animator.layerCount;
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams); m_AnimationMessage.NetworkSerialize(serializer);
}
} }
/// <summary> /// <summary>
/// Required for the server to synchronize newly joining players /// Used to synchronize newly joined clients
/// </summary> /// </summary>
private void OnClientConnectedCallback(ulong playerId) protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
{ {
m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId); if (serializer.IsWriter)
{
WriteSynchronizationData(ref serializer);
}
else
{
var parameters = new ParametersUpdateMessage();
var animationMessage = new AnimationMessage();
serializer.SerializeValue(ref parameters);
UpdateParameters(ref parameters);
serializer.SerializeValue(ref animationMessage);
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
}
/// <summary>
/// Checks for animation state changes in:
/// -Layer weights
/// -Cross fades
/// -Transitions
/// -Layer AnimationStates
/// </summary>
private void CheckForStateChange(int layer)
{
var stateChangeDetected = false;
var animState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
float layerWeightNow = m_Animator.GetLayerWeight(layer);
animState.CrossFade = false;
animState.Transition = false;
animState.NormalizedTime = 0.0f;
animState.Layer = layer;
animState.Duration = 0.0f;
animState.Weight = m_LayerWeights[layer];
animState.DestinationStateHash = 0;
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
stateChangeDetected = true;
animState.Weight = layerWeightNow;
}
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
AnimatorStateInfo nt = m_Animator.GetNextAnimatorStateInfo(layer);
if (tt.anyState && tt.fullPathHash == 0 && m_TransitionHash[layer] != nt.fullPathHash)
{
m_TransitionHash[layer] = nt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.DestinationStateHash = nt.fullPathHash; // Next state is the destination state for cross fade
animState.CrossFade = true;
animState.Transition = true;
animState.Duration = tt.duration;
animState.NormalizedTime = tt.normalizedTime;
stateChangeDetected = true;
//Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
else
if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.StateHash = tt.fullPathHash; // Transitioning from state
animState.CrossFade = false;
animState.Transition = true;
animState.NormalizedTime = tt.normalizedTime;
stateChangeDetected = true;
//Debug.Log($"[Transition] TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
}
else
{
if (st.fullPathHash != m_AnimationHash[layer])
{
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
animState.StateHash = st.fullPathHash;
animState.NormalizedTime = st.normalizedTime;
}
stateChangeDetected = true;
//Debug.Log($"[State] From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
}
if (stateChangeDetected)
{
m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animState;
m_AnimationMessage.IsDirtyCount++;
}
} }
/// <summary> /// <summary>
/// Checks for changes in both Animator parameters and state. /// Checks for changes in both Animator parameters and state.
/// </summary> /// </summary>
/// <remarks>
/// This is only invoked by clients that are the owner when not in server authoritative mode
/// or by the server itself when in server authoritative mode.
/// </remarks>
internal void CheckForAnimatorChanges() internal void CheckForAnimatorChanges()
{ {
if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer)
{
return;
}
if (CheckParametersChanged()) if (CheckParametersChanged())
{ {
SendParametersUpdate(); SendParametersUpdate();
@@ -774,9 +894,6 @@ namespace Unity.Netcode.Components
return; return;
} }
int stateHash;
float normalizedTime;
// Reset the dirty count before checking for AnimationState updates // Reset the dirty count before checking for AnimationState updates
m_AnimationMessage.IsDirtyCount = 0; m_AnimationMessage.IsDirtyCount = 0;
@@ -786,26 +903,7 @@ namespace Unity.Netcode.Components
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer); AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
var totalSpeed = st.speed * st.speedMultiplier; var totalSpeed = st.speed * st.speedMultiplier;
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f; var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
CheckForStateChange(layer);
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{
continue;
}
// If we made it here, then we need to synchronize this layer's animation state.
// Get one of the preallocated AnimationState entries and populate it with the
// current layer's state.
var animationState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
animationState.Transition = false; // Only used during synchronization
animationState.StateHash = stateHash;
animationState.NormalizedTime = normalizedTime;
animationState.Layer = layer;
animationState.Weight = m_LayerWeights[layer];
// Apply the changes
m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animationState;
m_AnimationMessage.IsDirtyCount++;
} }
// Send an AnimationMessage only if there are dirty AnimationStates to send // Send an AnimationMessage only if there are dirty AnimationStates to send
@@ -817,17 +915,19 @@ namespace Unity.Netcode.Components
} }
else else
{ {
SendAnimStateClientRpc(m_AnimationMessage); // Just notify all remote clients and not the local server
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.LocalClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
} }
} }
} }
private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, bool sendDirect = false) private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, bool sendDirect = false)
{ {
m_ParameterWriter.Seek(0); WriteParameters(ref m_ParameterWriter);
m_ParameterWriter.Truncate();
WriteParameters(m_ParameterWriter, sendDirect);
var parametersMessage = new ParametersUpdateMessage var parametersMessage = new ParametersUpdateMessage
{ {
@@ -854,7 +954,7 @@ namespace Unity.Netcode.Components
/// <summary> /// <summary>
/// Helper function to get the cached value /// Helper function to get the cached value
/// </summary> /// </summary>
unsafe private T GetValue<T>(ref AnimatorParamCache animatorParamCache) private unsafe T GetValue<T>(ref AnimatorParamCache animatorParamCache)
{ {
T currentValue; T currentValue;
fixed (void* value = animatorParamCache.Value) fixed (void* value = animatorParamCache.Value)
@@ -869,12 +969,20 @@ namespace Unity.Netcode.Components
/// If so, it fills out m_ParametersToUpdate with the indices of the parameters /// If so, it fills out m_ParametersToUpdate with the indices of the parameters
/// that have changed. Returns true if any parameters changed. /// that have changed. Returns true if any parameters changed.
/// </summary> /// </summary>
unsafe private bool CheckParametersChanged() private unsafe bool CheckParametersChanged()
{ {
m_ParametersToUpdate.Clear(); m_ParametersToUpdate.Clear();
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{ {
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i); ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
// If a parameter gets controlled by a curve during runtime after initialization of NetworkAnimator
// then ignore changes to this parameter. We are not removing the parameter in the event that
// it no longer is controlled by a curve.
if (m_Animator.IsParameterControlledByCurve(cacheValue.Hash))
{
continue;
}
var hash = cacheValue.Hash; var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt) if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{ {
@@ -910,59 +1018,15 @@ namespace Unity.Netcode.Components
return m_ParametersToUpdate.Count > 0; return m_ParametersToUpdate.Count > 0;
} }
/// <summary>
/// Checks if any of the Animator's states have changed
/// </summary>
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{
stateHash = 0;
normalizedTime = 0;
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
return true;
}
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
if (tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
return true;
}
}
else
{
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (st.fullPathHash != m_AnimationHash[layer])
{
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
return true;
}
}
return false;
}
/// <summary> /// <summary>
/// Writes all of the Animator's parameters /// Writes all of the Animator's parameters
/// This uses the m_ParametersToUpdate list to write out only /// This uses the m_ParametersToUpdate list to write out only
/// the parameters that have changed /// the parameters that have changed
/// </summary> /// </summary>
private unsafe void WriteParameters(FastBufferWriter writer, bool sendCacheState) private unsafe void WriteParameters(ref FastBufferWriter writer)
{ {
writer.Seek(0);
writer.Truncate();
// Write how many parameter entries we are going to write // Write how many parameter entries we are going to write
BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count); BytePacker.WriteValuePacked(writer, (uint)m_ParametersToUpdate.Count);
foreach (var parameterIndex in m_ParametersToUpdate) foreach (var parameterIndex in m_ParametersToUpdate)
@@ -1049,7 +1113,7 @@ namespace Unity.Netcode.Components
/// <summary> /// <summary>
/// Applies the ParametersUpdateMessage state to the Animator /// Applies the ParametersUpdateMessage state to the Animator
/// </summary> /// </summary>
internal unsafe void UpdateParameters(ParametersUpdateMessage parametersUpdate) internal unsafe void UpdateParameters(ref ParametersUpdateMessage parametersUpdate)
{ {
if (parametersUpdate.Parameters != null && parametersUpdate.Parameters.Length != 0) if (parametersUpdate.Parameters != null && parametersUpdate.Parameters.Length != 0)
{ {
@@ -1067,14 +1131,24 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
internal void UpdateAnimationState(AnimationState animationState) internal void UpdateAnimationState(AnimationState animationState)
{ {
if (animationState.StateHash == 0) // Handle updating layer weights first.
if (animationState.Layer < m_LayerWeights.Length)
{
if (m_LayerWeights[animationState.Layer] != animationState.Weight)
{
m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
}
}
// If there is no state transition then return
if (animationState.StateHash == 0 && !animationState.Transition)
{ {
return; return;
} }
var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer); var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer);
// If it is a transition, then we are synchronizing transitions in progress when a client late joins // If it is a transition, then we are synchronizing transitions in progress when a client late joins
if (animationState.Transition) if (animationState.Transition && !animationState.CrossFade)
{ {
// We should have all valid entries for any animation state transition update // We should have all valid entries for any animation state transition update
// Verify the AnimationState's assigned Layer exists // Verify the AnimationState's assigned Layer exists
@@ -1107,14 +1181,18 @@ namespace Unity.Netcode.Components
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!");
} }
} }
else if (animationState.Transition && animationState.CrossFade)
{
m_Animator.CrossFade(animationState.DestinationStateHash, animationState.Duration, animationState.Layer, animationState.NormalizedTime);
}
else else
{ {
if (currentState.fullPathHash != animationState.StateHash) // Make sure we are not just updating the weight of a layer.
if (currentState.fullPathHash != animationState.StateHash && m_Animator.HasState(animationState.Layer, animationState.StateHash))
{ {
m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime); m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime);
} }
} }
m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
} }
/// <summary> /// <summary>
@@ -1134,7 +1212,7 @@ namespace Unity.Netcode.Components
{ {
return; return;
} }
UpdateParameters(parametersUpdate); UpdateParameters(ref parametersUpdate);
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{ {
m_ClientSendList.Clear(); m_ClientSendList.Clear();
@@ -1183,7 +1261,6 @@ namespace Unity.Netcode.Components
UpdateAnimationState(animationState); UpdateAnimationState(animationState);
} }
m_NetworkAnimatorStateChangeHandler.AddAnimationMessageToProcessQueue(animationMessage);
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1)) if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{ {
m_ClientSendList.Clear(); m_ClientSendList.Clear();
@@ -1200,7 +1277,7 @@ namespace Unity.Netcode.Components
/// Internally-called RPC client receiving function to update some animation state on a client /// Internally-called RPC client receiving function to update some animation state on a client
/// </summary> /// </summary>
[ClientRpc] [ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default) internal unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
{ {
// This should never happen // This should never happen
if (IsHost) if (IsHost)
@@ -1211,16 +1288,11 @@ namespace Unity.Netcode.Components
} }
return; return;
} }
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
foreach (var animationState in animationMessage.AnimationStates) foreach (var animationState in animationMessage.AnimationStates)
{ {
UpdateAnimationState(animationState); UpdateAnimationState(animationState);
} }
} }
}
/// <summary> /// <summary>
/// Server-side trigger state update request /// Server-side trigger state update request
@@ -1228,21 +1300,6 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
[ServerRpc] [ServerRpc]
internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default) internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
{
// If it is server authoritative
if (IsServerAuthoritative())
{
// The only condition where this should (be allowed to) happen is when the owner sends the server a trigger message
if (OwnerClientId == serverRpcParams.Receive.SenderClientId)
{
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage);
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Server Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
}
else
{ {
// Ignore if a non-owner sent this. // Ignore if a non-owner sent this.
if (serverRpcParams.Receive.SenderClientId != OwnerClientId) if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
@@ -1257,16 +1314,18 @@ namespace Unity.Netcode.Components
// set the trigger locally on the server // set the trigger locally on the server
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
// send the message to all non-authority clients excluding the server and the owner
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
m_ClientSendList.Clear(); m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId); m_ClientSendList.Remove(NetworkManager.ServerClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
if (IsServerAuthoritative())
{
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
} }
else if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
} }
} }
@@ -1319,7 +1378,7 @@ namespace Unity.Netcode.Components
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage); m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage);
if (!IsHost) if (!IsHost)
{ {
InternalSetTrigger(hash); InternalSetTrigger(hash, setTrigger);
} }
} }
else else
@@ -1328,7 +1387,7 @@ namespace Unity.Netcode.Components
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToServer(animTriggerMessage); m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToServer(animTriggerMessage);
if (!IsServerAuthoritative()) if (!IsServerAuthoritative())
{ {
InternalSetTrigger(hash); InternalSetTrigger(hash, setTrigger);
} }
} }
} }

View File

@@ -0,0 +1,205 @@
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// Used to synchromnize delta position when half float precision is enabled
/// </summary>
public struct NetworkDeltaPosition : INetworkSerializable
{
internal const float MaxDeltaBeforeAdjustment = 64f;
/// <summary>
/// The HalfVector3 used to synchronize the delta in position
/// </summary>
public HalfVector3 HalfVector3;
internal Vector3 CurrentBasePosition;
internal Vector3 PrecisionLossDelta;
internal Vector3 HalfDeltaConvertedBack;
internal Vector3 PreviousPosition;
internal Vector3 DeltaPosition;
internal int NetworkTick;
/// <summary>
/// The serialization implementation of <see cref="INetworkSerializable"/>
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
HalfVector3.NetworkSerialize(serializer);
}
/// <summary>
/// Gets the full precision value of Vector3 position while also potentially updating the current base position.
/// </summary>
/// <param name="networkTick">Use the current network tick value.</param>
/// <returns>The full position as a <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3(int networkTick)
{
// When synchronizing, it is possible to have a state update arrive
// for the same synchronization network tick. Under this scenario,
// we only want to return the existing CurrentBasePosition + DeltaPosition
// values and not process the X, Y, or Z values.
// (See the constructors below)
if (networkTick == NetworkTick)
{
return CurrentBasePosition + DeltaPosition;
}
for (int i = 0; i < HalfVector3.Length; i++)
{
if (HalfVector3.AxisToSynchronize[i])
{
DeltaPosition[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value);
// If we exceed or are equal to the maximum delta value then we need to
// apply the delta to the CurrentBasePosition value and reset the delta
// position for the axis.
if (Mathf.Abs(DeltaPosition[i]) >= MaxDeltaBeforeAdjustment)
{
CurrentBasePosition[i] += DeltaPosition[i];
DeltaPosition[i] = 0.0f;
HalfVector3.Axis[i] = half.zero;
}
}
}
return CurrentBasePosition + DeltaPosition;
}
/// <summary>
/// Returns the current base position (excluding the delta position offset).
/// </summary>
/// <returns>The current base position as a <see cref="Vector3"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetCurrentBasePosition()
{
return CurrentBasePosition;
}
/// <summary>
/// Returns the full position which includes the delta offset position.
/// </summary>
/// <returns>The full position as a <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetFullPosition()
{
return CurrentBasePosition + DeltaPosition;
}
/// <summary>
/// The half float vector3 version of the current delta position.
/// </summary>
/// <remarks>
/// Only applies to the authoritative side for <see cref="NetworkTransform"/> instances.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetConvertedDelta()
{
return HalfDeltaConvertedBack;
}
/// <summary>
/// The full precision current delta position.
/// </summary>
/// <remarks>
/// Authoritative: Will have no precision loss
/// Non-Authoritative: Has the current network tick's loss of precision.
/// Precision loss adjustments are one network tick behind on the
/// non-authoritative side.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetDeltaPosition()
{
return DeltaPosition;
}
/// <summary>
/// Updates the position delta based off of the current base position.
/// </summary>
/// <param name="vector3">The full precision <see cref="Vector3"/> value to (converted to half floats) used to determine the delta offset positon.</param>
/// <param name="networkTick">Set the current network tick value when updating.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Vector3 vector3, int networkTick)
{
NetworkTick = networkTick;
DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition;
for (int i = 0; i < HalfVector3.Length; i++)
{
if (HalfVector3.AxisToSynchronize[i])
{
HalfVector3.Axis[i] = math.half(DeltaPosition[i]);
HalfDeltaConvertedBack[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value);
PrecisionLossDelta[i] = DeltaPosition[i] - HalfDeltaConvertedBack[i];
if (Mathf.Abs(HalfDeltaConvertedBack[i]) >= MaxDeltaBeforeAdjustment)
{
CurrentBasePosition[i] += HalfDeltaConvertedBack[i];
HalfDeltaConvertedBack[i] = 0.0f;
DeltaPosition[i] = 0.0f;
}
}
}
for (int i = 0; i < HalfVector3.Length; i++)
{
if (HalfVector3.AxisToSynchronize[i])
{
PreviousPosition[i] = vector3[i];
}
}
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
/// <param name="axisToSynchronize">The axis to be synchronized.</param>
public NetworkDeltaPosition(Vector3 vector3, int networkTick, bool3 axisToSynchronize)
{
NetworkTick = networkTick;
CurrentBasePosition = vector3;
PreviousPosition = vector3;
PrecisionLossDelta = Vector3.zero;
DeltaPosition = Vector3.zero;
HalfDeltaConvertedBack = Vector3.zero;
HalfVector3 = new HalfVector3(vector3, axisToSynchronize);
UpdateFrom(ref vector3, networkTick);
}
/// <summary>
/// Constructor that defaults to all axis being synchronized.
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
public NetworkDeltaPosition(Vector3 vector3, int networkTick) : this(vector3, networkTick, math.bool3(true))
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
/// <param name="axisToSynchronize">The axis to be synchronized.</param>
public NetworkDeltaPosition(float x, float y, float z, int networkTick, bool3 axisToSynchronize) :
this(new Vector3(x, y, z), networkTick, axisToSynchronize)
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
public NetworkDeltaPosition(float x, float y, float z, int networkTick) :
this(new Vector3(x, y, z), networkTick, math.bool3(true))
{
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// A Smallest Three Quaternion Compressor Implementation
/// </summary>
/// <remarks>
/// Explanation of why "The smallest three":
/// Since a normalized Quaternion's unit value is 1.0f:
/// x*x + y*y + z*z + w*w = M*M (where M is the magnitude of the vector)
/// If w was the largest value and the quaternion is normalized:
/// M = 1.0f (which M * M would still yield 1.0f)
/// w*w = M*M - (x*x + y*y + z*z) or Mathf.Sqrt(1.0f - (x*x + y*y + z*z))
/// w = Math.Sqrt(1.0f - (x*x + y*y + z*z))
/// Using the largest the number avoids potential loss of precision in the smallest three values.
/// </remarks>
public static class QuaternionCompressor
{
private const ushort k_PrecisionMask = (1 << 9) - 1;
// Square root of 2 over 2 (Mathf.Sqrt(2.0f) / 2.0f == 1.0f / Mathf.Sqrt(2.0f))
// This provides encoding the smallest three components into a (+/-) Mathf.Sqrt(2.0f) / 2.0f range
private const float k_SqrtTwoOverTwoEncoding = 0.70710678118654752440084436210485f;
// We can further improve the encoding compression by dividing k_SqrtTwoOverTwo into 1.0f and multiplying that
// by the precision mask (minor reduction of runtime calculations)
private const float k_CompressionEcodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask;
// Used to shift the negative bit to the 10th bit position when compressing and encoding
private const ushort k_ShiftNegativeBit = 9;
// We can do the same for our decoding and decompression by dividing k_PrecisionMask into 1.0 and multiplying
// that by k_SqrtTwoOverTwo (minor reduction of runtime calculations)
private const float k_DcompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding;
// The sign bit position (10th bit) used when decompressing and decoding
private const ushort k_NegShortBit = 0x200;
// Negative bit set values
private const ushort k_True = 1;
private const ushort k_False = 0;
// Used to store the absolute value of the 4 quaternion elements
private static Quaternion s_QuatAbsValues = Quaternion.identity;
/// <summary>
/// Compresses a Quaternion into an unsigned integer
/// </summary>
/// <param name="quaternion">the <see cref="Quaternion"/> to be compressed</param>
/// <returns>the <see cref="Quaternion"/> compressed as an unsigned integer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint CompressQuaternion(ref Quaternion quaternion)
{
// Store off the absolute value for each Quaternion element
s_QuatAbsValues[0] = Mathf.Abs(quaternion[0]);
s_QuatAbsValues[1] = Mathf.Abs(quaternion[1]);
s_QuatAbsValues[2] = Mathf.Abs(quaternion[2]);
s_QuatAbsValues[3] = Mathf.Abs(quaternion[3]);
// Get the largest element value of the quaternion to know what the remaining "Smallest Three" values are
var quatMax = Mathf.Max(s_QuatAbsValues[0], s_QuatAbsValues[1], s_QuatAbsValues[2], s_QuatAbsValues[3]);
// Find the index of the largest element so we can skip that element while compressing and decompressing
var indexToSkip = (ushort)(s_QuatAbsValues[0] == quatMax ? 0 : s_QuatAbsValues[1] == quatMax ? 1 : s_QuatAbsValues[2] == quatMax ? 2 : 3);
// Get the sign of the largest element which is all that is needed when calculating the sum of squares of a normalized quaternion.
var quatMaxSign = (quaternion[indexToSkip] < 0 ? k_True : k_False);
// Start with the index to skip which will be shifted to the highest two bits
var compressed = (uint)indexToSkip;
// Step 1: Start with the first element
var currentIndex = 0;
// Step 2: If we are on the index to skip preserve the current compressed value, otherwise proceed to step 3 and 4
// Step 3: Get the sign of the element we are processing. If it is the not the same as the largest value's sign bit then we set the bit
// Step 4: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEcodingMask and round that result up
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
currentIndex++;
// Repeat the last 3 steps for the remaining elements
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
currentIndex++;
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
currentIndex++;
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
// Return the compress quaternion
return compressed;
}
/// <summary>
/// Decompress a compressed quaternion
/// </summary>
/// <param name="quaternion">quaternion to store the decompressed values within</param>
/// <param name="compressed">the compressed quaternion</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DecompressQuaternion(ref Quaternion quaternion, uint compressed)
{
// Get the last two bits for the index to skip (0-3)
var indexToSkip = (int)(compressed >> 30);
// Reverse out the values while skipping over the largest value index
var sumOfSquaredMagnitudes = 0.0f;
for (int i = 3; i >= 0; --i)
{
if (i == indexToSkip)
{
continue;
}
// Check the negative bit and multiply that result with the decompressed and decoded value
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DcompressionDecodingMask);
sumOfSquaredMagnitudes += quaternion[i] * quaternion[i];
compressed = compressed >> 10;
}
// Since a normalized quaternion's magnitude is 1.0f, we subtract the sum of the squared smallest three from the unit value and take
// the square root of the difference to find the final largest value
quaternion[indexToSkip] = Mathf.Sqrt(1.0f - sumOfSquaredMagnitudes);
}
}
}

View File

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

View File

@@ -3,7 +3,8 @@
"rootNamespace": "Unity.Netcode.Components", "rootNamespace": "Unity.Netcode.Components",
"references": [ "references": [
"Unity.Netcode.Runtime", "Unity.Netcode.Runtime",
"Unity.Collections" "Unity.Collections",
"Unity.Mathematics"
], ],
"allowUnsafeCode": true, "allowUnsafeCode": true,
"versionDefines": [ "versionDefines": [

View File

@@ -1,3 +1,7 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if UNITY_INCLUDE_TESTS
#if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")] [assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
#endif // UNITY_EDITOR
#endif // UNITY_INCLUDE_TESTS

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
@@ -102,15 +102,19 @@ namespace Unity.Netcode.Editor.CodeGen
private PostProcessorAssemblyResolver m_AssemblyResolver; private PostProcessorAssemblyResolver m_AssemblyResolver;
private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef; private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef;
private MethodReference m_MessagingSystem_CreateMessageAndGetVersion_MethodRef;
private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef; private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
private MethodReference m_MessagingSystem_MessageHandler_Constructor_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_ILPPMessageProvider___network_message_types_FieldRef;
private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef; private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef;
private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef; private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef;
private FieldReference m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef;
private MethodReference m_Type_GetTypeFromHandle_MethodRef; private MethodReference m_Type_GetTypeFromHandle_MethodRef;
private MethodReference m_List_Add_MethodRef; private MethodReference m_List_Add_MethodRef;
private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage); private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage);
private const string k_CreateMessageAndGetVersionName = nameof(MessagingSystem.CreateMessageAndGetVersion);
private bool ImportReferences(ModuleDefinition moduleDefinition) private bool ImportReferences(ModuleDefinition moduleDefinition)
{ {
@@ -126,6 +130,7 @@ namespace Unity.Netcode.Editor.CodeGen
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve(); TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
TypeDefinition messageHandlerTypeDef = null; TypeDefinition messageHandlerTypeDef = null;
TypeDefinition versionGetterTypeDef = null;
TypeDefinition messageWithHandlerTypeDef = null; TypeDefinition messageWithHandlerTypeDef = null;
TypeDefinition ilppMessageProviderTypeDef = null; TypeDefinition ilppMessageProviderTypeDef = null;
TypeDefinition messagingSystemTypeDef = null; TypeDefinition messagingSystemTypeDef = null;
@@ -137,6 +142,12 @@ namespace Unity.Netcode.Editor.CodeGen
continue; continue;
} }
if (versionGetterTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.VersionGetter))
{
versionGetterTypeDef = netcodeTypeDef;
continue;
}
if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler)) if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler))
{ {
messageWithHandlerTypeDef = netcodeTypeDef; messageWithHandlerTypeDef = netcodeTypeDef;
@@ -157,6 +168,7 @@ namespace Unity.Netcode.Editor.CodeGen
} }
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First()); 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); m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef);
foreach (var fieldDef in messageWithHandlerTypeDef.Fields) foreach (var fieldDef in messageWithHandlerTypeDef.Fields)
@@ -169,6 +181,9 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(MessagingSystem.MessageWithHandler.Handler): case nameof(MessagingSystem.MessageWithHandler.Handler):
m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef); m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
case nameof(MessagingSystem.MessageWithHandler.GetVersion):
m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
} }
} }
@@ -211,6 +226,9 @@ namespace Unity.Netcode.Editor.CodeGen
case k_ReceiveMessageName: case k_ReceiveMessageName:
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef); m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
case k_CreateMessageAndGetVersionName:
m_MessagingSystem_CreateMessageAndGetVersion_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
} }
} }
@@ -236,7 +254,7 @@ namespace Unity.Netcode.Editor.CodeGen
return staticCtorMethodDef; 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}); // MessagingSystem.__network_message_types.Add(new MessagingSystem.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
processor.Body.Variables.Add(new VariableDefinition(m_MessagingSystem_MessageWithHandler_TypeRef)); processor.Body.Variables.Add(new VariableDefinition(m_MessagingSystem_MessageWithHandler_TypeRef));
@@ -252,7 +270,7 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Call, m_Type_GetTypeFromHandle_MethodRef)); instructions.Add(processor.Create(OpCodes.Call, m_Type_GetTypeFromHandle_MethodRef));
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_MessageType_FieldRef)); 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.Ldloca, messageWithHandlerLocIdx));
instructions.Add(processor.Create(OpCodes.Ldnull)); instructions.Add(processor.Create(OpCodes.Ldnull));
@@ -260,6 +278,15 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_MessageHandler_Constructor_TypeRef)); instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_MessageHandler_Constructor_TypeRef));
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_Handler_FieldRef)); 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); // ILPPMessageProvider.__network_message_types.Add(tmp);
instructions.Add(processor.Create(OpCodes.Ldloc, messageWithHandlerLocIdx)); instructions.Add(processor.Create(OpCodes.Ldloc, messageWithHandlerLocIdx));
instructions.Add(processor.Create(OpCodes.Callvirt, m_List_Add_MethodRef)); instructions.Add(processor.Create(OpCodes.Callvirt, m_List_Add_MethodRef));
@@ -285,7 +312,9 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef); var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef);
receiveMethod.GenericArguments.Add(type); 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)); instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.Diagnostics;

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
@@ -9,9 +9,9 @@ using Mono.Cecil.Rocks;
using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing; using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine; using UnityEngine;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
using MethodAttributes = Mono.Cecil.MethodAttributes; using MethodAttributes = Mono.Cecil.MethodAttributes;
using ParameterAttributes = Mono.Cecil.ParameterAttributes; using ParameterAttributes = Mono.Cecil.ParameterAttributes;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
namespace Unity.Netcode.Editor.CodeGen namespace Unity.Netcode.Editor.CodeGen
{ {
@@ -139,6 +139,19 @@ namespace Unity.Netcode.Editor.CodeGen
return false; 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) private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
{ {
foreach (var typeDefinition in assembly.MainModule.Types) foreach (var typeDefinition in assembly.MainModule.Types)
@@ -153,6 +166,11 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var type in m_WrappedNetworkVariableTypes) foreach (var type in m_WrappedNetworkVariableTypes)
{ {
if (IsSpecialCaseType(type))
{
continue;
}
// If a serializable type isn't found, FallbackSerializer will be used automatically, which will // 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 // call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton
// for types that aren't in our official supported types list. // for types that aren't in our official supported types list.
@@ -257,6 +275,20 @@ namespace Unity.Netcode.Editor.CodeGen
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_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 TypeReference m_FastBufferWriter_TypeRef;
private readonly Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, 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 readonly List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
@@ -276,12 +308,13 @@ namespace Unity.Netcode.Editor.CodeGen
typeof(decimal), typeof(decimal),
typeof(double), typeof(double),
typeof(float), typeof(float),
typeof(int), // the following types have special handling
/*typeof(int),
typeof(uint), typeof(uint),
typeof(long), typeof(long),
typeof(ulong), typeof(ulong),
typeof(short), typeof(short),
typeof(ushort), typeof(ushort),*/
typeof(Vector2), typeof(Vector2),
typeof(Vector3), typeof(Vector3),
typeof(Vector2Int), typeof(Vector2Int),
@@ -293,6 +326,16 @@ namespace Unity.Netcode.Editor.CodeGen
typeof(Ray), typeof(Ray),
typeof(Ray2D) 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_Debug_LogError = nameof(Debug.LogError);
private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId);
@@ -343,6 +386,8 @@ namespace Unity.Netcode.Editor.CodeGen
TypeDefinition fastBufferWriterTypeDef = null; TypeDefinition fastBufferWriterTypeDef = null;
TypeDefinition fastBufferReaderTypeDef = null; TypeDefinition fastBufferReaderTypeDef = null;
TypeDefinition networkVariableSerializationTypesTypeDef = null; TypeDefinition networkVariableSerializationTypesTypeDef = null;
TypeDefinition bytePackerTypeDef = null;
TypeDefinition byteUnpackerTypeDef = null;
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
{ {
if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager)) if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager))
@@ -398,6 +443,18 @@ namespace Unity.Netcode.Editor.CodeGen
networkVariableSerializationTypesTypeDef = netcodeTypeDef; networkVariableSerializationTypesTypeDef = netcodeTypeDef;
continue; 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) foreach (var methodDef in debugTypeDef.Methods)
@@ -652,6 +709,82 @@ namespace Unity.Netcode.Editor.CodeGen
} }
} }
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; return true;
} }
@@ -704,6 +837,58 @@ namespace Unity.Netcode.Editor.CodeGen
GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams); GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams);
} }
private void GetAllBaseTypesAndResolveGenerics(TypeDefinition type, ref List<TypeReference> baseTypes, Dictionary<string, TypeReference> genericParameters)
{
if (type == null || type.BaseType == null || type.BaseType.Name == "Object")
{
return;
}
var baseType = type.BaseType;
var genericParams = new Dictionary<string, TypeReference>();
if (baseType.IsGenericInstance)
{
var genericType = (GenericInstanceType)baseType;
var newGenericType = new GenericInstanceType(baseType.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]);
genericParams[baseType.Resolve().GenericParameters[newGenericType.GenericArguments.Count - 1].Name] = genericParameters[argument.Name];
}
else
{
newGenericType.GenericArguments.Add(argument);
}
}
baseTypes.Add(newGenericType);
}
else
{
baseTypes.Add(baseType);
}
var resolved = type.BaseType.Resolve();
if (type.BaseType.IsGenericInstance)
{
var genericType = (GenericInstanceType)type.BaseType;
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
if (!genericParams.ContainsKey(resolved.GenericParameters[i].Name))
{
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
}
}
}
GetAllBaseTypesAndResolveGenerics(resolved, ref baseTypes, genericParams);
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines) private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines)
{ {
var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>(); var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>();
@@ -765,6 +950,34 @@ namespace Unity.Netcode.Editor.CodeGen
} }
} }
} }
{
var baseTypes = new List<TypeReference>();
var genericParams = new Dictionary<string, TypeReference>();
var resolved = type.Resolve();
if (type.IsGenericInstance)
{
var genericType = (GenericInstanceType)type;
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
}
}
GetAllBaseTypesAndResolveGenerics(type.Resolve(), ref baseTypes, genericParams);
foreach (var baseType in baseTypes)
{
if (baseType.Resolve().Name == typeof(NetworkVariable<>).Name || baseType.Resolve().Name == typeof(NetworkList<>).Name)
{
var genericInstanceType = (GenericInstanceType)baseType;
var wrappedType = genericInstanceType.GenericArguments[0];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}
}
} }
} }
@@ -1008,6 +1221,36 @@ namespace Unity.Netcode.Editor.CodeGen
private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef) 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 assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName;
var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef);
@@ -1154,6 +1397,36 @@ namespace Unity.Netcode.Editor.CodeGen
private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef) 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 assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName;
var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef); var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef);

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b2f70916f7024c66aa5dfe1e43c151a2
timeCreated: 1654274400

View 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);
}
}
}

View File

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

View 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);
}
}
}

View File

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

View 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;
}
}
}

View File

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

View 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();
}
}
}

View File

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

View File

@@ -4,6 +4,7 @@ using Unity.Netcode.Transports.UNET;
#endif #endif
using Unity.Netcode.Transports.UTP; using Unity.Netcode.Transports.UTP;
using UnityEditor; using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
@@ -43,7 +44,109 @@ namespace Unity.Netcode.Editor
[CustomEditor(typeof(UnityTransport), true)] [CustomEditor(typeof(UnityTransport), true)]
public class UnityTransportEditor : HiddenScriptEditor 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 #if COM_UNITY_MODULES_ANIMATION

View File

@@ -1,11 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using UnityEngine; using Unity.Netcode.Editor.Configuration;
using UnityEditor; using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="NetworkBehaviour"/>
/// </summary>
[CustomEditor(typeof(NetworkBehaviour), true)] [CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects] [CanEditMultipleObjects]
public class NetworkBehaviourEditor : UnityEditor.Editor 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 readonly Dictionary<string, object> m_NetworkVariableObjects = new Dictionary<string, object>();
private GUIContent m_NetworkVariableLabelGuiContent; private GUIContent m_NetworkVariableLabelGuiContent;
private GUIContent m_NetworkListLabelGuiContent;
private void Init(MonoScript script) private void Init(MonoScript script)
{ {
@@ -26,6 +31,7 @@ namespace Unity.Netcode.Editor
m_NetworkVariableObjects.Clear(); 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_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); var fields = script.GetClass().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
for (int i = 0; i < fields.Length; i++) for (int i = 0; i < fields.Length; i++)
@@ -33,8 +39,15 @@ namespace Unity.Netcode.Editor
var ft = fields[i].FieldType; var ft = fields[i].FieldType;
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true)) if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
{ {
m_NetworkVariableNames.Add(fields[i].Name); m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
m_NetworkVariableFields.Add(fields[i].Name, fields[i]); 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(); EditorGUILayout.BeginHorizontal();
if (genericType.IsValueType) 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); var genericMethod = method.MakeGenericMethod(genericType);
genericMethod.Invoke(this, new[] { (object)index }); genericMethod.Invoke(this, new[] { (object)index });
} }
else else
{ {
EditorGUILayout.LabelField("Type not renderable"); EditorGUILayout.LabelField("Type not renderable");
}
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x)); GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
}
private void RenderNetworkVariableValueType<T>(int index) where T : unmanaged private void RenderNetworkContainerValueType<T>(int index) where T : unmanaged, IEquatable<T>
{
try
{ {
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target); 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;
}
}
EditorGUILayout.EndHorizontal();
}
private void RenderNetworkVariableValueType<T>(int index, NetworkVariable<T> networkVariable) where T : unmanaged
{
var type = typeof(T); var type = typeof(T);
object val = networkVariable.Value; object val = networkVariable.Value;
string name = m_NetworkVariableNames[index]; string variableName = m_NetworkVariableNames[index];
var behaviour = (NetworkBehaviour)target; var behaviour = (NetworkBehaviour)target;
@@ -95,47 +131,51 @@ namespace Unity.Netcode.Editor
{ {
if (type == typeof(int)) if (type == typeof(int))
{ {
val = EditorGUILayout.IntField(name, (int)val); val = EditorGUILayout.IntField(variableName, (int)val);
} }
else if (type == typeof(uint)) else if (type == typeof(uint))
{ {
val = (uint)EditorGUILayout.LongField(name, (long)((uint)val)); val = (uint)EditorGUILayout.LongField(variableName, (uint)val);
} }
else if (type == typeof(short)) else if (type == typeof(short))
{ {
val = (short)EditorGUILayout.IntField(name, (int)((short)val)); val = (short)EditorGUILayout.IntField(variableName, (short)val);
} }
else if (type == typeof(ushort)) else if (type == typeof(ushort))
{ {
val = (ushort)EditorGUILayout.IntField(name, (int)((ushort)val)); val = (ushort)EditorGUILayout.IntField(variableName, (ushort)val);
} }
else if (type == typeof(sbyte)) else if (type == typeof(sbyte))
{ {
val = (sbyte)EditorGUILayout.IntField(name, (int)((sbyte)val)); val = (sbyte)EditorGUILayout.IntField(variableName, (sbyte)val);
} }
else if (type == typeof(byte)) else if (type == typeof(byte))
{ {
val = (byte)EditorGUILayout.IntField(name, (int)((byte)val)); val = (byte)EditorGUILayout.IntField(variableName, (byte)val);
} }
else if (type == typeof(long)) else if (type == typeof(long))
{ {
val = EditorGUILayout.LongField(name, (long)val); val = EditorGUILayout.LongField(variableName, (long)val);
} }
else if (type == typeof(ulong)) 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(float))
{
val = EditorGUILayout.FloatField(variableName, (float)((float)val));
} }
else if (type == typeof(bool)) else if (type == typeof(bool))
{ {
val = EditorGUILayout.Toggle(name, (bool)val); val = EditorGUILayout.Toggle(variableName, (bool)val);
} }
else if (type == typeof(string)) else if (type == typeof(string))
{ {
val = EditorGUILayout.TextField(name, (string)val); val = EditorGUILayout.TextField(variableName, (string)val);
} }
else if (type.IsEnum) else if (type.IsEnum)
{ {
val = EditorGUILayout.EnumPopup(name, (Enum)val); val = EditorGUILayout.EnumPopup(variableName, (Enum)val);
} }
else else
{ {
@@ -146,11 +186,31 @@ namespace Unity.Netcode.Editor
} }
else else
{ {
EditorGUILayout.LabelField(name, EditorStyles.wordWrappedLabel); EditorGUILayout.LabelField(variableName, EditorStyles.wordWrappedLabel);
EditorGUILayout.SelectableLabel(val.ToString(), 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/> /// <inheritdoc/>
public override void OnInspectorGUI() public override void OnInspectorGUI()
@@ -230,8 +290,6 @@ namespace Unity.Netcode.Editor
CheckForNetworkObject((target as NetworkBehaviour).gameObject); CheckForNetworkObject((target as NetworkBehaviour).gameObject);
} }
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
/// <summary> /// <summary>
/// Recursively finds the root parent of a <see cref="Transform"/> /// Recursively finds the root parent of a <see cref="Transform"/>
/// </summary> /// </summary>
@@ -308,7 +366,7 @@ namespace Unity.Netcode.Editor
// and the user has already turned "Auto-Add NetworkObject" on when first notified about the requirement // 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" // then just send a reminder to the user why the NetworkObject they just deleted seemingly "re-appeared"
// again. // 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.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."); Debug.Log($"To reset Auto-Add NetworkObjects: Select the Netcode->General->Reset Auto-Add NetworkObject menu item.");
@@ -317,7 +375,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 // 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)}", 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)", $"{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>(); gameObject.AddComponent<NetworkObject>();
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
@@ -327,20 +385,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);
}
}
} }
} }

View File

@@ -1,8 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Unity.Netcode.Editor.Configuration;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEditorInternal;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
@@ -14,7 +15,6 @@ namespace Unity.Netcode.Editor
[CanEditMultipleObjects] [CanEditMultipleObjects]
public class NetworkManagerEditor : UnityEditor.Editor public class NetworkManagerEditor : UnityEditor.Editor
{ {
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
private static GUIStyle s_CenteredWordWrappedLabelStyle; private static GUIStyle s_CenteredWordWrappedLabelStyle;
private static GUIStyle s_HelpBoxStyle; private static GUIStyle s_HelpBoxStyle;
@@ -40,8 +40,7 @@ namespace Unity.Netcode.Editor
private SerializedProperty m_NetworkIdRecycleDelayProperty; private SerializedProperty m_NetworkIdRecycleDelayProperty;
private SerializedProperty m_RpcHashSizeProperty; private SerializedProperty m_RpcHashSizeProperty;
private SerializedProperty m_LoadSceneTimeOutProperty; private SerializedProperty m_LoadSceneTimeOutProperty;
private SerializedProperty m_PrefabsList;
private ReorderableList m_NetworkPrefabsList;
private NetworkManager m_NetworkManager; private NetworkManager m_NetworkManager;
private bool m_Initialized; private bool m_Initialized;
@@ -106,7 +105,9 @@ namespace Unity.Netcode.Editor
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay"); m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
m_PrefabsList = m_NetworkConfigProperty
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
ReloadTransports(); ReloadTransports();
} }
@@ -132,76 +133,9 @@ namespace Unity.Netcode.Editor
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay"); m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
} m_PrefabsList = m_NetworkConfigProperty
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
private void OnEnable() .FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
{
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");
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -224,7 +158,62 @@ namespace Unity.Netcode.Editor
EditorGUILayout.PropertyField(m_PlayerPrefabProperty); EditorGUILayout.PropertyField(m_PlayerPrefabProperty);
EditorGUILayout.Space(); 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.Space();
EditorGUILayout.LabelField("General", EditorStyles.boldLabel); EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
@@ -242,13 +231,7 @@ namespace Unity.Netcode.Editor
{ {
ReloadTransports(); ReloadTransports();
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]); var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
if (transportComponent == null)
{
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
}
m_NetworkTransportProperty.objectReferenceValue = transportComponent; m_NetworkTransportProperty.objectReferenceValue = transportComponent;
Repaint(); Repaint();
@@ -359,22 +342,26 @@ namespace Unity.Netcode.Editor
const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools"; const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
const string infoIconName = "console.infoicon"; const string infoIconName = "console.infoicon";
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0) if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
{ {
return; return;
} }
if (s_CenteredWordWrappedLabelStyle == null) if (s_CenteredWordWrappedLabelStyle == null)
{ {
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label); s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label)
s_CenteredWordWrappedLabelStyle.wordWrap = true; {
s_CenteredWordWrappedLabelStyle.alignment = TextAnchor.MiddleLeft; wordWrap = true,
alignment = TextAnchor.MiddleLeft
};
} }
if (s_HelpBoxStyle == null) if (s_HelpBoxStyle == null)
{ {
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox); s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox)
s_HelpBoxStyle.padding = new RectOffset(10, 10, 10, 10); {
padding = new RectOffset(10, 10, 10, 10)
};
} }
var openDocsButtonStyle = GUI.skin.button; var openDocsButtonStyle = GUI.skin.button;
@@ -405,7 +392,7 @@ namespace Unity.Netcode.Editor
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (GUILayout.Button(dismissButtonText, dismissButtonStyle, GUILayout.ExpandWidth(false))) if (GUILayout.Button(dismissButtonText, dismissButtonStyle, GUILayout.ExpandWidth(false)))
{ {
PlayerPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 1); NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(1);
} }
EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link); EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using UnityEditor;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
@@ -32,6 +33,24 @@ namespace Unity.Netcode.Editor
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged; EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged; 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) private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
@@ -65,7 +84,11 @@ namespace Unity.Netcode.Editor
var scenesList = EditorBuildSettings.scenes.ToList(); var scenesList = EditorBuildSettings.scenes.ToList();
var activeScene = SceneManager.GetActiveScene(); var activeScene = SceneManager.GetActiveScene();
var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 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>(); var networkManager = Object.FindObjectOfType<NetworkManager>();
#endif
if (!isSceneInBuildSettings && networkManager != null) if (!isSceneInBuildSettings && networkManager != null)
{ {
if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement) if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement)

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine;
using UnityEditor; using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {

View File

@@ -1,6 +1,6 @@
using Unity.Netcode.Components;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using Unity.Netcode.Components;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
@@ -25,6 +25,11 @@ namespace Unity.Netcode.Editor
private SerializedProperty m_InLocalSpaceProperty; private SerializedProperty m_InLocalSpaceProperty;
private SerializedProperty m_InterpolateProperty; private SerializedProperty m_InterpolateProperty;
private SerializedProperty m_UseQuaternionSynchronization;
private SerializedProperty m_UseQuaternionCompression;
private SerializedProperty m_UseHalfFloatPrecision;
private SerializedProperty m_SlerpPosition;
private static int s_ToggleOffset = 45; private static int s_ToggleOffset = 45;
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5; private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5;
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position"); private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
@@ -48,6 +53,10 @@ namespace Unity.Netcode.Editor
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold)); m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold));
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace)); m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace));
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization));
m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression));
m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision));
m_SlerpPosition = serializedObject.FindProperty(nameof(NetworkTransform.SlerpPosition));
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -71,6 +80,8 @@ namespace Unity.Netcode.Editor
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
if (!m_UseQuaternionSynchronization.boolValue)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
@@ -88,6 +99,13 @@ namespace Unity.Netcode.Editor
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
else
{
m_SyncRotationXProperty.boolValue = true;
m_SyncRotationYProperty.boolValue = true;
m_SyncRotationZProperty.boolValue = true;
}
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
@@ -116,6 +134,17 @@ namespace Unity.Netcode.Editor
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel); EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_InLocalSpaceProperty); EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
EditorGUILayout.PropertyField(m_InterpolateProperty); EditorGUILayout.PropertyField(m_InterpolateProperty);
EditorGUILayout.PropertyField(m_SlerpPosition);
EditorGUILayout.PropertyField(m_UseQuaternionSynchronization);
if (m_UseQuaternionSynchronization.boolValue)
{
EditorGUILayout.PropertyField(m_UseQuaternionCompression);
}
else
{
m_UseQuaternionCompression.boolValue = false;
}
EditorGUILayout.PropertyField(m_UseHalfFloatPrecision);
#if COM_UNITY_MODULES_PHYSICS #if COM_UNITY_MODULES_PHYSICS
// if rigidbody is present but network rigidbody is not present // if rigidbody is present but network rigidbody is not present

View File

@@ -1,15 +1,25 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Netcode.Components")]
#if UNITY_EDITOR #if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] [assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")] [assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
#endif #endif // UNITY_EDITOR
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")] #if MULTIPLAYER_TOOLS
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] [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.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")] [assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")] [assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")] #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

View File

@@ -1,8 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine;
using System.Linq; using System.Linq;
using Unity.Collections; using Unity.Collections;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Netcode 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.")] [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; public GameObject PlayerPrefab;
/// <summary>
/// A list of prefabs that can be dynamically spawned.
/// </summary>
[SerializeField] [SerializeField]
[Tooltip("The prefabs that can be spawned across the network")] public NetworkPrefabs Prefabs = new NetworkPrefabs();
internal List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
/// <summary>
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
/// Generated at runtime and OnValidate
/// </summary>
internal Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
internal Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
/// <summary> /// <summary>
@@ -219,6 +208,14 @@ namespace Unity.Netcode
private ulong? m_ConfigHash = null; private ulong? m_ConfigHash = null;
/// <summary>
/// Clears out the configuration hash value generated for a specific network session
/// </summary>
internal void ClearConfigHash()
{
m_ConfigHash = null;
}
/// <summary> /// <summary>
/// Gets a SHA256 hash of parts of the NetworkConfig instance /// Gets a SHA256 hash of parts of the NetworkConfig instance
/// </summary> /// </summary>
@@ -239,7 +236,7 @@ namespace Unity.Netcode
if (ForceSamePrefabs) if (ForceSamePrefabs)
{ {
var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key); var sortedDictionary = Prefabs.NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
foreach (var sortedEntry in sortedDictionary) foreach (var sortedEntry in sortedDictionary)
{ {
@@ -273,6 +270,75 @@ namespace Unity.Netcode
{ {
return hash == GetConfig(); return hash == GetConfig();
} }
internal void InitializePrefabs()
{
if (HasOldPrefabList())
{
MigrateOldNetworkPrefabsToNetworkPrefabsList();
}
Prefabs.Initialize();
}
[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&lt;NetworkPrefab&gt; 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&lt;NetworkPrefab&gt; 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;
}
}

View File

@@ -3,10 +3,23 @@ using UnityEngine;
namespace Unity.Netcode 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, None,
/// <summary>
/// Override the prefab when the given SourcePrefabToOverride is requested
/// </summary>
Prefab, 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 Hash
} }
@@ -14,10 +27,10 @@ namespace Unity.Netcode
/// Class that represents a NetworkPrefab /// Class that represents a NetworkPrefab
/// </summary> /// </summary>
[Serializable] [Serializable]
internal class NetworkPrefab public class NetworkPrefab
{ {
/// <summary> /// <summary>
/// The override setttings for this NetworkPrefab /// The override settings for this NetworkPrefab
/// </summary> /// </summary>
public NetworkPrefabOverride Override; public NetworkPrefabOverride Override;
@@ -41,5 +54,168 @@ namespace Unity.Netcode
/// The prefab to replace (override) the source prefab with /// The prefab to replace (override) the source prefab with
/// </summary> /// </summary>
public GameObject OverridingTargetPrefab; 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}}}";
}
} }
} }

View File

@@ -0,0 +1,308 @@
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()
{
Shutdown();
}
/// <summary>
/// Deregister from add and remove events
/// Clear the list
/// </summary>
internal void Shutdown()
{
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd -= AddTriggeredByNetworkPrefabList;
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
}
NetworkPrefabsLists.Clear();
}
/// <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;
}
}
}

View File

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

View 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;
}
}
}

View File

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

View File

@@ -52,6 +52,8 @@ namespace Unity.Netcode
public static void SetDefaults() public static void SetDefaults()
{ {
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager)); SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
SetDefault<IRealTimeProvider>(networkManager => new RealTimeProvider());
} }
private static void SetDefault<T>(CreateObjectDelegate creator) private static void SetDefault<T>(CreateObjectDelegate creator)

View File

@@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEngine;
using System.Reflection; using System.Reflection;
using Unity.Collections; using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -21,6 +21,7 @@ namespace Unity.Netcode
Client = 2 Client = 2
} }
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour); internal virtual string __getTypeName() => nameof(NetworkBehaviour);
@@ -81,7 +82,7 @@ namespace Unity.Netcode
var context = new NetworkContext var context = new NetworkContext
{ {
SenderId = NetworkManager.ServerClientId, SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup, Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = NetworkManager, SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message. // header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty. // RpcMessage doesn't access this stuff so it's just left empty.
@@ -218,7 +219,7 @@ namespace Unity.Netcode
var context = new NetworkContext var context = new NetworkContext
{ {
SenderId = NetworkManager.ServerClientId, SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup, Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = NetworkManager, SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message. // header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty. // RpcMessage doesn't access this stuff so it's just left empty.
@@ -286,7 +287,18 @@ namespace Unity.Netcode
/// Gets the NetworkManager that owns this NetworkBehaviour instance /// 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 /// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
/// </summary> /// </summary>
public NetworkManager NetworkManager => NetworkObject.NetworkManager; public NetworkManager NetworkManager
{
get
{
if (NetworkObject?.NetworkManager != null)
{
return NetworkObject?.NetworkManager;
}
return NetworkManager.Singleton;
}
}
/// <summary> /// <summary>
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject /// If a NetworkObject is assigned, it will return whether or not this NetworkObject
@@ -302,18 +314,18 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets if we are executing as server /// Gets if we are executing as server
/// </summary> /// </summary>
protected bool IsServer { get; private set; } public bool IsServer { get; private set; }
/// <summary> /// <summary>
/// Gets if we are executing as client /// Gets if we are executing as client
/// </summary> /// </summary>
protected bool IsClient { get; private set; } public bool IsClient { get; private set; }
/// <summary> /// <summary>
/// Gets if we are executing as Host, I.E Server and Client /// Gets if we are executing as Host, I.E Server and Client
/// </summary> /// </summary>
protected bool IsHost { get; private set; } public bool IsHost { get; private set; }
/// <summary> /// <summary>
/// Gets Whether or not the object has a owner /// Gets Whether or not the object has a owner
@@ -335,24 +347,30 @@ namespace Unity.Netcode
m_NetworkObject.NetworkManager.IsServer; 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 /// 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 /// 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 /// (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 /// 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 /// 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 /// 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> /// </summary>
public NetworkObject NetworkObject public NetworkObject NetworkObject
{ {
get get
{
try
{ {
if (m_NetworkObject == null) if (m_NetworkObject == null)
{ {
m_NetworkObject = GetComponentInParent<NetworkObject>(); m_NetworkObject = GetComponentInParent<NetworkObject>();
} }
}
catch (Exception)
{
return null;
}
// ShutdownInProgress check: // ShutdownInProgress check:
// This prevents an edge case scenario where the NetworkManager is shutting down but user code // This prevents an edge case scenario where the NetworkManager is shutting down but user code
@@ -552,13 +570,10 @@ namespace Unity.Netcode
if (list == null) if (list == null)
{ {
list = new List<FieldInfo>(); list = new List<FieldInfo>();
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
}
else
{
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
} }
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour)) if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
{ {
return GetFieldInfoForTypeRecursive(type.BaseType, list); return GetFieldInfoForTypeRecursive(type.BaseType, list);
@@ -582,13 +597,7 @@ namespace Unity.Netcode
var fieldType = sortedFields[i].FieldType; var fieldType = sortedFields[i].FieldType;
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase))) if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
{ {
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this); var instance = (NetworkVariableBase)sortedFields[i].GetValue(this) ?? throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
if (instance == null)
{
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
}
instance.Initialize(this); instance.Initialize(this);
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name)); var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
@@ -712,7 +721,7 @@ namespace Unity.Netcode
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE); var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
using (tmpWriter) using (tmpWriter)
{ {
message.Serialize(tmpWriter); message.Serialize(tmpWriter, message.Version);
} }
} }
else else
@@ -745,6 +754,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) internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
{ {
if (NetworkVariableFields.Count == 0) if (NetworkVariableFields.Count == 0)
@@ -754,11 +771,17 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
if (canClientRead) if (NetworkVariableFields[j].CanClientRead(targetClientId))
{
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
var writePos = writer.Position; 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); writer.WriteValueSafe((ushort)0);
var startPos = writer.Position; var startPos = writer.Position;
NetworkVariableFields[j].WriteField(writer); NetworkVariableFields[j].WriteField(writer);
@@ -768,13 +791,27 @@ namespace Unity.Netcode
writer.Seek(startPos + size); writer.Seek(startPos + size);
} }
else else
{
NetworkVariableFields[j].WriteField(writer);
}
}
else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
writer.WriteValueSafe((ushort)0); 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) if (NetworkVariableFields.Count == 0)
{ {
@@ -783,13 +820,23 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
reader.ReadValueSafe(out ushort varSize); var varSize = (ushort)0;
var readStartPos = 0;
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
reader.ReadValueSafe(out varSize);
if (varSize == 0) if (varSize == 0)
{ {
continue; 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); NetworkVariableFields[j].ReadField(reader);
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
@@ -826,6 +873,156 @@ namespace Unity.Netcode
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null; 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>
/// <param name="targetClientId">the relative client identifier being synchronized</param>
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
}
/// <summary>
/// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance.
/// </summary>
/// <remarks>
/// This value will be set prior to <see cref="OnSynchronize{T}(ref BufferSerializer{T})"/> being invoked.
/// For writing (server-side), this is useful to know which client will receive the serialized data.
/// For reading (client-side), this will be the <see cref="NetworkManager.LocalClientId"/>.
/// When synchronization of this instance is complete, this value will be reset to 0
/// </remarks>
protected ulong m_TargetIdBeingSynchronized { get; private set; }
/// <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, ulong targetClientId = 0) where T : IReaderWriter
{
m_TargetIdBeingSynchronized = targetClientId;
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;
// Reset before exiting
m_TargetIdBeingSynchronized = default;
// 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;
}
// Reset before exiting
m_TargetIdBeingSynchronized = default;
// Skip over the entry if deserialization fails
if (synchronizationError)
{
var skipToPosition = positionBeforeSynchronize + expectedBytesToRead;
reader.Seek(skipToPosition);
return false;
}
return true;
}
}
/// <summary> /// <summary>
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to. /// 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 /// NOTE: If you override this, you will want to always invoke this base class version of this

View File

@@ -72,6 +72,23 @@ 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 // Now, reset all the no-longer-dirty variables
foreach (var dirtyobj in m_DirtyNetworkObjects) foreach (var dirtyobj in m_DirtyNetworkObjects)
{ {

View File

@@ -67,6 +67,9 @@ namespace Unity.Netcode
internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>(); internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>();
// Stores the objects that need to be shown at end-of-frame
internal Dictionary<ulong, List<NetworkObject>> ObjectsToShowToClient = new Dictionary<ulong, List<NetworkObject>>();
/// <summary> /// <summary>
/// The <see cref="NetworkPrefabHandler"/> instance created after starting the <see cref="NetworkManager"/> /// The <see cref="NetworkPrefabHandler"/> instance created after starting the <see cref="NetworkManager"/>
/// </summary> /// </summary>
@@ -86,6 +89,12 @@ namespace Unity.Netcode
private bool m_ShuttingDown; private bool m_ShuttingDown;
private bool m_StopProcessingMessages; private bool m_StopProcessingMessages;
/// <summary>
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
/// </summary>
public string DisconnectReason { get; internal set; }
private class NetworkManagerHooks : INetworkHooks private class NetworkManagerHooks : INetworkHooks
{ {
private NetworkManager m_NetworkManager; private NetworkManager m_NetworkManager;
@@ -134,6 +143,16 @@ namespace Unity.Netcode
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context) public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
{ {
if (m_NetworkManager.IsServer)
{
if (messageType == typeof(ConnectionApprovedMessage))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from a client on the server side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
}
return false;
}
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) && if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage)))) (client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
{ {
@@ -145,6 +164,36 @@ namespace Unity.Netcode
return false; return false;
} }
if (m_NetworkManager.ConnectedClients.TryGetValue(senderId, out NetworkClient connectedClient) && messageType == typeof(ConnectionRequestMessage))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from a client when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
}
return false;
}
}
else
{
if (messageType == typeof(ConnectionRequestMessage))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionRequestMessage)} was received from the server on the client side. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
}
return false;
}
if (m_NetworkManager.IsConnectedClient && messageType == typeof(ConnectionApprovedMessage))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"A {nameof(ConnectionApprovedMessage)} was received from the server when the connection has already been established. This should not happen. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Message Size: {messageContent.Length}. Message Content: {MessagingSystem.ByteArrayToString(messageContent.ToArray(), 0, messageContent.Length)}");
}
return false;
}
}
return !m_NetworkManager.m_StopProcessingMessages; return !m_NetworkManager.m_StopProcessingMessages;
} }
@@ -189,14 +238,14 @@ namespace Unity.Netcode
{ {
if (gameObject.TryGetComponent<NetworkObject>(out var networkObject)) if (gameObject.TryGetComponent<NetworkObject>(out var networkObject))
{ {
if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash)) if (NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{ {
switch (NetworkConfig.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override) switch (NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override)
{ {
case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Hash:
case NetworkPrefabOverride.Prefab: case NetworkPrefabOverride.Prefab:
{ {
return NetworkConfig.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab; return NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab;
} }
} }
} }
@@ -249,6 +298,8 @@ namespace Unity.Netcode
internal IDeferredMessageManager DeferredMessageManager { get; private set; } internal IDeferredMessageManager DeferredMessageManager { get; private set; }
internal IRealTimeProvider RealTimeProvider { get; private set; }
/// <summary> /// <summary>
/// Gets the CustomMessagingManager for this NetworkManager /// Gets the CustomMessagingManager for this NetworkManager
/// </summary> /// </summary>
@@ -274,7 +325,7 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public ulong LocalClientId public ulong LocalClientId
{ {
get => IsServer ? NetworkConfig.NetworkTransport.ServerClientId : m_LocalClientId; get => m_LocalClientId;
internal set => m_LocalClientId = value; internal set => m_LocalClientId = value;
} }
@@ -400,10 +451,28 @@ namespace Unity.Netcode
public event Action<ulong> OnClientDisconnectCallback = null; public event Action<ulong> OnClientDisconnectCallback = null;
/// <summary> /// <summary>
/// The callback to invoke once the server is ready /// This callback is invoked when the local server is started and listening for incoming connections.
/// </summary> /// </summary>
public event Action OnServerStarted = null; public event Action OnServerStarted = null;
/// <summary>
/// The callback to invoke once the local client is ready
/// </summary>
public event Action OnClientStarted = null;
/// <summary>
/// This callback is invoked once the local server is stopped.
/// </summary>
/// <param name="arg1">The first parameter of this event will be set to <see cref="true"/> when stopping a host instance and <see cref="false"/> when stopping a server instance.</param>
public event Action<bool> OnServerStopped = null;
/// <summary>
/// The callback to invoke once the local client stops
/// </summary>
/// <remarks>The parameter states whether the client was running in host mode</remarks>
/// <param name="arg1">The first parameter of this event will be set to <see cref="true"/> when stopping the host client and <see cref="false"/> when stopping a standard client instance.</param>
public event Action<bool> OnClientStopped = null;
/// <summary> /// <summary>
/// The callback to invoke if the <see cref="NetworkTransport"/> fails. /// The callback to invoke if the <see cref="NetworkTransport"/> fails.
/// </summary> /// </summary>
@@ -443,6 +512,12 @@ namespace Unity.Netcode
/// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then. /// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
/// </summary> /// </summary>
public bool Pending; public bool Pending;
/// <summary>
/// Optional reason. If Approved is false, this reason will be sent to the client so they know why they
/// were not approved.
/// </summary>
public string Reason;
} }
/// <summary> /// <summary>
@@ -496,6 +571,19 @@ namespace Unity.Netcode
internal static event Action OnSingletonReady; internal static event Action OnSingletonReady;
#if UNITY_EDITOR
internal delegate void ResetNetworkManagerDelegate(NetworkManager manager);
internal static ResetNetworkManagerDelegate OnNetworkManagerReset;
#endif
private void Reset()
{
#if UNITY_EDITOR
OnNetworkManagerReset?.Invoke(this);
#endif
}
#if UNITY_EDITOR #if UNITY_EDITOR
internal void OnValidate() internal void OnValidate()
{ {
@@ -521,24 +609,30 @@ namespace Unity.Netcode
} }
// During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it // During OnValidate we will always clear out NetworkPrefabOverrideLinks and rebuild it
NetworkConfig.NetworkPrefabOverrideLinks.Clear(); NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Clear();
var prefabs = NetworkConfig.Prefabs.Prefabs;
// Check network prefabs and assign to dictionary for quick look up // Check network prefabs and assign to dictionary for quick look up
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++) for (int i = 0; i < prefabs.Count; i++)
{ {
var networkPrefab = NetworkConfig.NetworkPrefabs[i]; var networkPrefab = prefabs[i];
var networkPrefabGo = networkPrefab?.Prefab; var networkPrefabGo = networkPrefab?.Prefab;
if (networkPrefabGo != null) if (networkPrefabGo == null)
{ {
if (!networkPrefabGo.TryGetComponent<NetworkObject>(out var networkObject)) continue;
}
var networkObject = networkPrefabGo.GetComponent<NetworkObject>();
if (networkObject == null)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
NetworkLog.LogError($"Cannot register {PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root"); NetworkLog.LogError($"Cannot register {PrefabDebugHelper(networkPrefab)}, it does not have a {nameof(NetworkObject)} component at its root");
} }
continue;
} }
else
{
{ {
var childNetworkObjects = new List<NetworkObject>(); var childNetworkObjects = new List<NetworkObject>();
networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects); networkPrefabGo.GetComponentsInChildren(true, childNetworkObjects);
@@ -550,46 +644,6 @@ namespace Unity.Netcode
} }
} }
} }
// Default to the standard NetworkPrefab.Prefab's NetworkObject first
var globalObjectIdHash = networkObject.GlobalObjectIdHash;
// Now check to see if it has an override
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Prefab:
{
if (NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride == null &&
NetworkConfig.NetworkPrefabs[i].Prefab != null)
{
if (networkPrefab.SourcePrefabToOverride == null)
{
networkPrefab.SourcePrefabToOverride = networkPrefabGo;
}
globalObjectIdHash = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>().GlobalObjectIdHash;
}
break;
}
case NetworkPrefabOverride.Hash:
globalObjectIdHash = networkPrefab.SourceHashToOverride;
break;
}
// Add to the NetworkPrefabOverrideLinks or handle a new (blank) entries
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(globalObjectIdHash))
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(globalObjectIdHash, networkPrefab);
}
else
{
// Duplicate entries can happen when adding a new entry into a list of existing entries
// Either this is user error or a new entry, either case we replace it with a new, blank, NetworkPrefab under this condition
NetworkConfig.NetworkPrefabs[i] = new NetworkPrefab();
}
}
}
} }
} }
#endif #endif
@@ -628,22 +682,9 @@ namespace Unity.Netcode
} }
var networkPrefab = new NetworkPrefab { Prefab = prefab }; var networkPrefab = new NetworkPrefab { Prefab = prefab };
NetworkConfig.NetworkPrefabs.Add(networkPrefab); bool added = NetworkConfig.Prefabs.Add(networkPrefab);
if (IsListening) if (IsListening && added)
{ {
var sourcePrefabGlobalObjectIdHash = (uint)0;
var targetPrefabGlobalObjectIdHash = (uint)0;
if (!ShouldAddPrefab(networkPrefab, out sourcePrefabGlobalObjectIdHash, out targetPrefabGlobalObjectIdHash))
{
NetworkConfig.NetworkPrefabs.Remove(networkPrefab);
return;
}
if (!AddPrefabRegistration(networkPrefab, sourcePrefabGlobalObjectIdHash, targetPrefabGlobalObjectIdHash))
{
NetworkConfig.NetworkPrefabs.Remove(networkPrefab);
return;
}
DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash); DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
} }
} }
@@ -666,221 +707,14 @@ namespace Unity.Netcode
} }
var globalObjectIdHash = prefab.GetComponent<NetworkObject>().GlobalObjectIdHash; var globalObjectIdHash = prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
for (var i = 0; i < NetworkConfig.NetworkPrefabs.Count; ++i) NetworkConfig.Prefabs.Remove(prefab);
{
if (NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash == globalObjectIdHash)
{
NetworkConfig.NetworkPrefabs.RemoveAt(i);
break;
}
}
if (PrefabHandler.ContainsHandler(globalObjectIdHash)) if (PrefabHandler.ContainsHandler(globalObjectIdHash))
{ {
PrefabHandler.RemoveHandler(globalObjectIdHash); PrefabHandler.RemoveHandler(globalObjectIdHash);
} }
if (NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, out var targetPrefab))
{
NetworkConfig.NetworkPrefabOverrideLinks.Remove(globalObjectIdHash);
var targetHash = targetPrefab.Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
if (NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetHash))
{
NetworkConfig.OverrideToNetworkPrefab.Remove(targetHash);
}
}
} }
private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabGlobalObjectIdHash, out uint targetPrefabGlobalObjectIdHash, int index = -1) internal void Initialize(bool server)
{
sourcePrefabGlobalObjectIdHash = 0;
targetPrefabGlobalObjectIdHash = 0;
var networkObject = (NetworkObject)null;
if (networkPrefab == null || (networkPrefab.Prefab == null && networkPrefab.Override == NetworkPrefabOverride.None))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning(
$"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {index})");
}
return false;
}
else if (networkPrefab.Override == NetworkPrefabOverride.None)
{
if (!networkPrefab.Prefab.TryGetComponent(out networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} is missing " +
$"a {nameof(NetworkObject)} component (entry will be ignored).");
}
return false;
}
// Otherwise get the GlobalObjectIdHash value
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
}
else // Validate Overrides
{
// Validate source prefab override values first
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Hash:
{
if (networkPrefab.SourceHashToOverride == 0)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourceHashToOverride)} is zero " +
"(entry will be ignored).");
}
return false;
}
sourcePrefabGlobalObjectIdHash = networkPrefab.SourceHashToOverride;
break;
}
case NetworkPrefabOverride.Prefab:
{
if (networkPrefab.SourcePrefabToOverride == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourcePrefabToOverride)} is null (entry will be ignored).");
}
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {networkPrefab.SourceHashToOverride} will be removed and ignored.");
return false;
}
else
{
if (!networkPrefab.SourcePrefabToOverride.TryGetComponent(out networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({networkPrefab.SourcePrefabToOverride.name}) " +
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
}
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry (\"{networkPrefab.SourcePrefabToOverride.name}\") will be removed and ignored.");
return false;
}
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
}
break;
}
}
// Validate target prefab override values next
if (networkPrefab.OverridingTargetPrefab == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.OverridingTargetPrefab)} is null!");
}
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Hash:
{
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {networkPrefab.SourceHashToOverride} will be removed and ignored.");
break;
}
case NetworkPrefabOverride.Prefab:
{
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({networkPrefab.SourcePrefabToOverride.name}) will be removed and ignored.");
break;
}
}
return false;
}
else
{
targetPrefabGlobalObjectIdHash = networkPrefab.OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
}
}
return true;
}
internal bool AddPrefabRegistration(NetworkPrefab networkPrefab, uint sourcePrefabGlobalObjectIdHash, uint targetPrefabGlobalObjectIdHash)
{
// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(sourcePrefabGlobalObjectIdHash))
{
if (networkPrefab.Override == NetworkPrefabOverride.None)
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
}
else
{
if (!NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetPrefabGlobalObjectIdHash))
{
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Prefab:
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
}
break;
case NetworkPrefabOverride.Hash:
{
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
}
break;
}
}
else
{
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: {targetPrefabGlobalObjectIdHash}! Removing entry from list!");
return false;
}
}
}
else
{
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: {sourcePrefabGlobalObjectIdHash}! Removing entry from list!");
return false;
}
return true;
}
private void InitializePrefabs(int startIdx = 0)
{
// This is used to remove entries not needed or invalid
var removeEmptyPrefabs = new List<int>();
// Build the NetworkPrefabOverrideLinks dictionary
for (int i = startIdx; i < NetworkConfig.NetworkPrefabs.Count; i++)
{
var sourcePrefabGlobalObjectIdHash = (uint)0;
var targetPrefabGlobalObjectIdHash = (uint)0;
if (!ShouldAddPrefab(NetworkConfig.NetworkPrefabs[i], out sourcePrefabGlobalObjectIdHash, out targetPrefabGlobalObjectIdHash, i))
{
removeEmptyPrefabs.Add(i);
continue;
}
if (!AddPrefabRegistration(NetworkConfig.NetworkPrefabs[i], sourcePrefabGlobalObjectIdHash, targetPrefabGlobalObjectIdHash))
{
removeEmptyPrefabs.Add(i);
continue;
}
}
// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
// Iterate backwards so indices don't shift as we remove
for (int i = removeEmptyPrefabs.Count - 1; i >= 0; i--)
{
NetworkConfig.NetworkPrefabs.RemoveAt(removeEmptyPrefabs[i]);
}
removeEmptyPrefabs.Clear();
}
private void Initialize(bool server)
{ {
// Don't allow the user to start a network session if the NetworkManager is // Don't allow the user to start a network session if the NetworkManager is
// still parented under another GameObject // still parented under another GameObject
@@ -889,6 +723,7 @@ namespace Unity.Netcode
return; return;
} }
DisconnectReason = string.Empty;
IsApproved = false; IsApproved = false;
ComponentFactory.SetDefaults(); ComponentFactory.SetDefaults();
@@ -920,6 +755,8 @@ namespace Unity.Netcode
DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this); DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this);
RealTimeProvider = ComponentFactory.Create<IRealTimeProvider>(this);
CustomMessagingManager = new CustomMessagingManager(this); CustomMessagingManager = new CustomMessagingManager(this);
SceneManager = new NetworkSceneManager(this); SceneManager = new NetworkSceneManager(this);
@@ -969,11 +806,7 @@ namespace Unity.Netcode
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
// Always clear our prefab override links before building NetworkConfig.InitializePrefabs();
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
NetworkConfig.OverrideToNetworkPrefab.Clear();
InitializePrefabs();
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning. // If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
if (NetworkConfig.PlayerPrefab != null) if (NetworkConfig.PlayerPrefab != null)
@@ -981,15 +814,11 @@ namespace Unity.Netcode
if (NetworkConfig.PlayerPrefab.TryGetComponent<NetworkObject>(out var playerPrefabNetworkObject)) if (NetworkConfig.PlayerPrefab.TryGetComponent<NetworkObject>(out var playerPrefabNetworkObject))
{ {
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab) //In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject if (!NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject
.GlobalObjectIdHash)) .GlobalObjectIdHash))
{ {
//Then add a new entry for the player prefab //Then add a new entry for the player prefab
var playerNetworkPrefab = new NetworkPrefab(); AddNetworkPrefab(NetworkConfig.PlayerPrefab);
playerNetworkPrefab.Prefab = NetworkConfig.PlayerPrefab;
NetworkConfig.NetworkPrefabs.Insert(0, playerNetworkPrefab);
NetworkConfig.NetworkPrefabOverrideLinks.Add(playerPrefabNetworkObject.GlobalObjectIdHash,
playerNetworkPrefab);
} }
} }
else else
@@ -1035,6 +864,7 @@ namespace Unity.Netcode
IsServer = true; IsServer = true;
IsClient = false; IsClient = false;
IsListening = true; IsListening = true;
LocalClientId = ServerClientId;
try try
{ {
@@ -1100,6 +930,7 @@ namespace Unity.Netcode
IsClient = true; IsClient = true;
IsListening = true; IsListening = true;
OnClientStarted?.Invoke();
return true; return true;
} }
@@ -1182,6 +1013,12 @@ namespace Unity.Netcode
SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
OnServerStarted?.Invoke(); OnServerStarted?.Invoke();
OnClientStarted?.Invoke();
// This assures that any in-scene placed NetworkObject is spawned and
// any associated NetworkBehaviours' netcode related properties are
// set prior to invoking OnClientConnected.
InvokeOnClientConnectedCallback(LocalClientId);
return true; return true;
} }
@@ -1264,6 +1101,8 @@ namespace Unity.Netcode
private void Awake() private void Awake()
{ {
NetworkConfig?.InitializePrefabs();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded; UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
} }
@@ -1293,13 +1132,13 @@ namespace Unity.Netcode
return isParented; return isParented;
} }
static internal string GenerateNestedNetworkManagerMessage(Transform transform) internal static string GenerateNestedNetworkManagerMessage(Transform transform)
{ {
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n"; return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
} }
#if UNITY_EDITOR #if UNITY_EDITOR
static internal INetworkManagerHelper NetworkManagerHelper; internal static INetworkManagerHelper NetworkManagerHelper;
/// <summary> /// <summary>
/// Interface for NetworkManagerHelper /// Interface for NetworkManagerHelper
/// </summary> /// </summary>
@@ -1341,6 +1180,7 @@ namespace Unity.Netcode
private void DisconnectRemoteClient(ulong clientId) private void DisconnectRemoteClient(ulong clientId)
{ {
var transportId = ClientIdToTransportId(clientId); var transportId = ClientIdToTransportId(clientId);
MessagingSystem.ProcessSendQueues();
NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId); NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
} }
@@ -1379,13 +1219,12 @@ namespace Unity.Netcode
NetworkLog.LogInfo(nameof(ShutdownInternal)); NetworkLog.LogInfo(nameof(ShutdownInternal));
} }
if (IsServer) bool wasServer = IsServer;
bool wasClient = IsClient;
if (wasServer)
{ {
// make sure all messages are flushed before transport disconnect clients // make sure all messages are flushed before transport disconnect clients
if (MessagingSystem != null) MessagingSystem?.ProcessSendQueues();
{
MessagingSystem.ProcessSendQueues();
}
var disconnectedIds = new HashSet<ulong>(); var disconnectedIds = new HashSet<ulong>();
@@ -1421,11 +1260,23 @@ namespace Unity.Netcode
} }
} }
if (IsClient && IsConnectedClient) // Unregister network updates before trying to disconnect the client
this.UnregisterAllNetworkUpdates();
if (IsClient && IsListening)
{ {
// Client only, send disconnect to server // Client only, send disconnect to server
// If transport throws and exception, log the exception and
// continue the shutdown sequence (or forever be shutting down)
try
{
NetworkConfig.NetworkTransport.DisconnectLocalClient(); NetworkConfig.NetworkTransport.DisconnectLocalClient();
} }
catch (Exception ex)
{
Debug.LogException(ex);
}
}
IsConnectedClient = false; IsConnectedClient = false;
IsApproved = false; IsApproved = false;
@@ -1445,8 +1296,6 @@ namespace Unity.Netcode
IsServer = false; IsServer = false;
IsClient = false; IsClient = false;
this.UnregisterAllNetworkUpdates();
if (NetworkTickSystem != null) if (NetworkTickSystem != null)
{ {
NetworkTickSystem.Tick -= OnNetworkManagerTick; NetworkTickSystem.Tick -= OnNetworkManagerTick;
@@ -1464,10 +1313,7 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll; NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
} }
if (DeferredMessageManager != null) DeferredMessageManager?.CleanupAllTriggers();
{
DeferredMessageManager.CleanupAllTriggers();
}
if (SceneManager != null) if (SceneManager != null)
{ {
@@ -1502,6 +1348,22 @@ namespace Unity.Netcode
m_StopProcessingMessages = false; m_StopProcessingMessages = false;
ClearClients(); ClearClients();
if (wasClient)
{
OnClientStopped?.Invoke(wasServer);
}
if (wasServer)
{
OnServerStopped?.Invoke(wasClient);
}
// This cleans up the internal prefabs list
NetworkConfig?.Prefabs.Shutdown();
// Reset the configuration hash for next session in the event
// that the prefab list changes
NetworkConfig?.ClearConfigHash();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -1579,6 +1441,7 @@ namespace Unity.Netcode
} while (IsListening && networkEvent != NetworkEvent.Nothing); } while (IsListening && networkEvent != NetworkEvent.Nothing);
MessagingSystem.ProcessIncomingMessageQueue(); MessagingSystem.ProcessIncomingMessageQueue();
MessagingSystem.CleanupDisconnectedClients();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportPoll.End(); s_TransportPoll.End();
@@ -1600,7 +1463,7 @@ namespace Unity.Netcode
} }
// Only update RTT here, server time is updated by time sync messages // Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.unscaledDeltaTime); var reset = NetworkTimeSystem.Advance(RealTimeProvider.UnscaledDeltaTime);
if (reset) if (reset)
{ {
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime); NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
@@ -1609,7 +1472,7 @@ namespace Unity.Netcode
if (IsServer == false) if (IsServer == false)
{ {
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.unscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d); NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + RealTimeProvider.UnscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
} }
} }
@@ -1618,6 +1481,10 @@ namespace Unity.Netcode
if (!m_ShuttingDown || !m_StopProcessingMessages) if (!m_ShuttingDown || !m_StopProcessingMessages)
{ {
// This should be invoked just prior to the MessagingSystem
// processes its outbound queue.
SceneManager.CheckForAndSendNetworkObjectSceneChanged();
MessagingSystem.ProcessSendQueues(); MessagingSystem.ProcessSendQueues();
NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count); NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1); NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
@@ -1643,6 +1510,17 @@ namespace Unity.Netcode
// Do NetworkVariable updates // Do NetworkVariable updates
BehaviourUpdater.NetworkBehaviourUpdate(this); BehaviourUpdater.NetworkBehaviourUpdate(this);
// Handle NetworkObjects to show
foreach (var client in ObjectsToShowToClient)
{
ulong clientId = client.Key;
foreach (var networkObject in client.Value)
{
SpawnManager.SendSpawnCallForObject(clientId, networkObject);
}
}
ObjectsToShowToClient.Clear();
int timeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * NetworkConfig.TickRate); int timeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * NetworkConfig.TickRate);
if (IsServer && NetworkTickSystem.ServerTime.Tick % timeSyncFrequencyTicks == 0) if (IsServer && NetworkTickSystem.ServerTime.Tick % timeSyncFrequencyTicks == 0)
{ {
@@ -1658,14 +1536,29 @@ namespace Unity.Netcode
// we should always force the rebuilding of the NetworkConfig hash value // we should always force the rebuilding of the NetworkConfig hash value
ConfigHash = NetworkConfig.GetConfig(false), ConfigHash = NetworkConfig.GetConfig(false),
ShouldSendConnectionData = NetworkConfig.ConnectionApproval, ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData ConnectionData = NetworkConfig.ConnectionData,
MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp)
}; };
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{
if (MessagingSystem.MessageTypes[index] != null)
{
var type = MessagingSystem.MessageTypes[index];
message.MessageVersions[index] = new MessageVersionData
{
Hash = XXHash.Hash32(type.FullName),
Version = MessagingSystem.GetLocalVersion(type)
};
}
}
SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId); SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId);
message.MessageVersions.Dispose();
} }
private IEnumerator ApprovalTimeout(ulong clientId) private IEnumerator ApprovalTimeout(ulong clientId)
{ {
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup; var timeStarted = IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup;
var timedOut = false; var timedOut = false;
var connectionApproved = false; var connectionApproved = false;
var connectionNotApproved = false; var connectionNotApproved = false;
@@ -1675,7 +1568,7 @@ namespace Unity.Netcode
{ {
yield return null; yield return null;
// Check if we timed out // Check if we timed out
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup); timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup);
if (IsServer) if (IsServer)
{ {
@@ -1821,7 +1714,18 @@ namespace Unity.Netcode
NetworkLog.LogInfo($"Disconnect Event From {clientId}"); NetworkLog.LogInfo($"Disconnect Event From {clientId}");
} }
// Process the incoming message queue so that we get everything from the server disconnecting us
// or, if we are the server, so we got everything from that client.
MessagingSystem.ProcessIncomingMessageQueue();
try
{
OnClientDisconnectCallback?.Invoke(clientId); OnClientDisconnectCallback?.Invoke(clientId);
}
catch (Exception exception)
{
Debug.LogException(exception);
}
if (IsServer) if (IsServer)
{ {
@@ -1987,12 +1891,33 @@ namespace Unity.Netcode
/// </summary> /// </summary>
/// <param name="clientId">The ClientId to disconnect</param> /// <param name="clientId">The ClientId to disconnect</param>
public void DisconnectClient(ulong clientId) public void DisconnectClient(ulong clientId)
{
DisconnectClient(clientId, null);
}
/// <summary>
/// Disconnects the remote client.
/// </summary>
/// <param name="clientId">The ClientId to disconnect</param>
/// <param name="reason">Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the
/// reason available in the NetworkManager.DisconnectReason property</param>
public void DisconnectClient(ulong clientId, string reason)
{ {
if (!IsServer) if (!IsServer)
{ {
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
} }
if (!string.IsNullOrEmpty(reason))
{
var disconnectReason = new DisconnectReasonMessage
{
Reason = reason
};
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
}
MessagingSystem.ProcessSendQueues();
OnClientDisconnectFromServer(clientId); OnClientDisconnectFromServer(clientId);
DisconnectRemoteClient(clientId); DisconnectRemoteClient(clientId);
} }
@@ -2016,7 +1941,12 @@ namespace Unity.Netcode
} }
else else
{ {
Destroy(playerObject.gameObject); // Call despawn to assure NetworkBehaviour.OnNetworkDespawn is invoked
// on the server-side (when the client side disconnected).
// This prevents the issue (when just destroying the GameObject) where
// any NetworkBehaviour component(s) destroyed before the NetworkObject
// would not have OnNetworkDespawn invoked.
SpawnManager.DespawnObject(playerObject, true);
} }
} }
else else
@@ -2132,19 +2062,20 @@ namespace Unity.Netcode
if (response.CreatePlayerObject) if (response.CreatePlayerObject)
{ {
var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash; var prefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash;
// Generate a SceneObject for the player object to spawn // Generate a SceneObject for the player object to spawn
// Note: This is only to create the local NetworkObject,
// many of the serialized properties of the player prefab
// will be set when instantiated.
var sceneObject = new NetworkObject.SceneObject var sceneObject = new NetworkObject.SceneObject
{ {
Header = new NetworkObject.SceneObject.HeaderData
{
IsPlayerObject = true,
OwnerClientId = ownerClientId, OwnerClientId = ownerClientId,
IsPlayerObject = true,
IsSceneObject = false, IsSceneObject = false,
HasTransform = true, HasTransform = prefabNetworkObject.SynchronizeTransform,
Hash = playerPrefabHash, Hash = playerPrefabHash,
},
TargetClientId = ownerClientId, TargetClientId = ownerClientId,
Transform = new NetworkObject.SceneObject.TransformData Transform = new NetworkObject.SceneObject.TransformData
{ {
@@ -2184,21 +2115,22 @@ namespace Unity.Netcode
} }
} }
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); message.MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++) for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{ {
if (MessagingSystem.MessageTypes[index] != null) if (MessagingSystem.MessageTypes[index] != null)
{ {
var orderingMessage = new OrderingMessage var type = MessagingSystem.MessageTypes[index];
message.MessageVersions[index] = new MessageVersionData
{ {
Order = index, Hash = XXHash.Hash32(type.FullName),
Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName) Version = MessagingSystem.GetLocalVersion(type)
}; };
}
}
SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
} message.MessageVersions.Dispose();
}
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement) if (!NetworkConfig.EnableSceneManagement)
@@ -2214,7 +2146,6 @@ namespace Unity.Netcode
{ {
LocalClient = client; LocalClient = client;
SpawnManager.UpdateObservedNetworkObjects(ownerClientId); SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId);
} }
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null)) if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
@@ -2227,6 +2158,17 @@ namespace Unity.Netcode
} }
else else
{ {
if (!string.IsNullOrEmpty(response.Reason))
{
var disconnectReason = new DisconnectReasonMessage
{
Reason = response.Reason
};
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
MessagingSystem.ProcessSendQueues();
}
PendingClients.Remove(ownerClientId); PendingClients.Remove(ownerClientId);
DisconnectRemoteClient(ownerClientId); DisconnectRemoteClient(ownerClientId);
} }
@@ -2253,14 +2195,46 @@ namespace Unity.Netcode
{ {
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key) ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
}; };
message.ObjectInfo.Header.Hash = playerPrefabHash; message.ObjectInfo.Hash = playerPrefabHash;
message.ObjectInfo.Header.IsSceneObject = false; message.ObjectInfo.IsSceneObject = false;
message.ObjectInfo.Header.HasParent = false; message.ObjectInfo.HasParent = false;
message.ObjectInfo.Header.IsPlayerObject = true; message.ObjectInfo.IsPlayerObject = true;
message.ObjectInfo.Header.OwnerClientId = clientId; message.ObjectInfo.OwnerClientId = clientId;
var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key); var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size); NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
} }
} }
internal void MarkObjectForShowingTo(NetworkObject networkObject, ulong clientId)
{
if (!ObjectsToShowToClient.ContainsKey(clientId))
{
ObjectsToShowToClient.Add(clientId, new List<NetworkObject>());
}
ObjectsToShowToClient[clientId].Add(networkObject);
}
// returns whether any matching objects would have become visible and were returned to hidden state
internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clientId)
{
var ret = false;
if (!ObjectsToShowToClient.ContainsKey(clientId))
{
return false;
}
// probably overkill, but deals with multiple entries
while (ObjectsToShowToClient[clientId].Contains(networkObject))
{
Debug.LogWarning(
"Object was shown and hidden from the same client in the same Network frame. As a result, the client will _not_ receive a NetworkSpawn");
ObjectsToShowToClient[clientId].Remove(networkObject);
ret = true;
}
networkObject.Observers.Remove(clientId);
return ret;
}
} }
} }

View File

@@ -17,6 +17,28 @@ namespace Unity.Netcode
[SerializeField] [SerializeField]
internal uint GlobalObjectIdHash; internal uint GlobalObjectIdHash;
/// <summary>
/// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0
/// </summary>
[HideInInspector]
public uint PrefabIdHash
{
get
{
foreach (var prefab in NetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (prefab.Prefab == gameObject)
{
return GlobalObjectIdHash;
}
}
return 0;
}
}
private bool m_IsPrefab;
#if UNITY_EDITOR #if UNITY_EDITOR
private void OnValidate() private void OnValidate()
{ {
@@ -75,6 +97,18 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public bool IsPlayerObject { get; internal set; } public bool IsPlayerObject { get; internal set; }
/// <summary>
/// Determines if the associated NetworkObject's transform will get
/// synchronized when spawned.
/// </summary>
/// <remarks>
/// For things like in-scene placed NetworkObjects that have no visual
/// components can help reduce the instance's initial synchronization
/// bandwidth cost. This can also be useful for UI elements that have
/// a predetermined fixed position.
/// </remarks>
public bool SynchronizeTransform = true;
/// <summary> /// <summary>
/// Gets if the object is the personal clients player object /// Gets if the object is the personal clients player object
/// </summary> /// </summary>
@@ -105,6 +139,55 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public bool DestroyWithScene { get; set; } public bool DestroyWithScene { get; set; }
/// <summary>
/// When set to true and the active scene is changed, this will automatically migrate the <see cref="NetworkObject"/>
/// into the new active scene on both the server and client instances.
/// </summary>
/// <remarks>
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
///
/// If there are more than one scenes loaded and the currently active scene is unloaded, then typically
/// the <see cref="SceneManager"/> will automatically assign a new active scene. Similar to <see cref="DestroyWithScene"/>
/// being set to <see cref="false"/>, this prevents any <see cref="NetworkObject"/> from being destroyed
/// with the unloaded active scene by migrating it into the automatically assigned active scene.
/// Additionally, this is can be useful in some seamless scene streaming implementations.
/// Note:
/// Only having <see cref="ActiveSceneSynchronization"/> set to true will *not* synchronize clients when
/// changing a <see cref="NetworkObject"/>'s scene via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>.
/// To synchronize clients of a <see cref="NetworkObject"/>'s scene being changed via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>,
/// make sure <see cref="SceneMigrationSynchronization"/> is enabled (it is by default).
/// </remarks>
public bool ActiveSceneSynchronization;
/// <summary>
/// When enabled (the default), if a <see cref="NetworkObject"/> is migrated to a different scene (active or not)
/// via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/> on the server side all client
/// instances will be synchronized and the <see cref="NetworkObject"/> migrated into the newly assigned scene.
/// The updated scene migration will get synchronized with late joining clients as well.
/// </summary>
/// <remarks>
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
/// Note:
/// You can have both <see cref="ActiveSceneSynchronization"/> and <see cref="SceneMigrationSynchronization"/> enabled.
/// The primary difference between the two is that <see cref="SceneMigrationSynchronization"/> only synchronizes clients
/// when the server migrates a <see cref="NetworkObject"/> to a new scene. If the scene is unloaded and <see cref="DestroyWithScene"/>
/// is <see cref="true"/> and <see cref="ActiveSceneSynchronization"/> is <see cref="false"/> and the scene is not the currently
/// active scene, then the <see cref="NetworkObject"/> will be destroyed.
/// </remarks>
public bool SceneMigrationSynchronization = true;
/// <summary>
/// Notifies when the NetworkObject is migrated into a new scene
/// </summary>
/// <remarks>
/// - <see cref="ActiveSceneSynchronization"/> or <see cref="SceneMigrationSynchronization"/> (or both) need to be enabled
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
/// </remarks>
public Action OnMigratedToNewScene;
/// <summary> /// <summary>
/// Delegate type for checking visibility /// Delegate type for checking visibility
/// </summary> /// </summary>
@@ -188,6 +271,11 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal int SceneOriginHandle = 0; internal int SceneOriginHandle = 0;
/// <summary>
/// The server-side scene origin handle
/// </summary>
internal int NetworkSceneHandle = 0;
private Scene m_SceneOrigin; private Scene m_SceneOrigin;
/// <summary> /// <summary>
/// The scene where the NetworkObject was first instantiated /// The scene where the NetworkObject was first instantiated
@@ -265,9 +353,17 @@ namespace Unity.Netcode
throw new VisibilityChangeException("The object is already visible"); throw new VisibilityChangeException("The object is already visible");
} }
Observers.Add(clientId); if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId))
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"[NetworkShow] Trying to make {nameof(NetworkObject)} {gameObject.name} visible to client ({clientId}) but {nameof(CheckObjectVisibility)} returned false!");
}
return;
}
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this); NetworkManager.MarkObjectForShowingTo(this, clientId);
Observers.Add(clientId);
} }
@@ -351,16 +447,17 @@ namespace Unity.Netcode
throw new NotServerException("Only server can change visibility"); throw new NotServerException("Only server can change visibility");
} }
if (!Observers.Contains(clientId))
{
throw new VisibilityChangeException("The object is already hidden");
}
if (clientId == NetworkManager.ServerClientId) if (clientId == NetworkManager.ServerClientId)
{ {
throw new VisibilityChangeException("Cannot hide an object from the server"); throw new VisibilityChangeException("Cannot hide an object from the server");
} }
if (!NetworkManager.RemoveObjectFromShowingTo(this, clientId))
{
if (!Observers.Contains(clientId))
{
throw new VisibilityChangeException("The object is already hidden");
}
Observers.Remove(clientId); Observers.Remove(clientId);
var message = new DestroyObjectMessage var message = new DestroyObjectMessage
@@ -372,6 +469,7 @@ namespace Unity.Netcode
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
} }
}
/// <summary> /// <summary>
/// Hides a list of <see cref="NetworkObject"/>s from the targeted client. /// Hides a list of <see cref="NetworkObject"/>s from the targeted client.
@@ -577,6 +675,22 @@ namespace Unity.Netcode
private Transform m_CachedParent; // What is our last set parent Transform reference? 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 private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent
/// <summary>
/// Returns the last known cached WorldPositionStays value for this NetworkObject
/// </summary>
/// <remarks>
/// When parenting NetworkObjects, the optional WorldPositionStays value is cached and synchronized with clients.
/// This method provides access to the instance relative cached value.
/// <see cref="TrySetParent(GameObject, bool)"/>
/// <see cref="TrySetParent(NetworkObject, bool)"/>
/// <see cref="TrySetParent(Transform, bool)"/>
/// </remarks>
/// <returns><see cref="true"/> or <see cref="false"/></returns>
public bool WorldPositionStays()
{
return m_CachedWorldPositionStays;
}
internal void SetCachedParent(Transform parentTransform) internal void SetCachedParent(Transform parentTransform)
{ {
m_CachedParent = parentTransform; m_CachedParent = parentTransform;
@@ -830,6 +944,9 @@ namespace Unity.Netcode
// parent and then re-parents the child under a GameObject with a NetworkObject component attached. // parent and then re-parents the child under a GameObject with a NetworkObject component attached.
if (parentNetworkObject == null) 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; return true;
} }
else // If the parent still isn't spawned add this to the orphaned children and return false else // If the parent still isn't spawned add this to the orphaned children and return false
@@ -841,8 +958,11 @@ namespace Unity.Netcode
else else
{ {
// If we made it this far, go ahead and set the network parenting values // If we made it this far, go ahead and set the network parenting values
// with the default WorldPoisitonSays value // with the WorldPoisitonSays value set to false
SetNetworkParenting(parentNetworkObject.NetworkObjectId, true); // 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 // Set the cached parent
m_CachedParent = parentNetworkObject.transform; m_CachedParent = parentNetworkObject.transform;
@@ -1002,13 +1122,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++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
var behaviour = ChildNetworkBehaviours[i]; var behaviour = ChildNetworkBehaviours[i];
behaviour.InitializeVariables(); behaviour.InitializeVariables();
behaviour.SetNetworkVariableData(reader); behaviour.SetNetworkVariableData(reader, clientId);
} }
} }
@@ -1045,7 +1169,20 @@ namespace Unity.Netcode
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) 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; return null;
@@ -1056,19 +1193,55 @@ namespace Unity.Netcode
internal struct SceneObject internal struct SceneObject
{ {
public struct HeaderData : INetworkSerializeByMemcpy private byte m_BitField;
{ public uint Hash;
public ulong NetworkObjectId; public ulong NetworkObjectId;
public ulong OwnerClientId; public ulong OwnerClientId;
public uint Hash;
public bool IsPlayerObject; public bool IsPlayerObject
public bool HasParent; {
public bool IsSceneObject; get => ByteUtility.GetBit(m_BitField, 0);
public bool HasTransform; 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);
}
/// <summary>
/// Even though the server sends notifications for NetworkObjects that get
/// destroyed when a scene is unloaded, we want to synchronize this so
/// the client side can use it as part of a filter for automatically migrating
/// to the current active scene when its scene is unloaded. (only for dynamically spawned)
/// </summary>
public bool DestroyWithScene
{
get => ByteUtility.GetBit(m_BitField, 6);
set => ByteUtility.SetBit(ref m_BitField, 6, value);
}
//If(Metadata.HasParent) //If(Metadata.HasParent)
public ulong ParentObjectId; public ulong ParentObjectId;
@@ -1084,7 +1257,6 @@ namespace Unity.Netcode
public TransformData Transform; public TransformData Transform;
//If(Metadata.IsReparented) //If(Metadata.IsReparented)
public bool IsLatestParentSet;
//If(IsLatestParentSet) //If(IsLatestParentSet)
public ulong? LatestParent; public ulong? LatestParent;
@@ -1094,115 +1266,81 @@ namespace Unity.Netcode
public int NetworkSceneHandle; public int NetworkSceneHandle;
public bool WorldPositionStays;
public unsafe void Serialize(FastBufferWriter writer) public void Serialize(FastBufferWriter writer)
{ {
var writeSize = sizeof(HeaderData); writer.WriteValueSafe(m_BitField);
if (Header.HasParent) writer.WriteValueSafe(Hash);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
if (HasParent)
{ {
writeSize += FastBufferWriter.GetWriteSize(ParentObjectId); BytePacker.WriteValueBitPacked(writer, ParentObjectId);
writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays); if (IsLatestParentSet)
writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet); {
writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0; BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
} }
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0; }
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
var writeSize = 0;
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
writeSize += FastBufferWriter.GetWriteSize<int>();
if (!writer.TryBeginWrite(writeSize)) if (!writer.TryBeginWrite(writeSize))
{ {
throw new OverflowException("Could not serialize SceneObject: Out of buffer space."); throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
} }
writer.WriteValue(Header); if (HasTransform)
if (Header.HasParent)
{
writer.WriteValue(ParentObjectId);
writer.WriteValue(WorldPositionStays);
writer.WriteValue(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValue(LatestParent.Value);
}
}
if (Header.HasTransform)
{ {
writer.WriteValue(Transform); writer.WriteValue(Transform);
} }
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their // The NetworkSceneHandle is the server-side relative
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique // scene handle that the NetworkObject resides in.
// 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)
{
writer.WriteValue(OwnerObject.GetSceneOriginHandle()); writer.WriteValue(OwnerObject.GetSceneOriginHandle());
// Synchronize NetworkVariables and NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);
} }
OwnerObject.WriteNetworkVariableData(writer, TargetClientId); public void Deserialize(FastBufferReader reader)
{
reader.ReadValueSafe(out m_BitField);
reader.ReadValueSafe(out Hash);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
if (HasParent)
{
ByteUnpacker.ReadValueBitPacked(reader, out ParentObjectId);
if (IsLatestParentSet)
{
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
LatestParent = latestParent;
}
} }
public unsafe 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 = 0; var readSize = 0;
if (Header.HasParent) readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
{ readSize += FastBufferWriter.GetWriteSize<int>();
readSize += FastBufferWriter.GetWriteSize(ParentObjectId);
readSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
// We need to read at this point in order to get the IsLatestParentSet value
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
}
// Read the initial parenting related properties
reader.ReadValue(out ParentObjectId);
reader.ReadValue(out WorldPositionStays);
reader.ReadValue(out IsLatestParentSet);
// Now calculate the remaining bytes to read
readSize = 0;
readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
}
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
// Try to begin reading the remaining bytes // Try to begin reading the remaining bytes
if (!reader.TryBeginRead(readSize)) if (!reader.TryBeginRead(readSize))
{ {
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space."); throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer");
} }
if (IsLatestParentSet) if (HasTransform)
{
reader.ReadValueSafe(out ulong latestParent);
LatestParent = latestParent;
}
if (Header.HasTransform)
{ {
reader.ReadValue(out Transform); reader.ReadValue(out Transform);
} }
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their // The NetworkSceneHandle is the server-side relative
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique // scene handle that the NetworkObject resides in.
// handle, it provides us with a unique and persistent "scene prefab asset" instance. reader.ReadValue(out NetworkSceneHandle);
// Client-side NetworkSceneManagers use this to locate their local instance of the
// NetworkObject instance.
if (Header.IsSceneObject)
{
reader.ReadValueSafe(out NetworkSceneHandle);
}
} }
} }
@@ -1214,18 +1352,88 @@ namespace Unity.Netcode
} }
} }
/// <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, targetClientId))
{
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, targetClientId);
}
}
}
internal SceneObject GetMessageSceneObject(ulong targetClientId) internal SceneObject GetMessageSceneObject(ulong targetClientId)
{ {
var obj = new SceneObject var obj = new SceneObject
{ {
Header = new SceneObject.HeaderData
{
IsPlayerObject = IsPlayerObject,
NetworkObjectId = NetworkObjectId, NetworkObjectId = NetworkObjectId,
OwnerClientId = OwnerClientId, OwnerClientId = OwnerClientId,
IsPlayerObject = IsPlayerObject,
IsSceneObject = IsSceneObject ?? true, IsSceneObject = IsSceneObject ?? true,
DestroyWithScene = DestroyWithScene,
Hash = HostCheckForGlobalObjectIdHashOverride(), Hash = HostCheckForGlobalObjectIdHashOverride(),
},
OwnerObject = this, OwnerObject = this,
TargetClientId = targetClientId TargetClientId = targetClientId
}; };
@@ -1235,11 +1443,18 @@ namespace Unity.Netcode
if (!AlwaysReplicateAsRoot && transform.parent != null) if (!AlwaysReplicateAsRoot && transform.parent != null)
{ {
parentNetworkObject = transform.parent.GetComponent<NetworkObject>(); parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
// 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)
{
obj.HasParent = true;
obj.WorldPositionStays = m_CachedWorldPositionStays;
}
} }
if (parentNetworkObject != null) if (parentNetworkObject != null)
{ {
obj.Header.HasParent = true; obj.HasParent = true;
obj.ParentObjectId = parentNetworkObject.NetworkObjectId; obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
obj.WorldPositionStays = m_CachedWorldPositionStays; obj.WorldPositionStays = m_CachedWorldPositionStays;
var latestParent = GetNetworkParenting(); var latestParent = GetNetworkParenting();
@@ -1253,20 +1468,38 @@ namespace Unity.Netcode
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId)) if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
{ {
obj.Header.HasTransform = true; obj.HasTransform = SynchronizeTransform;
// 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 obj.Transform = new SceneObject.TransformData
{ {
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space // If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
// values as opposed world space values. // values as opposed world space values.
Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position, Position = syncRotationPositionLocalSpaceRelative ? transform.localPosition : transform.position,
Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation, Rotation = syncRotationPositionLocalSpaceRelative ? transform.localRotation : transform.rotation,
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can // 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 // 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". // which can be thought of as "world space scale".
// More information: // More information:
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html // https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale, Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale,
}; };
} }
@@ -1278,10 +1511,10 @@ namespace Unity.Netcode
/// when the client is approved or during a scene transition /// when the client is approved or during a scene transition
/// </summary> /// </summary>
/// <param name="sceneObject">Deserialized scene object data</param> /// <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> /// <param name="networkManager">NetworkManager instance</param>
/// <returns>optional to use NetworkObject deserialized</returns> /// <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)
{ {
//Attempt to create a local NetworkObject //Attempt to create a local NetworkObject
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject); var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
@@ -1289,22 +1522,155 @@ namespace Unity.Netcode
if (networkObject == null) if (networkObject == null)
{ {
// Log the error that the NetworkObject failed to construct // 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 try
variableData.ReadValueSafe(out ushort varSize); {
variableData.Seek(variableData.Position + varSize); // 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. // We have nothing left to do here.
return null; return null;
} }
// 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 // Spawn the NetworkObject
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false); networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene);
return networkObject; return networkObject;
} }
/// <summary>
/// Subscribes to changes in the currently active scene
/// </summary>
/// <remarks>
/// Only for dynamically spawned NetworkObjects
/// </remarks>
internal void SubscribeToActiveSceneForSynch()
{
if (ActiveSceneSynchronization)
{
if (IsSceneObject.HasValue && !IsSceneObject.Value)
{
// Just in case it is a recycled NetworkObject, unsubscribe first
SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged;
SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged;
}
}
}
/// <summary>
/// If AutoSynchActiveScene is enabled, then this is the callback that handles updating
/// a NetworkObject's scene information.
/// </summary>
private void CurrentlyActiveSceneChanged(Scene current, Scene next)
{
// Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject
// is not spawned, or an in-scene placed NetworkObject
if (NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false)
{
return;
}
// This check is here in the event a user wants to disable this for some reason but also wants
// the NetworkObject to synchronize to changes in the currently active scene at some later time.
if (ActiveSceneSynchronization)
{
// Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate
// and update their scene handles
if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null)
{
SceneManager.MoveGameObjectToScene(gameObject, next);
SceneChangedUpdate(next);
}
}
}
/// <summary>
/// Handles updating the NetworkObject's tracked scene handles
/// </summary>
internal void SceneChangedUpdate(Scene scene, bool notify = false)
{
// Avoiding edge case scenarios, if no NetworkSceneManager exit early
if (NetworkManager.SceneManager == null)
{
return;
}
SceneOriginHandle = scene.handle;
// Clients need to update the NetworkSceneHandle
if (!NetworkManager.IsServer && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle))
{
NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle];
}
else if (NetworkManager.IsServer)
{
// Since the server is the source of truth for the NetworkSceneHandle,
// the NetworkSceneHandle is the same as the SceneOriginHandle.
NetworkSceneHandle = SceneOriginHandle;
}
else // Otherwise, the client did not find the client to server scene handle
if (NetworkManager.LogLevel == LogLevel.Developer)
{
// There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject
// into, but that scenario seemed very edge case and under most instances a user should be notified that this
// server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
// the server-side too.
NetworkLog.LogWarningServer($"[Client-{NetworkManager.LocalClientId}][{gameObject.name}] Server - " +
$"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
$"has no associated server side (network) scene handle!");
}
OnMigratedToNewScene?.Invoke();
// Only the server side will notify clients of non-parented NetworkObject scene changes
if (NetworkManager.IsServer && notify && transform.parent == null)
{
NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this);
}
}
/// <summary>
/// Update
/// Detects if a NetworkObject's scene has changed for both server and client instances
/// </summary>
/// <remarks>
/// About In-Scene Placed NetworkObjects:
/// Since the same scene can be loaded more than once and in-scene placed NetworkObjects GlobalObjectIdHash
/// values are only unique to the scene asset itself (and not per scene instance loaded), we will not be able
/// to add this same functionality to in-scene placed NetworkObjects until we have a way to generate
/// per-NetworkObject-instance unique GlobalObjectIdHash values for in-scene placed NetworkObjects.
/// </remarks>
private void Update()
{
// Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned,
// the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed
// NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle
if (!SceneMigrationSynchronization || NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned
|| IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle)
{
return;
}
// Otherwise, this has to be a dynamically spawned NetworkObject that has been
// migrated to a new scene.
SceneChangedUpdate(gameObject.scene, true);
}
/// <summary> /// <summary>
/// Only applies to Host mode. /// Only applies to Host mode.
/// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists. /// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists.
@@ -1320,9 +1686,9 @@ namespace Unity.Netcode
var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash); var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash);
return globalObjectIdHash == 0 ? GlobalObjectIdHash : 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;
} }
} }

View File

@@ -167,7 +167,7 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public static NetworkUpdateStage UpdateStage; public static NetworkUpdateStage UpdateStage;
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage) internal static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
{ {
UpdateStage = updateStage; UpdateStage = updateStage;

248
Runtime/Hashing/XXHash.cs Normal file
View File

@@ -0,0 +1,248 @@
using System;
using System.Runtime.CompilerServices;
using System.Text;
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));
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b5aa7a49e9e694f148d810d34577546b guid: c3077af091aa443acbdea9d3e97727b0
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

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

View File

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

View File

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

View File

@@ -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));
}
}
}
}

View File

@@ -5,9 +5,26 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal struct BatchHeader : INetworkSerializeByMemcpy 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> /// <summary>
/// Total number of messages in the batch. /// Total number of messages in the batch.
/// </summary> /// </summary>
public ushort BatchSize; public ushort BatchCount;
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -68,9 +69,23 @@ namespace Unity.Netcode
if (clientIds == null) 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 var message = new UnnamedMessage
{ {
SendData = messageBuffer SendData = messageBuffer
@@ -92,6 +107,18 @@ namespace Unity.Netcode
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param> /// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) 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 var message = new UnnamedMessage
{ {
SendData = messageBuffer SendData = messageBuffer
@@ -124,14 +151,18 @@ namespace Unity.Netcode
// We dont know what size to use. Try every (more collision prone) // We dont know what size to use. Try every (more collision prone)
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{ {
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup32[hash];
messageHandler32(sender, reader); messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
} }
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{ {
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup64[hash];
messageHandler64(sender, reader); messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
} }
} }
else else
@@ -142,15 +173,19 @@ namespace Unity.Netcode
case HashSize.VarIntFourBytes: case HashSize.VarIntFourBytes:
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32)) if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{ {
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup32[hash];
messageHandler32(sender, reader); messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
} }
break; break;
case HashSize.VarIntEightBytes: case HashSize.VarIntEightBytes:
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64)) if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{ {
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup64[hash];
messageHandler64(sender, reader); messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount); m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
} }
break; break;
} }
@@ -220,6 +255,20 @@ namespace Unity.Netcode
hash = XXHash.Hash64(messageName); hash = XXHash.Hash64(messageName);
break; 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 var message = new NamedMessage
{ {
@@ -251,7 +300,7 @@ namespace Unity.Netcode
if (clientIds == null) 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; ulong hash = 0;
@@ -264,6 +313,21 @@ namespace Unity.Netcode
hash = XXHash.Hash64(messageName); hash = XXHash.Hash64(messageName);
break; 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 var message = new NamedMessage
{ {
Hash = hash, Hash = hash,

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Unity.Collections; using Unity.Collections;
using Time = UnityEngine.Time;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -49,7 +48,7 @@ namespace Unity.Netcode
{ {
triggerInfo = new TriggerInfo triggerInfo = new TriggerInfo
{ {
Expiry = Time.realtimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout, Expiry = m_NetworkManager.RealTimeProvider.RealTimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
TriggerData = new NativeList<TriggerData>(Allocator.Persistent) TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
}; };
triggers[key] = triggerInfo; triggers[key] = triggerInfo;
@@ -77,7 +76,7 @@ namespace Unity.Netcode
int index = 0; int index = 0;
foreach (var kvp2 in kvp.Value) foreach (var kvp2 in kvp.Value)
{ {
if (kvp2.Value.Expiry < Time.realtimeSinceStartup) if (kvp2.Value.Expiry < m_NetworkManager.RealTimeProvider.RealTimeSinceStartup)
{ {
staleKeys[index++] = kvp2.Key; staleKeys[index++] = kvp2.Key;
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value); PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);

View File

@@ -0,0 +1,46 @@
namespace Unity.Netcode
{
internal struct DisconnectReasonMessage : INetworkMessage
{
public string Reason;
public int Version => 0;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
string reasonSent = Reason ?? 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;
}
};
}

View File

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

View File

@@ -40,8 +40,9 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal interface INetworkMessage internal interface INetworkMessage
{ {
void Serialize(FastBufferWriter writer); void Serialize(FastBufferWriter writer, int targetVersion);
bool Deserialize(FastBufferReader reader, ref NetworkContext context); bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion);
void Handle(ref NetworkContext context); void Handle(ref NetworkContext context);
int Version { get; }
} }
} }

View File

@@ -2,22 +2,26 @@ namespace Unity.Netcode
{ {
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
{ {
public int Version => 0;
public ulong NetworkObjectId; public ulong NetworkObjectId;
public ulong OwnerClientId; 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; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
{ {
return false; return false;
} }
reader.ReadValueSafe(out this); ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{ {
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context); networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal struct ConnectionApprovedMessage : INetworkMessage internal struct ConnectionApprovedMessage : INetworkMessage
{ {
public int Version => 0;
public ulong OwnerClientId; public ulong OwnerClientId;
public int NetworkTick; public int NetworkTick;
@@ -13,14 +15,26 @@ namespace Unity.Netcode
private FastBufferReader m_ReceivedSceneObjectData; 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; uint sceneObjectCount = 0;
if (SpawnedObjectsList != null) if (SpawnedObjectsList != null)
@@ -39,17 +53,19 @@ namespace Unity.Netcode
++sceneObjectCount; ++sceneObjectCount;
} }
} }
writer.Seek(pos); 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); writer.Seek(writer.Length);
} }
else 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; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
@@ -57,13 +73,36 @@ namespace Unity.Netcode
return false; 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); // Update the received version since this message will always be passed version 0, due to the map not
reader.ReadValue(out NetworkTick); // 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; m_ReceivedSceneObjectData = reader;
return true; return true;
} }
@@ -85,7 +124,7 @@ namespace Unity.Netcode
if (!networkManager.NetworkConfig.EnableSceneManagement) if (!networkManager.NetworkConfig.EnableSceneManagement)
{ {
networkManager.SpawnManager.DestroySceneObjects(); 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 // 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. // to create a list to hold the data. This is a breach of convention for performance reasons.

View File

@@ -1,15 +1,35 @@
using Unity.Collections;
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal struct ConnectionRequestMessage : INetworkMessage internal struct ConnectionRequestMessage : INetworkMessage
{ {
public int Version => 0;
public ulong ConfigHash; public ulong ConfigHash;
public byte[] ConnectionData; public byte[] ConnectionData;
public bool ShouldSendConnectionData; 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) if (ShouldSendConnectionData)
{ {
writer.WriteValueSafe(ConfigHash); 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; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsServer) if (!networkManager.IsServer)
@@ -29,6 +49,30 @@ namespace Unity.Netcode
return false; 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 (networkManager.NetworkConfig.ConnectionApproval)
{ {
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>())) if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>()))

View File

@@ -2,15 +2,17 @@ namespace Unity.Netcode
{ {
internal struct CreateObjectMessage : INetworkMessage internal struct CreateObjectMessage : INetworkMessage
{ {
public int Version => 0;
public NetworkObject.SceneObject ObjectInfo; public NetworkObject.SceneObject ObjectInfo;
private FastBufferReader m_ReceivedNetworkVariableData; private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer) public void Serialize(FastBufferWriter writer, int targetVersion)
{ {
ObjectInfo.Serialize(writer); 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; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
@@ -21,7 +23,7 @@ namespace Unity.Netcode
ObjectInfo.Deserialize(reader); ObjectInfo.Deserialize(reader);
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo)) 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; return false;
} }
m_ReceivedNetworkVariableData = reader; m_ReceivedNetworkVariableData = reader;

View File

@@ -2,15 +2,18 @@ namespace Unity.Netcode
{ {
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
{ {
public int Version => 0;
public ulong NetworkObjectId; public ulong NetworkObjectId;
public bool DestroyGameObject; 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; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
@@ -18,7 +21,8 @@ namespace Unity.Netcode
return false; return false;
} }
reader.ReadValueSafe(out this); ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out DestroyGameObject);
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{ {

View 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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 754d727b316b4263a2fa0d4c54fdad52
timeCreated: 1666895514

View File

@@ -2,18 +2,20 @@ namespace Unity.Netcode
{ {
internal struct NamedMessage : INetworkMessage internal struct NamedMessage : INetworkMessage
{ {
public int Version => 0;
public ulong Hash; public ulong Hash;
public FastBufferWriter SendData; public FastBufferWriter SendData;
private FastBufferReader m_ReceiveData; private FastBufferReader m_ReceiveData;
public unsafe void Serialize(FastBufferWriter writer) public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{ {
writer.WriteValueSafe(Hash); writer.WriteValueSafe(Hash);
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length); 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); reader.ReadValueSafe(out Hash);
m_ReceiveData = reader; m_ReceiveData = reader;

View File

@@ -12,6 +12,8 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal struct NetworkVariableDeltaMessage : INetworkMessage internal struct NetworkVariableDeltaMessage : INetworkMessage
{ {
public int Version => 0;
public ulong NetworkObjectId; public ulong NetworkObjectId;
public ushort NetworkBehaviourIndex; public ushort NetworkBehaviourIndex;
@@ -21,15 +23,15 @@ namespace Unity.Netcode
private FastBufferReader m_ReceivedNetworkVariableData; 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))) if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{ {
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
} }
writer.WriteValue(NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValue(NetworkBehaviourIndex); BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++) 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. // This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
writer.WriteValueSafe((ushort)0); BytePacker.WriteValueBitPacked(writer, (ushort)0);
} }
else else
{ {
@@ -62,11 +64,21 @@ namespace Unity.Netcode
shouldWrite = false; 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 (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{ {
if (!shouldWrite) if (!shouldWrite)
{ {
BytePacker.WriteValueBitPacked(writer, 0); BytePacker.WriteValueBitPacked(writer, (ushort)0);
} }
} }
else else
@@ -94,12 +106,6 @@ namespace Unity.Netcode
networkVariable.WriteDelta(writer); networkVariable.WriteDelta(writer);
} }
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
{
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
}
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
TargetClientId, TargetClientId,
NetworkBehaviour.NetworkObject, 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))) ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
{ ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
}
reader.ReadValue(out NetworkObjectId);
reader.ReadValue(out NetworkBehaviourIndex);
m_ReceivedNetworkVariableData = reader; m_ReceivedNetworkVariableData = reader;

View File

@@ -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);
}
}
}

View File

@@ -4,18 +4,34 @@ namespace Unity.Netcode
{ {
internal struct ParentSyncMessage : INetworkMessage internal struct ParentSyncMessage : INetworkMessage
{ {
public int Version => 0;
public ulong NetworkObjectId; public ulong NetworkObjectId;
public bool WorldPositionStays; private byte m_BitField;
public bool WorldPositionStays
{
get => ByteUtility.GetBit(m_BitField, 0);
set => ByteUtility.SetBit(ref m_BitField, 0, value);
}
//If(Metadata.IsReparented) //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) //If(IsLatestParentSet)
public ulong? LatestParent; public ulong? LatestParent;
// Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent) // Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
public bool RemoveParent; public bool RemoveParent
{
get => ByteUtility.GetBit(m_BitField, 2);
set => ByteUtility.SetBit(ref m_BitField, 2, value);
}
// These additional properties are used to synchronize clients with the current position, // These additional properties are used to synchronize clients with the current position,
// rotation, and scale after parenting/de-parenting (world/local space relative). This // rotation, and scale after parenting/de-parenting (world/local space relative). This
@@ -25,18 +41,15 @@ namespace Unity.Netcode
public Quaternion Rotation; public Quaternion Rotation;
public Vector3 Scale; public Vector3 Scale;
public void Serialize(FastBufferWriter writer) public void Serialize(FastBufferWriter writer, int targetVersion)
{ {
BytePacker.WriteValuePacked(writer, NetworkObjectId); BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValueSafe(RemoveParent); writer.WriteValueSafe(m_BitField);
writer.WriteValueSafe(WorldPositionStays);
if (!RemoveParent) if (!RemoveParent)
{ {
writer.WriteValueSafe(IsLatestParentSet);
if (IsLatestParentSet) if (IsLatestParentSet)
{ {
BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent); BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
} }
} }
@@ -46,7 +59,7 @@ namespace Unity.Netcode
writer.WriteValueSafe(Scale); writer.WriteValueSafe(Scale);
} }
public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{ {
var networkManager = (NetworkManager)context.SystemOwner; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
@@ -54,13 +67,10 @@ namespace Unity.Netcode
return false; return false;
} }
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out RemoveParent); reader.ReadValueSafe(out m_BitField);
reader.ReadValueSafe(out WorldPositionStays);
if (!RemoveParent) if (!RemoveParent)
{ {
reader.ReadValueSafe(out IsLatestParentSet);
if (IsLatestParentSet) if (IsLatestParentSet)
{ {
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent); ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);

View File

@@ -1,6 +1,6 @@
using System; using System;
using UnityEngine;
using Unity.Collections; using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -8,24 +8,17 @@ namespace Unity.Netcode
{ {
public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload) public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload)
{ {
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<RpcMetadata>() + payload.Length)) BytePacker.WriteValueBitPacked(writer, metadata.NetworkObjectId);
{ BytePacker.WriteValueBitPacked(writer, metadata.NetworkBehaviourId);
throw new OverflowException("Not enough space in the buffer to store RPC data."); BytePacker.WriteValueBitPacked(writer, metadata.NetworkRpcMethodId);
} writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length);
writer.WriteValue(metadata);
writer.WriteBytes(payload.GetUnsafePtr(), payload.Length);
} }
public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload) public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload)
{ {
int metadataSize = FastBufferWriter.GetWriteSize<RpcMetadata>(); ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId);
if (!reader.TryBeginRead(metadataSize)) ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId);
{ ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkRpcMethodId);
throw new InvalidOperationException("Not enough data in the buffer to read RPC meta.");
}
reader.ReadValue(out metadata);
var networkManager = (NetworkManager)context.SystemOwner; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId)) if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
@@ -46,7 +39,7 @@ namespace Unity.Netcode
return false; 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 DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
@@ -92,17 +85,19 @@ namespace Unity.Netcode
internal struct ServerRpcMessage : INetworkMessage internal struct ServerRpcMessage : INetworkMessage
{ {
public int Version => 0;
public RpcMetadata Metadata; public RpcMetadata Metadata;
public FastBufferWriter WriteBuffer; public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer; 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); 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); return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
} }
@@ -125,17 +120,19 @@ namespace Unity.Netcode
internal struct ClientRpcMessage : INetworkMessage internal struct ClientRpcMessage : INetworkMessage
{ {
public int Version => 0;
public RpcMetadata Metadata; public RpcMetadata Metadata;
public FastBufferWriter WriteBuffer; public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer; public FastBufferReader ReadBuffer;
public void Serialize(FastBufferWriter writer) public void Serialize(FastBufferWriter writer, int targetVersion)
{ {
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); 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); return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
} }

View File

@@ -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. // like most of the other messages when we have some more time and can come back and refactor this.
internal struct SceneEventMessage : INetworkMessage internal struct SceneEventMessage : INetworkMessage
{ {
public int Version => 0;
public SceneEventData EventData; public SceneEventData EventData;
private FastBufferReader m_ReceivedData; private FastBufferReader m_ReceivedData;
public void Serialize(FastBufferWriter writer) public void Serialize(FastBufferWriter writer, int targetVersion)
{ {
EventData.Serialize(writer); EventData.Serialize(writer);
} }
public bool Deserialize(FastBufferReader reader, ref NetworkContext context) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{ {
m_ReceivedData = reader; m_ReceivedData = reader;
return true; return true;

View File

@@ -2,6 +2,8 @@ namespace Unity.Netcode
{ {
internal struct ServerLogMessage : INetworkMessage internal struct ServerLogMessage : INetworkMessage
{ {
public int Version => 0;
public NetworkLog.LogType LogType; public NetworkLog.LogType LogType;
// It'd be lovely to be able to replace this with FixedString or NativeArray... // 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 // 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 string Message;
public void Serialize(FastBufferWriter writer) public void Serialize(FastBufferWriter writer, int targetVersion)
{ {
writer.WriteValueSafe(LogType); writer.WriteValueSafe(LogType);
BytePacker.WriteValuePacked(writer, Message); 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; var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs) if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)

View File

@@ -2,21 +2,23 @@ namespace Unity.Netcode
{ {
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
{ {
public int Version => 0;
public int Tick; 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; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
{ {
return false; return false;
} }
reader.ReadValueSafe(out this); ByteUnpacker.ReadValueBitPacked(reader, out Tick);
return true; return true;
} }

View File

@@ -2,15 +2,17 @@ namespace Unity.Netcode
{ {
internal struct UnnamedMessage : INetworkMessage internal struct UnnamedMessage : INetworkMessage
{ {
public int Version => 0;
public FastBufferWriter SendData; public FastBufferWriter SendData;
private FastBufferReader m_ReceivedData; private FastBufferReader m_ReceivedData;
public unsafe void Serialize(FastBufferWriter writer) public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{ {
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length); 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; m_ReceivedData = reader;
return true; return true;

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text;
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using UnityEngine; using UnityEngine;
@@ -41,11 +42,12 @@ namespace Unity.Netcode
{ {
Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize); Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize);
NetworkDelivery = delivery; NetworkDelivery = delivery;
BatchHeader = default; BatchHeader = new BatchHeader { Magic = BatchHeader.MagicValue };
} }
} }
internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system); 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); 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<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>(); 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 List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
private uint m_HighMessageType; private uint m_HighMessageType;
@@ -74,12 +81,13 @@ namespace Unity.Netcode
} }
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300; 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 internal struct MessageWithHandler
{ {
public Type MessageType; public Type MessageType;
public MessageHandler Handler; public MessageHandler Handler;
public VersionGetter GetVersion;
} }
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes) 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 // Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes) foreach (var t in allowedTypes)
{ {
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" || if (t.MessageType.FullName == typeof(ConnectionRequestMessage).FullName ||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" || t.MessageType.FullName == typeof(ConnectionApprovedMessage).FullName)
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
{ {
prioritizedTypes.Add(t); prioritizedTypes.Add(t);
} }
@@ -100,9 +107,8 @@ namespace Unity.Netcode
foreach (var t in allowedTypes) foreach (var t in allowedTypes)
{ {
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" && if (t.MessageType.FullName != typeof(ConnectionRequestMessage).FullName &&
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" && t.MessageType.FullName != typeof(ConnectionApprovedMessage).FullName)
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
{ {
prioritizedTypes.Add(t); prioritizedTypes.Add(t);
} }
@@ -189,7 +195,25 @@ namespace Unity.Netcode
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler; m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType; m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
m_MessagesByHash[XXHash.Hash32(messageWithHandler.MessageType.FullName)] = messageWithHandler.MessageType;
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++; 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) 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); new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
if (!batchReader.TryBeginRead(sizeof(BatchHeader))) 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; return;
} }
batchReader.ReadValue(out BatchHeader batchHeader); 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(); var messageHeader = new MessageHeader();
@@ -225,7 +269,7 @@ namespace Unity.Netcode
} }
catch (OverflowException) 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; throw;
} }
@@ -233,7 +277,7 @@ namespace Unity.Netcode
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize)) 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; return;
} }
m_IncomingMessageQueue.Add(new ReceiveQueueItem m_IncomingMessageQueue.Add(new ReceiveQueueItem
@@ -251,7 +295,7 @@ namespace Unity.Netcode
} }
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) 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,76 +314,62 @@ namespace Unity.Netcode
return true; return true;
} }
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list internal Type GetMessageForHash(uint messageHash)
// 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)
{ {
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 && internal void SetVersion(ulong clientId, uint messageHash, int version)
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash) {
if (!m_MessagesByHash.ContainsKey(messageHash))
{ {
// matching positions and hashes. All good.
return; return;
} }
var messageType = m_MessagesByHash[messageHash];
Debug.Log($"Unexpected hash for {desiredOrder}"); if (!m_PerClientMessageVersions.ContainsKey(clientId))
// 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 (typesAsList[position] != null && m_PerClientMessageVersions[clientId] = new Dictionary<Type, int>();
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
{
found = true;
break;
} }
position++; m_PerClientMessageVersions[clientId][messageType] = version;
} }
if (found) internal void SetServerMessageOrder(NativeArray<uint> messagesInIdOrder)
{ {
// Copy the handler and type to the right index 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>();
typesAsList[desiredOrder] = typesAsList[position]; for (var i = 0; i < messagesInIdOrder.Length; ++i)
handlersAsList[desiredOrder] = handlersAsList[position]; {
typesAsList.RemoveAt(position); if (!m_MessagesByHash.ContainsKey(messagesInIdOrder[i]))
handlersAsList.RemoveAt(position); {
continue;
// we removed a copy after moving a message, reduce the high message index }
m_HighMessageType--; 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;
} }
m_ReverseTypeMap = typesAsList.ToArray();
m_MessageHandlers = handlersAsList.ToArray();
} }
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize) public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
using (reader)
{ {
if (header.MessageType >= m_HighMessageType) if (header.MessageType >= m_HighMessageType)
{ {
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}"); Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
reader.Dispose();
return; return;
} }
var context = new NetworkContext var context = new NetworkContext
@@ -355,18 +385,15 @@ namespace Unity.Netcode
var type = m_ReverseTypeMap[header.MessageType]; var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type, reader, ref context)) if (!CanReceive(senderId, type, reader, ref context))
{ {
reader.Dispose();
return; return;
} }
var handler = m_MessageHandlers[header.MessageType];
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>()); m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
} }
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
// This will also log an exception is if the server knows about a message type the client doesn't know // This will also log an exception is if the server knows about a message type the client doesn't know
// about. In this case the handler will be null. It is still an issue the user must deal with: If the // about. In this case the handler will be null. It is still an issue the user must deal with: If the
// two connecting builds know about different messages, the server should not send a message to a client // two connecting builds know about different messages, the server should not send a message to a client
@@ -391,12 +418,12 @@ namespace Unity.Netcode
Debug.LogException(e); Debug.LogException(e);
} }
} }
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>()); m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
} }
} }
}
internal unsafe void ProcessIncomingMessageQueue() internal unsafe void ProcessIncomingMessageQueue()
{ {
@@ -433,7 +460,7 @@ namespace Unity.Netcode
m_SendQueues.Remove(clientId); m_SendQueues.Remove(clientId);
} }
private unsafe void CleanupDisconnectedClient(ulong clientId) private void CleanupDisconnectedClient(ulong clientId)
{ {
var queue = m_SendQueues[clientId]; var queue = m_SendQueues[clientId];
for (var i = 0; i < queue.Length; ++i) for (var i = 0; i < queue.Length; ++i)
@@ -444,10 +471,67 @@ namespace Unity.Netcode
queue.Dispose(); 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() public static void ReceiveMessage<T>(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new()
{ {
var message = new T(); 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) for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
{ {
@@ -485,16 +569,47 @@ namespace Unity.Netcode
return 0; return 0;
} }
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;
}
}
if (sentMessageVersions.Contains(messageVersion))
{
continue;
}
sentMessageVersions.Add(messageVersion);
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE; 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>()); using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
message.Serialize(tmpSerializer); message.Serialize(tmpSerializer, messageVersion);
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds); var size = SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds, messageVersion);
largestSerializedSize = size > largestSerializedSize ? size : largestSerializedSize;
} }
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds) sentMessageVersions.Dispose();
return largestSerializedSize;
}
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds, int messageVersionFilter)
where TMessageType : INetworkMessage where TMessageType : INetworkMessage
{ {
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp); using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
@@ -509,6 +624,25 @@ namespace Unity.Netcode
for (var i = 0; i < clientIds.Count; ++i) 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]; var clientId = clientIds[i];
if (!CanSend(clientId, typeof(TMessageType), delivery)) if (!CanSend(clientId, typeof(TMessageType), delivery))
@@ -546,7 +680,7 @@ namespace Unity.Netcode
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length); writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length); writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++; writeQueueItem.BatchHeader.BatchCount++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length); m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length);
@@ -559,8 +693,22 @@ namespace Unity.Netcode
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId) internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId)
where TMessageType : INetworkMessage 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 }; 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> private struct PointerListWrapper<T> : IReadOnlyList<T>
@@ -627,7 +775,7 @@ namespace Unity.Netcode
for (var i = 0; i < sendQueueItem.Length; ++i) for (var i = 0; i < sendQueueItem.Length; ++i)
{ {
ref var queueItem = ref sendQueueItem.ElementAt(i); ref var queueItem = ref sendQueueItem.ElementAt(i);
if (queueItem.BatchHeader.BatchSize == 0) if (queueItem.BatchHeader.BatchCount == 0)
{ {
queueItem.Writer.Dispose(); queueItem.Writer.Dispose();
continue; continue;
@@ -635,23 +783,28 @@ namespace Unity.Netcode
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) 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); queueItem.Writer.Seek(0);
#if UNITY_EDITOR || DEVELOPMENT_BUILD #if UNITY_EDITOR || DEVELOPMENT_BUILD
// Skipping the Verify and sneaking the write mark in because we know it's fine. // Skipping the Verify and sneaking the write mark in because we know it's fine.
queueItem.Writer.Handle->AllowedWriteMark = 2; queueItem.Writer.Handle->AllowedWriteMark = sizeof(BatchHeader);
#endif #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); queueItem.Writer.WriteValue(queueItem.BatchHeader);
try try
{ {
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer); m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) 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 finally

View File

@@ -413,7 +413,7 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public bool Contains(T item) public bool Contains(T item)
{ {
int index = NativeArrayExtensions.IndexOf(m_List, item); int index = m_List.IndexOf(item);
return index != -1; return index != -1;
} }
@@ -426,7 +426,7 @@ namespace Unity.Netcode
throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
} }
int index = NativeArrayExtensions.IndexOf(m_List, item); int index = m_List.IndexOf(item);
if (index == -1) if (index == -1)
{ {
return false; return false;

View File

@@ -1,5 +1,5 @@
using UnityEngine;
using System; using System;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {

View File

@@ -18,6 +18,11 @@ namespace Unity.Netcode
/// </summary> /// </summary>
private protected NetworkBehaviour m_NetworkBehaviour; private protected NetworkBehaviour m_NetworkBehaviour;
public NetworkBehaviour GetBehaviour()
{
return m_NetworkBehaviour;
}
/// <summary> /// <summary>
/// Initializes the NetworkVariable /// Initializes the NetworkVariable
/// </summary> /// </summary>

View File

@@ -1,6 +1,8 @@
using System; using System;
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -20,6 +22,96 @@ namespace Unity.Netcode
public void Read(FastBufferReader reader, ref 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> /// <summary>
/// Basic serializer for unmanaged types. /// Basic serializer for unmanaged types.
/// This covers primitives, built-in unity types, and IForceSerializeByMemcpy /// This covers primitives, built-in unity types, and IForceSerializeByMemcpy
@@ -188,6 +280,26 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public static class NetworkVariableSerializationTypes public static class NetworkVariableSerializationTypes
{ {
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
#if UNITY_EDITOR
[InitializeOnLoadMethod]
#endif
internal static void InitializeIntegerSerialization()
{
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> /// <summary>
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
/// </summary> /// </summary>

View File

@@ -0,0 +1,377 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
/// </summary>
internal class DefaultSceneManagerHandler : ISceneManagerHandler
{
private Scene m_InvalidScene = new Scene();
internal struct SceneEntry
{
public bool IsAssigned;
public Scene Scene;
}
internal Dictionary<string, Dictionary<int, SceneEntry>> SceneNameToSceneHandles = new Dictionary<string, Dictionary<int, SceneEntry>>();
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
{
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
sceneEventProgress.SetAsyncOperation(operation);
return operation;
}
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
{
var operation = SceneManager.UnloadSceneAsync(scene);
sceneEventProgress.SetAsyncOperation(operation);
return operation;
}
/// <summary>
/// Resets scene tracking
/// </summary>
public void ClearSceneTracking(NetworkManager networkManager)
{
SceneNameToSceneHandles.Clear();
}
/// <summary>
/// Stops tracking a specific scene
/// </summary>
public void StopTrackingScene(int handle, string name, NetworkManager networkManager)
{
if (SceneNameToSceneHandles.ContainsKey(name))
{
if (SceneNameToSceneHandles[name].ContainsKey(handle))
{
SceneNameToSceneHandles[name].Remove(handle);
if (SceneNameToSceneHandles[name].Count == 0)
{
SceneNameToSceneHandles.Remove(name);
}
}
}
}
/// <summary>
/// Starts tracking a specific scene
/// </summary>
public void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager)
{
if (!SceneNameToSceneHandles.ContainsKey(scene.name))
{
SceneNameToSceneHandles.Add(scene.name, new Dictionary<int, SceneEntry>());
}
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
var sceneEntry = new SceneEntry()
{
IsAssigned = true,
Scene = scene
};
SceneNameToSceneHandles[scene.name].Add(scene.handle, sceneEntry);
}
else
{
throw new Exception($"[Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
}
}
/// <summary>
/// Determines if there is an existing scene loaded that matches the scene name but has not been assigned
/// </summary>
public bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager)
{
var scenesWithSceneName = new List<Scene>();
// Get all loaded scenes with the same name
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (scene.name == sceneName)
{
scenesWithSceneName.Add(scene);
}
}
// If there are no scenes of this name loaded then we have no loaded scenes
// to use
if (scenesWithSceneName.Count == 0)
{
return false;
}
// If we have 1 or more scenes with the name and we have no entries, then we do have
// a scene to use
if (scenesWithSceneName.Count > 0 && !SceneNameToSceneHandles.ContainsKey(sceneName))
{
return true;
}
// Determine if any of the loaded scenes has been used for synchronizing
foreach (var scene in scenesWithSceneName)
{
// If we don't have the handle, then we can use that scene
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
return true;
}
// If we have an entry, but it is not yet assigned (i.e. preloaded)
// then we can use that.
if (!SceneNameToSceneHandles[scene.name][scene.handle].IsAssigned)
{
return true;
}
}
// If none were found, then we have no available scene (which most likely means one will get loaded)
return false;
}
/// <summary>
/// This will find any scene entry that hasn't been used/assigned, set the entry to assigned, and
/// return the associated scene. If none are found it returns an invalid scene.
/// </summary>
public Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager)
{
if (SceneNameToSceneHandles.ContainsKey(sceneName))
{
foreach (var sceneHandleEntry in SceneNameToSceneHandles[sceneName])
{
if (!sceneHandleEntry.Value.IsAssigned)
{
var sceneEntry = sceneHandleEntry.Value;
sceneEntry.IsAssigned = true;
SceneNameToSceneHandles[sceneName][sceneHandleEntry.Key] = sceneEntry;
return sceneEntry.Scene;
}
}
}
// If we found nothing return an invalid scene
return m_InvalidScene;
}
/// <summary>
/// Only invoked is client synchronization is additive, this will generate the scene tracking table
/// in order to re-use the same scenes the server is synchronizing instead of having to unload the
/// scenes and reload them when synchronizing (i.e. client disconnects due to external reason, the
/// same application instance is still running, the same scenes are still loaded on the client, and
/// upon reconnecting the client doesn't have to unload the scenes and then reload them)
/// </summary>
public void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager)
{
SceneNameToSceneHandles.Clear();
var sceneCount = SceneManager.sceneCount;
for (int i = 0; i < sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (!SceneNameToSceneHandles.ContainsKey(scene.name))
{
SceneNameToSceneHandles.Add(scene.name, new Dictionary<int, SceneEntry>());
}
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
var sceneEntry = new SceneEntry()
{
IsAssigned = false,
Scene = scene
};
SceneNameToSceneHandles[scene.name].Add(scene.handle, sceneEntry);
if (!scenesLoaded.ContainsKey(scene.handle))
{
scenesLoaded.Add(scene.handle, scene);
}
}
else
{
throw new Exception($"[Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
}
}
}
private List<Scene> m_ScenesToUnload = new List<Scene>();
/// <summary>
/// Unloads any scenes that have not been assigned.
/// </summary>
/// <param name="networkManager"></param>
public void UnloadUnassignedScenes(NetworkManager networkManager = null)
{
var sceneManager = networkManager.SceneManager;
SceneManager.sceneUnloaded += SceneManager_SceneUnloaded;
foreach (var sceneEntry in SceneNameToSceneHandles)
{
var scenHandleEntries = SceneNameToSceneHandles[sceneEntry.Key];
foreach (var sceneHandleEntry in scenHandleEntries)
{
if (!sceneHandleEntry.Value.IsAssigned)
{
if (sceneManager.VerifySceneBeforeUnloading == null || sceneManager.VerifySceneBeforeUnloading.Invoke(sceneHandleEntry.Value.Scene))
{
m_ScenesToUnload.Add(sceneHandleEntry.Value.Scene);
}
}
}
}
foreach (var sceneToUnload in m_ScenesToUnload)
{
SceneManager.UnloadSceneAsync(sceneToUnload);
}
}
private void SceneManager_SceneUnloaded(Scene scene)
{
if (SceneNameToSceneHandles.ContainsKey(scene.name))
{
if (SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
SceneNameToSceneHandles[scene.name].Remove(scene.handle);
}
if (SceneNameToSceneHandles[scene.name].Count == 0)
{
SceneNameToSceneHandles.Remove(scene.name);
}
m_ScenesToUnload.Remove(scene);
if (m_ScenesToUnload.Count == 0)
{
SceneManager.sceneUnloaded -= SceneManager_SceneUnloaded;
}
}
}
/// <summary>
/// Handles determining if a client should attempt to load a scene during synchronization.
/// </summary>
/// <param name="sceneName">name of the scene to be loaded</param>
/// <param name="isPrimaryScene">when in client synchronization mode single, this determines if the scene is the primary active scene</param>
/// <param name="clientSynchronizationMode">the current client synchronization mode</param>
/// <param name="networkManager"><see cref="NetworkManager"/> instance</param>
/// <returns></returns>
public bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager)
{
var shouldPassThrough = clientSynchronizationMode == LoadSceneMode.Single ? false : DoesSceneHaveUnassignedEntry(sceneName, networkManager);
var activeScene = SceneManager.GetActiveScene();
// If shouldPassThrough is not yet true and the scene to be loaded is the currently active scene
if (!shouldPassThrough && sceneName == activeScene.name)
{
// In additive mode we always pass through, but in LoadSceneMode.Single we only pass through if the currently active scene
// is the primary scene to be loaded
if (clientSynchronizationMode == LoadSceneMode.Additive || (isPrimaryScene && clientSynchronizationMode == LoadSceneMode.Single))
{
// don't try to reload this scene and pass through to post load processing.
shouldPassThrough = true;
}
}
return shouldPassThrough;
}
/// <summary>
/// Handles migrating dynamically spawned NetworkObjects to the DDOL when a scene is unloaded
/// </summary>
/// <param name="networkManager"><see cref="NetworkManager"/>relative instance</param>
/// <param name="scene">scene being unloaded</param>
public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene)
{
bool isActiveScene = scene == SceneManager.GetActiveScene();
// Create a local copy of the spawned objects list since the spawn manager will adjust the list as objects
// are despawned.
var localSpawnedObjectsHashSet = new HashSet<NetworkObject>(networkManager.SpawnManager.SpawnedObjectsList);
foreach (var networkObject in localSpawnedObjectsHashSet)
{
if (networkObject == null || (networkObject != null && networkObject.gameObject.scene.handle != scene.handle))
{
continue;
}
// Only NetworkObjects marked to not be destroyed with the scene and are not already in the DDOL are preserved
if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene)
{
// Only move dynamically spawned NetworkObjects with no parent as the children will follow
if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
else if (networkManager.IsServer)
{
networkObject.Despawn();
}
else // We are a client, migrate the object into the DDOL temporarily until it receives the destroy command from the server
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
}
/// <summary>
/// Sets the client synchronization mode which impacts whether both the server or client take into consideration scenes loaded before
/// starting the <see cref="NetworkManager"/>.
/// </summary>
/// <remarks>
/// <see cref="LoadSceneMode.Single"/>: Does not take preloaded scenes into consideration
/// <see cref="LoadSceneMode.Single"/>: Does take preloaded scenes into consideration
/// </remarks>
/// <param name="networkManager">relative <see cref="NetworkManager"/> instance</param>
/// <param name="mode"><see cref="LoadSceneMode.Single"/> or <see cref="LoadSceneMode.Additive"/></param>
public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode)
{
var sceneManager = networkManager.SceneManager;
// Don't let client's set this value
if (!networkManager.IsServer)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Clients should not set this value as it is automatically synchronized with the server's setting!");
}
return;
}
else // Warn users if they are changing this after there are clients already connected and synchronized
if (networkManager.ConnectedClientsIds.Count > (networkManager.IsServer ? 0 : 1) && sceneManager.ClientSynchronizationMode != mode)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!");
}
}
// For additive client synchronization, we take into consideration scenes
// already loaded.
if (mode == LoadSceneMode.Additive)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
// If using scene verification
if (sceneManager.VerifySceneBeforeLoading != null)
{
// Determine if we should take this scene into consideration
if (!sceneManager.VerifySceneBeforeLoading.Invoke(scene.buildIndex, scene.name, LoadSceneMode.Additive))
{
continue;
}
}
// If the scene is not already in the ScenesLoaded list, then add it
if (!sceneManager.ScenesLoaded.ContainsKey(scene.handle))
{
sceneManager.ScenesLoaded.Add(scene.handle, scene);
}
}
}
// Set the client synchronization mode
sceneManager.ClientSynchronizationMode = mode;
}
}
}

View File

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

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
@@ -12,5 +13,24 @@ namespace Unity.Netcode
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress); AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress); AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager = null);
Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager = null);
bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager = null);
void StopTrackingScene(int handle, string name, NetworkManager networkManager = null);
void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager = null);
void ClearSceneTracking(NetworkManager networkManager = null);
void UnloadUnassignedScenes(NetworkManager networkManager = null);
void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene);
void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode);
bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Unity.Collections; using Unity.Collections;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
@@ -80,6 +80,16 @@ namespace Unity.Netcode
/// <b>Event Notification:</b> Both server and client receive a local notification. /// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary> /// </summary>
SynchronizeComplete, SynchronizeComplete,
/// <summary>
/// Synchronizes clients when the active scene has changed
/// See: <see cref="NetworkObject.ActiveSceneSynchronization"/>
/// </summary>
ActiveSceneChanged,
/// <summary>
/// Synchronizes clients when one or more NetworkObjects are migrated into a new scene
/// See: <see cref="NetworkObject.SceneMigrationSynchronization"/>
/// </summary>
ObjectSceneChanged,
} }
/// <summary> /// <summary>
@@ -94,7 +104,7 @@ namespace Unity.Netcode
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId; internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
internal uint SceneEventId; internal uint SceneEventId;
internal uint ActiveSceneHash;
internal uint SceneHash; internal uint SceneHash;
internal int SceneHandle; internal int SceneHandle;
@@ -139,6 +149,8 @@ namespace Unity.Netcode
internal Queue<uint> ScenesToSynchronize; internal Queue<uint> ScenesToSynchronize;
internal Queue<uint> SceneHandlesToSynchronize; internal Queue<uint> SceneHandlesToSynchronize;
internal LoadSceneMode ClientSynchronizationMode;
/// <summary> /// <summary>
/// Server Side: /// Server Side:
@@ -269,7 +281,12 @@ namespace Unity.Netcode
{ {
m_DespawnedInSceneObjectsSync.Clear(); m_DespawnedInSceneObjectsSync.Clear();
// Find all active and non-active in-scene placed NetworkObjects // 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); var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
#endif
foreach (var sobj in inSceneNetworkObjects) foreach (var sobj in inSceneNetworkObjects)
{ {
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned) if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
@@ -310,6 +327,8 @@ namespace Unity.Netcode
case SceneEventType.ReSynchronize: case SceneEventType.ReSynchronize:
case SceneEventType.LoadEventCompleted: case SceneEventType.LoadEventCompleted:
case SceneEventType.UnloadEventCompleted: case SceneEventType.UnloadEventCompleted:
case SceneEventType.ActiveSceneChanged:
case SceneEventType.ObjectSceneChanged:
{ {
return true; return true;
} }
@@ -379,14 +398,30 @@ namespace Unity.Netcode
// Write the scene event type // Write the scene event type
writer.WriteValueSafe(SceneEventType); writer.WriteValueSafe(SceneEventType);
if (SceneEventType == SceneEventType.ActiveSceneChanged)
{
writer.WriteValueSafe(ActiveSceneHash);
return;
}
if (SceneEventType == SceneEventType.ObjectSceneChanged)
{
SerializeObjectsMovedIntoNewScene(writer);
return;
}
// Write the scene loading mode // Write the scene loading mode
writer.WriteValueSafe(LoadSceneMode); writer.WriteValueSafe((byte)LoadSceneMode);
// Write the scene event progress Guid // Write the scene event progress Guid
if (SceneEventType != SceneEventType.Synchronize) if (SceneEventType != SceneEventType.Synchronize)
{ {
writer.WriteValueSafe(SceneEventProgressId); writer.WriteValueSafe(SceneEventProgressId);
} }
else
{
writer.WriteValueSafe(ClientSynchronizationMode);
}
// Write the scene index and handle // Write the scene index and handle
writer.WriteValueSafe(SceneHash); writer.WriteValueSafe(SceneHash);
@@ -396,6 +431,7 @@ namespace Unity.Netcode
{ {
case SceneEventType.Synchronize: case SceneEventType.Synchronize:
{ {
writer.WriteValueSafe(ActiveSceneHash);
WriteSceneSynchronizationData(writer); WriteSceneSynchronizationData(writer);
break; break;
} }
@@ -440,20 +476,20 @@ namespace Unity.Netcode
// Size Place Holder -- Start // Size Place Holder -- Start
// !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written,
// for stream offset purposes this MUST not be a packed value! // for stream offset purposes this MUST not be a packed value!
writer.WriteValueSafe((int)0); writer.WriteValueSafe(0);
int totalBytes = 0; int totalBytes = 0;
// Write the number of NetworkObjects we are serializing // 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 // 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 noStart = writer.Position;
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId); var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
sceneObject.Serialize(writer); sceneObject.Serialize(writer);
var noStop = writer.Position; var noStop = writer.Position;
totalBytes += (int)(noStop - noStart); totalBytes += noStop - noStart;
} }
// Write the number of despawned in-scene placed NetworkObjects // Write the number of despawned in-scene placed NetworkObjects
@@ -462,10 +498,10 @@ namespace Unity.Netcode
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i) for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{ {
var noStart = writer.Position; var noStart = writer.Position;
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position; var noStop = writer.Position;
totalBytes += (int)(noStop - noStart); totalBytes += noStop - noStart;
} }
// Size Place Holder -- End // Size Place Holder -- End
@@ -497,8 +533,6 @@ namespace Unity.Netcode
{ {
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId)) if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
{ {
// Write our server relative scene handle for the NetworkObject being serialized
writer.WriteValueSafe(keyValuePairBySceneHandle.Key);
// Serialize the NetworkObject // Serialize the NetworkObject
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId); var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId);
sceneObject.Serialize(writer); sceneObject.Serialize(writer);
@@ -512,8 +546,8 @@ namespace Unity.Netcode
// Write the scene handle and GlobalObjectIdHash value // 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)
{ {
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
} }
var tailPosition = writer.Position; var tailPosition = writer.Position;
@@ -533,12 +567,37 @@ namespace Unity.Netcode
internal void Deserialize(FastBufferReader reader) internal void Deserialize(FastBufferReader reader)
{ {
reader.ReadValueSafe(out SceneEventType); reader.ReadValueSafe(out SceneEventType);
reader.ReadValueSafe(out LoadSceneMode); if (SceneEventType == SceneEventType.ActiveSceneChanged)
{
reader.ReadValueSafe(out ActiveSceneHash);
return;
}
if (SceneEventType == SceneEventType.ObjectSceneChanged)
{
// Defer these scene event types if a client hasn't finished synchronizing
if (!m_NetworkManager.IsConnectedClient)
{
DeferObjectsMovedIntoNewScene(reader);
}
else
{
DeserializeObjectsMovedIntoNewScene(reader);
}
return;
}
reader.ReadValueSafe(out byte loadSceneMode);
LoadSceneMode = (LoadSceneMode)loadSceneMode;
if (SceneEventType != SceneEventType.Synchronize) if (SceneEventType != SceneEventType.Synchronize)
{ {
reader.ReadValueSafe(out SceneEventProgressId); reader.ReadValueSafe(out SceneEventProgressId);
} }
else
{
reader.ReadValueSafe(out ClientSynchronizationMode);
}
reader.ReadValueSafe(out SceneHash); reader.ReadValueSafe(out SceneHash);
reader.ReadValueSafe(out SceneHandle); reader.ReadValueSafe(out SceneHandle);
@@ -547,6 +606,7 @@ namespace Unity.Netcode
{ {
case SceneEventType.Synchronize: case SceneEventType.Synchronize:
{ {
reader.ReadValueSafe(out ActiveSceneHash);
CopySceneSynchronizationData(reader); CopySceneSynchronizationData(reader);
break; break;
} }
@@ -624,13 +684,15 @@ namespace Unity.Netcode
for (ushort i = 0; i < newObjectsCount; i++) 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(); var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer); 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); NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
} }
// Now deserialize the despawned in-scene placed NetworkObjects list (if any) // Now deserialize the despawned in-scene placed NetworkObjects list (if any)
@@ -656,7 +718,11 @@ namespace Unity.Netcode
if (networkObjectsToRemove.Length > 0) 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>(); var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
var networkObjectIdToNetworkObject = new Dictionary<ulong, NetworkObject>(); var networkObjectIdToNetworkObject = new Dictionary<ulong, NetworkObject>();
foreach (var networkObject in networkObjects) foreach (var networkObject in networkObjects)
{ {
@@ -771,8 +837,8 @@ namespace Unity.Netcode
for (int i = 0; i < despawnedObjectsCount; i++) for (int i = 0; i < despawnedObjectsCount; i++)
{ {
// We just need to get the scene // We just need to get the scene
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle); InternalBuffer.ReadValueSafe(out int networkSceneHandle);
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash); InternalBuffer.ReadValueSafe(out uint globalObjectIdHash);
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>(); var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
if (!sceneCache.ContainsKey(networkSceneHandle)) if (!sceneCache.ContainsKey(networkSceneHandle))
{ {
@@ -784,8 +850,14 @@ namespace Unity.Netcode
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle]; var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
// Find all active and non-active in-scene placed NetworkObjects // 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) => var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#endif
foreach (var inSceneObject in inSceneNetworkObjects) foreach (var inSceneObject in inSceneNetworkObjects)
{ {
@@ -847,26 +919,28 @@ namespace Unity.Netcode
try try
{ {
// Process all spawned NetworkObjects for this network session // 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++) 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(); var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer); 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); var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager);
// If we failed to deserialize the NetowrkObject then don't add null to the list
if (spawnedNetworkObject != null)
{
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
{ {
m_NetworkObjectsSync.Add(spawnedNetworkObject); m_NetworkObjectsSync.Add(spawnedNetworkObject);
} }
} }
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any) // Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects(); DeserializeDespawnedInScenePlacedNetworkObjects();
@@ -921,6 +995,143 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Serialize scene handles and associated NetworkObjects that were migrated
/// into a new scene.
/// </summary>
private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer)
{
var sceneManager = m_NetworkManager.SceneManager;
// Write the number of scene handles
writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count);
foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene)
{
// Write the scene handle
writer.WriteValueSafe(sceneHandleObjects.Key);
// Write the number of NetworkObjectIds to expect
writer.WriteValueSafe(sceneHandleObjects.Value.Count);
foreach (var networkObject in sceneHandleObjects.Value)
{
writer.WriteValueSafe(networkObject.NetworkObjectId);
}
}
// Once we are done, clear the table
sceneManager.ObjectsMigratedIntoNewScene.Clear();
}
/// <summary>
/// Deserialize scene handles and associated NetworkObjects that need to
/// be migrated into a new scene.
/// </summary>
private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader)
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
// Just always assure this has no entries
sceneManager.ObjectsMigratedIntoNewScene.Clear();
var numberOfScenes = 0;
var sceneHandle = 0;
var objectCount = 0;
var networkObjectId = (ulong)0;
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, new List<NetworkObject>());
reader.ReadValueSafe(out objectCount);
for (int j = 0; j < objectCount; j++)
{
reader.ReadValueSafe(out networkObjectId);
if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId))
{
NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!");
continue;
}
// Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed
//
sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(spawnManager.SpawnedObjects[networkObjectId]);
}
}
}
/// <summary>
/// While a client is synchronizing ObjectSceneChanged messages could be received.
/// This defers any ObjectSceneChanged message processing to occur after the client
/// has completed synchronization to assure the associated NetworkObjects being
/// migrated to a new scene are instantiated and spawned.
/// </summary>
private void DeferObjectsMovedIntoNewScene(FastBufferReader reader)
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
var numberOfScenes = 0;
var sceneHandle = 0;
var objectCount = 0;
var networkObjectId = (ulong)0;
var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent()
{
ObjectsMigratedTable = new Dictionary<int, List<ulong>>()
};
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List<ulong>());
reader.ReadValueSafe(out objectCount);
for (int j = 0; j < objectCount; j++)
{
reader.ReadValueSafe(out networkObjectId);
deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId);
}
}
sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent);
}
internal void ProcessDeferredObjectSceneChangedEvents()
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
if (sceneManager.DeferredObjectsMovedEvents.Count == 0)
{
return;
}
foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents)
{
foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable)
{
if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key))
{
sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new List<NetworkObject>());
}
foreach (var objectId in keyEntry.Value)
{
if (!spawnManager.SpawnedObjects.ContainsKey(objectId))
{
NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!");
continue;
}
var networkObject = spawnManager.SpawnedObjects[objectId];
if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Contains(networkObject))
{
sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(networkObject);
}
}
}
objectsMovedEvent.ObjectsMigratedTable.Clear();
}
sceneManager.DeferredObjectsMovedEvents.Clear();
// If there are any pending objects to migrate, then migrate them
if (sceneManager.ObjectsMigratedIntoNewScene.Count > 0)
{
sceneManager.MigrateNetworkObjectsIntoScenes();
}
}
/// <summary> /// <summary>
/// Used to release the pooled network buffer /// Used to release the pooled network buffer
/// </summary> /// </summary>

View File

@@ -83,7 +83,7 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal bool HasTimedOut() internal bool HasTimedOut()
{ {
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup; return WhenSceneEventHasTimedOut <= m_NetworkManager.RealTimeProvider.RealTimeSinceStartup;
} }
/// <summary> /// <summary>
@@ -108,6 +108,16 @@ namespace Unity.Netcode
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent) internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
{ {
var clients = new List<ulong>(); 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) foreach (var clientStatus in ClientsProcessingSceneEvent)
{ {
if (clientStatus.Value == completedSceneEvent) if (clientStatus.Value == completedSceneEvent)
@@ -115,12 +125,20 @@ namespace Unity.Netcode
clients.Add(clientStatus.Key); 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 // 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, then add any clients that disconnected during this
// scene event. // scene event.
if (!completedSceneEvent)
{
clients.AddRange(ClientsThatDisconnected); clients.AddRange(ClientsThatDisconnected);
} }
return clients; return clients;
@@ -138,10 +156,15 @@ namespace Unity.Netcode
// Track the clients that were connected when we started this event // Track the clients that were connected when we started this event
foreach (var connectedClientId in networkManager.ConnectedClientsIds) foreach (var connectedClientId in networkManager.ConnectedClientsIds)
{ {
// Ignore the host client
if (NetworkManager.ServerClientId == connectedClientId)
{
continue;
}
ClientsProcessingSceneEvent.Add(connectedClientId, false); ClientsProcessingSceneEvent.Add(connectedClientId, false);
} }
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut; WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress()); m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
} }
} }
@@ -218,7 +241,10 @@ namespace Unity.Netcode
} }
// Return the local scene event's AsyncOperation status // Return the local scene event's AsyncOperation status
return m_AsyncOperation.isDone; // 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> /// <summary>

View File

@@ -7,14 +7,10 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Two-way serializer wrapping FastBufferReader or FastBufferWriter. /// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
/// ///
/// Implemented as a ref struct for two reasons: /// Implemented as a ref struct to help enforce the requirement that
/// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash /// 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
/// ///
/// Ref structs help enforce both of those rules: they can't ref live the stack context in which they were /// BufferSerializer doesn't wrap FastBufferReader or FastBufferWriter directly because it can't.
/// created, and they're always passed by reference no matter what.
///
/// BufferSerializer doesn't wrapp 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 /// 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 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, /// the struct has to implement an interface. So IReaderWriter exists as the interface,

View File

@@ -50,7 +50,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, float value) public static void WriteValuePacked(FastBufferWriter writer, float value)
{ {
WriteUInt32Packed(writer, ToUint(value)); WriteValueBitPacked(writer, ToUint(value));
} }
/// <summary> /// <summary>
@@ -61,7 +61,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, double value) public static void WriteValuePacked(FastBufferWriter writer, double value)
{ {
WriteUInt64Packed(writer, ToUlong(value)); WriteValueBitPacked(writer, ToUlong(value));
} }
/// <summary> /// <summary>
@@ -98,7 +98,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param> /// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param> /// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Write an unsigned short (UInt16) as a varint to the buffer. /// 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="writer">The writer to write to</param>
/// <param name="value">Value to write</param> /// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Write a two-byte character as a varint to the buffer. /// 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="writer">The writer to write to</param>
/// <param name="c">Value to write</param> /// <param name="c">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Write a signed int (Int32) as a ZigZag encoded varint to the buffer. /// 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="writer">The writer to write to</param>
/// <param name="value">Value to write</param> /// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Write an unsigned int (UInt32) to the buffer. /// 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="writer">The writer to write to</param>
/// <param name="value">Value to write</param> /// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Write an unsigned long (UInt64) to the buffer. /// 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="writer">The writer to write to</param>
/// <param name="value">Value to write</param> /// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Write a signed long (Int64) as a ZigZag encoded varint to the buffer. /// 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="writer">The writer to write to</param>
/// <param name="value">Value to write</param> /// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Convenience method that writes two packed Vector3 from the ray to the buffer /// Convenience method that writes two packed Vector3 from the ray to the buffer
@@ -282,231 +282,183 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value); public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
#else #else
/// <summary> /// <summary>
/// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0) /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const ushort BitPackedUshortMax = (1 << 15) - 1; public const ushort BitPackedUshortMax = (1 << 15) - 1;
/// <summary> /// <summary>
/// Maximum serializable value for a BitPacked short /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const short BitPackedShortMax = (1 << 14) - 1; public const short BitPackedShortMax = (1 << 14) - 1;
/// <summary> /// <summary>
/// Minimum serializable value size for a BitPacked ushort /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const short BitPackedShortMin = -(1 << 14); public const short BitPackedShortMin = -(1 << 14);
/// <summary> /// <summary>
/// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0) /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const uint BitPackedUintMax = (1 << 30) - 1; public const uint BitPackedUintMax = (1 << 30) - 1;
/// <summary> /// <summary>
/// Maximum serializable value for a BitPacked int /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const int BitPackedIntMax = (1 << 29) - 1; public const int BitPackedIntMax = (1 << 29) - 1;
/// <summary> /// <summary>
/// Minimum serializable value size for a BitPacked int /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const int BitPackedIntMin = -(1 << 29); public const int BitPackedIntMin = -(1 << 29);
/// <summary> /// <summary>
/// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0) /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const ulong BitPackedULongMax = (1L << 61) - 1; public const ulong BitPackedULongMax = (1L << 61) - 1;
/// <summary> /// <summary>
/// Maximum serializable value for a BitPacked long /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const long BitPackedLongMax = (1L << 60) - 1; public const long BitPackedLongMax = (1L << 60) - 1;
/// <summary> /// <summary>
/// Minimum serializable value size for a BitPacked long /// Obsolete value that no longer carries meaning. Do not use.
/// </summary> /// </summary>
public const long BitPackedLongMin = -(1L << 60); public const long BitPackedLongMin = -(1L << 60);
/// <summary> /// <summary>
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format. /// Writes a 16-bit signed short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2. /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
/// The sign bit takes up another bit. /// are still able to be compressed.
/// That leaves 14 bits for the value. /// The first two bits indicate whether the value is 1, 2, or 3 bytes.
/// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds. /// If the value uses 14 bits or less, the remaining 14 bits contain the value.
/// In release builds builds the exception is not thrown and the value is truncated by losing its two /// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed
/// most significant bits after zig-zag encoding. /// by the original unmodified 16-bit value in the next 2 bytes.
/// </summary> /// </summary>
/// <param name="writer">The writer to write to</param> /// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param> /// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, (ushort)Arithmetic.ZigZagEncode(value)); public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, (ushort)Arithmetic.ZigZagEncode(value));
/// <summary> /// <summary>
/// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format. /// Writes a 16-bit unsigned short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2. /// The first two bits indicate whether the value is 1, 2, or 3 bytes.
/// That leaves 15 bits for the value. /// If the value uses 14 bits or less, the remaining 14 bits contain the value.
/// A value greater than 2^15-1 will throw an exception in editor and development builds. /// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed
/// In release builds builds the exception is not thrown and the value is truncated by losing its /// by the original unmodified 16-bit value in the next 2 bytes.
/// most significant bit.
/// </summary> /// </summary>
/// <param name="writer">The writer to write to</param> /// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param> /// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value) public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR if (value > (1 << 14) - 1)
if (value >= BitPackedUshortMax)
{ {
throw new ArgumentException("BitPacked ushorts must be <= 15 bits"); if (!writer.TryBeginWriteInternal(3))
}
#endif
if (value <= 0b0111_1111)
{
if (!writer.TryBeginWriteInternal(1))
{ {
throw new OverflowException("Writing past the end of the buffer"); throw new OverflowException("Writing past the end of the buffer");
} }
writer.WriteByte((byte)(value << 1)); writer.WriteByte(3);
writer.WriteValue(value);
return; 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; value <<= 2;
var numBytes = BitCounter.GetUsedByteCount(value); var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes)) if (!writer.TryBeginWriteInternal(numBytes))
{ {
throw new OverflowException("Writing past the end of the buffer"); throw new OverflowException("Writing past the end of the buffer");
} }
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes); writer.WritePartialValue(value | (ushort)(numBytes), numBytes);
} }
/// <summary> /// <summary>
/// Writes a 60-bit signed long to the buffer in a bit-encoded packed format. /// Writes a 32-bit signed int 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. /// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
/// The sign bit takes up another bit. /// are still able to be compressed.
/// That leaves 60 bits for the value. /// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes.
/// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds. /// If the value uses 29 bits or less, the remaining 29 bits contain the value.
/// In release builds builds the exception is not thrown and the value is truncated by losing its four /// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed
/// most significant bits after zig-zag encoding. /// by the original unmodified 32-bit value in the next 4 bytes.
/// </summary> /// </summary>
/// <param name="writer">The writer to write to</param> /// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</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> /// <summary>
/// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format. /// 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, 5, 6, 7, or 8 bytes. /// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes.
/// That leaves 31 bits for the value. /// If the value uses 29 bits or less, the remaining 29 bits contain the value.
/// A value greater than 2^61-1 will throw an exception in editor and development builds. /// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed
/// In release builds builds the exception is not thrown and the value is truncated by losing its three /// by the original unmodified 32-bit value in the next 4 bytes.
/// most significant bits.
/// </summary> /// </summary>
/// <param name="writer">The writer to write to</param> /// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</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 > (1 << 29) - 1)
if (value > BitPackedULongMax)
{ {
throw new ArgumentException("BitPacked ulongs must be <= 61 bits"); if (!writer.TryBeginWriteInternal(5))
{
throw new OverflowException("Writing past the end of the buffer");
} }
#endif writer.WriteByte(5);
writer.WriteValue(value);
return;
}
value <<= 3; value <<= 3;
var numBytes = BitCounter.GetUsedByteCount(value); var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes)) if (!writer.TryBeginWriteInternal(numBytes))
{ {
throw new OverflowException("Writing past the end of the buffer"); 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 #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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint ToUint<T>(T value) where T : unmanaged private static unsafe uint ToUint<T>(T value) where T : unmanaged
{ {

View File

@@ -11,7 +11,6 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public static class ByteUnpacker public static class ByteUnpacker
{ {
#if UNITY_NETCODE_DEBUG_NO_PACKING #if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -58,7 +57,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out float value) public static void ReadValuePacked(FastBufferReader reader, out float value)
{ {
ReadUInt32Packed(reader, out uint asUInt); ReadValueBitPacked(reader, out uint asUInt);
value = ToSingle(asUInt); value = ToSingle(asUInt);
} }
@@ -70,7 +69,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out double value) public static void ReadValuePacked(FastBufferReader reader, out double value)
{ {
ReadUInt64Packed(reader, out ulong asULong); ReadValueBitPacked(reader, out ulong asULong);
value = ToDouble(asULong); value = ToDouble(asULong);
} }
@@ -109,11 +108,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param> /// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param> /// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out short value) public static void ReadValuePacked(FastBufferReader reader, out short value) => ReadValueBitPacked(reader, out value);
{
ReadUInt32Packed(reader, out uint readValue);
value = (short)Arithmetic.ZigZagDecode(readValue);
}
/// <summary> /// <summary>
/// Read an unsigned short (UInt16) as a varint from the stream. /// 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="reader">The reader to read from</param>
/// <param name="value">Value to read</param> /// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out ushort value) public static void ReadValuePacked(FastBufferReader reader, out ushort value) => ReadValueBitPacked(reader, out value);
{
ReadUInt32Packed(reader, out uint readValue);
value = (ushort)readValue;
}
/// <summary> /// <summary>
/// Read a two-byte character as a varint from the stream. /// Read a two-byte character as a varint from the stream.
@@ -135,7 +126,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out char c) public static void ReadValuePacked(FastBufferReader reader, out char c)
{ {
ReadUInt32Packed(reader, out uint readValue); ReadValueBitPacked(reader, out ushort readValue);
c = (char)readValue; c = (char)readValue;
} }
@@ -145,11 +136,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param> /// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param> /// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out int value) public static void ReadValuePacked(FastBufferReader reader, out int value) => ReadValueBitPacked(reader, out value);
{
ReadUInt32Packed(reader, out uint readValue);
value = (int)Arithmetic.ZigZagDecode(readValue);
}
/// <summary> /// <summary>
/// Read an unsigned int (UInt32) from the stream. /// 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="reader">The reader to read from</param>
/// <param name="value">Value to read</param> /// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Read an unsigned long (UInt64) from the stream. /// 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="reader">The reader to read from</param>
/// <param name="value">Value to read</param> /// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <summary>
/// Read a signed long (Int64) as a ZigZag encoded varint from the stream. /// Read a signed long (Int64) as a ZigZag encoded varint from the stream.
@@ -175,8 +162,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out long value) public static void ReadValuePacked(FastBufferReader reader, out long value)
{ {
ReadUInt64Packed(reader, out ulong readValue); ReadValueBitPacked(reader, out value);
value = Arithmetic.ZigZagDecode(readValue);
} }
/// <summary> /// <summary>
@@ -341,7 +327,9 @@ namespace Unity.Netcode
ushort returnValue = 0; ushort returnValue = 0;
byte* ptr = ((byte*)&returnValue); byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition(); 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)) if (!reader.TryBeginReadInternal(numBytes))
{ {
throw new OverflowException("Reading past the end of the buffer"); throw new OverflowException("Reading past the end of the buffer");
@@ -350,17 +338,23 @@ namespace Unity.Netcode
switch (numBytes) switch (numBytes)
{ {
case 1: case 1:
*ptr = *data; ptr[0] = data[0];
break; break;
case 2: case 2:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
break; 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: default:
throw new InvalidOperationException("Could not read bit-packed value: impossible byte count"); throw new InvalidOperationException("Could not read bit-packed value: impossible byte count");
} }
value = (ushort)(returnValue >> 1); value = (ushort)(returnValue >> 2);
} }
/// <summary> /// <summary>
@@ -386,7 +380,8 @@ namespace Unity.Netcode
uint returnValue = 0; uint returnValue = 0;
byte* ptr = ((byte*)&returnValue); byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition(); 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)) if (!reader.TryBeginReadInternal(numBytes))
{ {
throw new OverflowException("Reading past the end of the buffer"); throw new OverflowException("Reading past the end of the buffer");
@@ -395,26 +390,34 @@ namespace Unity.Netcode
switch (numBytes) switch (numBytes)
{ {
case 1: case 1:
*ptr = *data; ptr[0] = data[0];
break; break;
case 2: case 2:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
break; break;
case 3: case 3:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
break; break;
case 4: case 4:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
*(ptr + 3) = *(data + 3); ptr[3] = data[3];
break; 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> /// <summary>
@@ -440,7 +443,8 @@ namespace Unity.Netcode
ulong returnValue = 0; ulong returnValue = 0;
byte* ptr = ((byte*)&returnValue); byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition(); 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)) if (!reader.TryBeginReadInternal(numBytes))
{ {
throw new OverflowException("Reading past the end of the buffer"); throw new OverflowException("Reading past the end of the buffer");
@@ -449,109 +453,74 @@ namespace Unity.Netcode
switch (numBytes) switch (numBytes)
{ {
case 1: case 1:
*ptr = *data; ptr[0] = data[0];
break; break;
case 2: case 2:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
break; break;
case 3: case 3:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
break; break;
case 4: case 4:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
*(ptr + 3) = *(data + 3); ptr[3] = data[3];
break; break;
case 5: case 5:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
*(ptr + 3) = *(data + 3); ptr[3] = data[3];
*(ptr + 4) = *(data + 4); ptr[4] = data[4];
break; break;
case 6: case 6:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
*(ptr + 3) = *(data + 3); ptr[3] = data[3];
*(ptr + 4) = *(data + 4); ptr[4] = data[4];
*(ptr + 5) = *(data + 5); ptr[5] = data[5];
break; break;
case 7: case 7:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
*(ptr + 3) = *(data + 3); ptr[3] = data[3];
*(ptr + 4) = *(data + 4); ptr[4] = data[4];
*(ptr + 5) = *(data + 5); ptr[5] = data[5];
*(ptr + 6) = *(data + 6); ptr[6] = data[6];
break; break;
case 8: case 8:
*ptr = *data; ptr[0] = data[0];
*(ptr + 1) = *(data + 1); ptr[1] = data[1];
*(ptr + 2) = *(data + 2); ptr[2] = data[2];
*(ptr + 3) = *(data + 3); ptr[3] = data[3];
*(ptr + 4) = *(data + 4); ptr[4] = data[4];
*(ptr + 5) = *(data + 5); ptr[5] = data[5];
*(ptr + 6) = *(data + 6); ptr[6] = data[6];
*(ptr + 7) = *(data + 7); ptr[7] = data[7];
break; 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 #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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe float ToSingle<T>(T value) where T : unmanaged private static unsafe float ToSingle<T>(T value) where T : unmanaged

View 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));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25bb0dd7157c423b8cfe0ecf06e15ae5
timeCreated: 1666711082

View File

@@ -65,7 +65,7 @@ namespace Unity.Netcode
ReaderHandle* readerHandle = null; ReaderHandle* readerHandle = null;
if (copyAllocator == Allocator.None) 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->BufferPointer = buffer;
readerHandle->Position = offset; readerHandle->Position = offset;
} }
@@ -1299,8 +1299,10 @@ namespace Unity.Netcode
where T : unmanaged, INativeList<byte>, IUTF8Bytes where T : unmanaged, INativeList<byte>, IUTF8Bytes
{ {
ReadUnmanaged(out int length); ReadUnmanaged(out int length);
value = new T(); value = new T
value.Length = length; {
Length = length
};
ReadBytes(value.GetUnsafePtr(), length); ReadBytes(value.GetUnsafePtr(), length);
} }
@@ -1319,8 +1321,10 @@ namespace Unity.Netcode
where T : unmanaged, INativeList<byte>, IUTF8Bytes where T : unmanaged, INativeList<byte>, IUTF8Bytes
{ {
ReadUnmanagedSafe(out int length); ReadUnmanagedSafe(out int length);
value = new T(); value = new T
value.Length = length; {
Length = length
};
ReadBytesSafe(value.GetUnsafePtr(), length); ReadBytesSafe(value.GetUnsafePtr(), length);
} }

View File

@@ -54,13 +54,7 @@ namespace Unity.Netcode
throw new ArgumentNullException(nameof(gameObject)); throw new ArgumentNullException(nameof(gameObject));
} }
var networkObject = gameObject.GetComponent<NetworkObject>(); var networkObject = gameObject.GetComponent<NetworkObject>() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
if (networkObject == null)
{
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
}
if (networkObject.IsSpawned == false) if (networkObject.IsSpawned == false)
{ {
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s."); throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
@@ -90,7 +84,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null) private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
{ {
networkManager = networkManager != null ? networkManager : NetworkManager.Singleton; networkManager = networkManager ?? NetworkManager.Singleton;
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject); networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);
return networkObject; return networkObject;

View File

@@ -154,7 +154,7 @@ namespace Unity.Netcode
internal ulong GetNetworkObjectId() internal ulong GetNetworkObjectId()
{ {
if (ReleasedNetworkObjectIds.Count > 0 && NetworkManager.NetworkConfig.RecycleNetworkIds && (Time.unscaledTime - ReleasedNetworkObjectIds.Peek().ReleaseTime) >= NetworkManager.NetworkConfig.NetworkIdRecycleDelay) if (ReleasedNetworkObjectIds.Count > 0 && NetworkManager.NetworkConfig.RecycleNetworkIds && (NetworkManager.RealTimeProvider.UnscaledTime - ReleasedNetworkObjectIds.Peek().ReleaseTime) >= NetworkManager.NetworkConfig.NetworkIdRecycleDelay)
{ {
return ReleasedNetworkObjectIds.Dequeue().NetworkId; return ReleasedNetworkObjectIds.Dequeue().NetworkId;
} }
@@ -270,6 +270,9 @@ namespace Unity.Netcode
networkObject.OwnerClientId = clientId; networkObject.OwnerClientId = clientId;
networkObject.MarkVariablesDirty(true);
NetworkManager.BehaviourUpdater.AddForUpdate(networkObject);
// Server adds entries for all client ownership // Server adds entries for all client ownership
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId); UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
@@ -291,13 +294,13 @@ namespace Unity.Netcode
internal bool HasPrefab(NetworkObject.SceneObject sceneObject) 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; 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) switch (networkPrefab.Override)
{ {
@@ -312,7 +315,7 @@ namespace Unity.Netcode
return false; return false;
} }
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle); var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Hash, sceneObject.NetworkSceneHandle);
return networkObject != null; return networkObject != null;
} }
@@ -326,22 +329,22 @@ namespace Unity.Netcode
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject) internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
{ {
NetworkObject networkObject = null; NetworkObject networkObject = null;
var globalObjectIdHash = sceneObject.Header.Hash; var globalObjectIdHash = sceneObject.Hash;
var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default; var position = sceneObject.HasTransform ? sceneObject.Transform.Position : default;
var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default; var rotation = sceneObject.HasTransform ? sceneObject.Transform.Rotation : default;
var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default; var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default;
var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default; var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default;
var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true; var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays;
var isSpawnedByPrefabHandler = false; var isSpawnedByPrefabHandler = false;
// If scene management is disabled or the NetworkObject was dynamically spawned // If scene management is disabled or the NetworkObject was dynamically spawned
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject) if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
{ {
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash)) if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
{ {
// Let the handler spawn the NetworkObject // Let the handler spawn the NetworkObject
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation); networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation);
networkObject.NetworkManagerOwner = NetworkManager; networkObject.NetworkManagerOwner = NetworkManager;
isSpawnedByPrefabHandler = true; isSpawnedByPrefabHandler = true;
} }
@@ -349,17 +352,17 @@ namespace Unity.Netcode
{ {
// See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash // See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash
GameObject networkPrefabReference = null; 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: default:
case NetworkPrefabOverride.None: case NetworkPrefabOverride.None:
networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].Prefab; networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].Prefab;
break; break;
case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Hash:
case NetworkPrefabOverride.Prefab: case NetworkPrefabOverride.Prefab:
networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab; networkPrefabReference = NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[globalObjectIdHash].OverridingTargetPrefab;
break; break;
} }
} }
@@ -402,12 +405,15 @@ namespace Unity.Netcode
if (networkObject != null) if (networkObject != null)
{ {
// SPECIAL CASE: networkObject.DestroyWithScene = sceneObject.DestroyWithScene;
networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle;
// 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 // 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 // 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. // 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. // Under this scenario, we want to remove the parent before spawning and setting the transform values.
if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null) 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 // 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 // include parenting, then we need to force the removal of that parent
@@ -421,9 +427,11 @@ namespace Unity.Netcode
// Set the transform unless we were spawned by a prefab handler // Set the transform unless we were spawned by a prefab handler
// Note: prefab handlers are provided the position and rotation // Note: prefab handlers are provided the position and rotation
// but it is up to the user to set those values // but it is up to the user to set those values
if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler) if (sceneObject.HasTransform && !isSpawnedByPrefabHandler)
{ {
if (worldPositionStays) // 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.position = position;
networkObject.transform.rotation = rotation; networkObject.transform.rotation = rotation;
@@ -441,22 +449,31 @@ namespace Unity.Netcode
// the network prefab used to represent the player. // the network prefab used to represent the player.
// Note: not doing this would set the player's scale to zero since // Note: not doing this would set the player's scale to zero since
// that is the default value of Vector3. // that is the default value of Vector3.
if (!sceneObject.Header.IsPlayerObject) 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; networkObject.transform.localScale = scale;
} }
} }
if (sceneObject.Header.HasParent) if (sceneObject.HasParent)
{ {
// Go ahead and set network parenting properties // Go ahead and set network parenting properties, if the latest parent is not set then pass in null
networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays); // (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 // 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. // until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{ {
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
} }
@@ -490,8 +507,7 @@ namespace Unity.Netcode
} }
// Ran on both server and client // Ran on both server and client
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene)
FastBufferReader variableData, bool destroyWithScene)
{ {
if (networkObject == null) if (networkObject == null)
{ {
@@ -503,9 +519,7 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is already spawned"); throw new SpawnStateException("Object is already spawned");
} }
networkObject.SetNetworkVariableData(variableData); SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene);
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
} }
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
@@ -592,8 +606,19 @@ namespace Unity.Netcode
var children = networkObject.GetComponentsInChildren<NetworkObject>(); var children = networkObject.GetComponentsInChildren<NetworkObject>();
foreach (var childObject in children) 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; childObject.IsSceneObject = sceneObject;
} }
// Only dynamically spawned NetworkObjects are allowed
if (!sceneObject)
{
networkObject.SubscribeToActiveSceneForSynch();
}
} }
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
@@ -647,7 +672,11 @@ namespace Unity.Netcode
// Makes scene objects ready to be reused // Makes scene objects ready to be reused
internal void ServerResetShudownStateForSceneObjects() 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); var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
#endif
foreach (var sobj in networkObjects) foreach (var sobj in networkObjects)
{ {
sobj.IsSpawned = false; sobj.IsSpawned = false;
@@ -678,7 +707,11 @@ namespace Unity.Netcode
internal void DespawnAndDestroyNetworkObjects() internal void DespawnAndDestroyNetworkObjects()
{ {
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(); var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
for (int i = 0; i < networkObjects.Length; i++) for (int i = 0; i < networkObjects.Length; i++)
{ {
@@ -708,7 +741,11 @@ namespace Unity.Netcode
internal void DestroySceneObjects() internal void DestroySceneObjects()
{ {
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(); var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
for (int i = 0; i < networkObjects.Length; i++) for (int i = 0; i < networkObjects.Length; i++)
{ {
@@ -735,7 +772,11 @@ namespace Unity.Netcode
internal void ServerSpawnSceneObjectsOnStartSweep() internal void ServerSpawnSceneObjectsOnStartSweep()
{ {
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(); var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
var networkObjectsToSpawn = new List<NetworkObject>(); var networkObjectsToSpawn = new List<NetworkObject>();
for (int i = 0; i < networkObjects.Length; i++) for (int i = 0; i < networkObjects.Length; i++)
@@ -816,7 +857,7 @@ namespace Unity.Netcode
ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId() ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId()
{ {
NetworkId = networkObject.NetworkObjectId, NetworkId = networkObject.NetworkObjectId,
ReleaseTime = Time.unscaledTime ReleaseTime = NetworkManager.RealTimeProvider.UnscaledTime
}); });
} }

View File

@@ -0,0 +1,10 @@
namespace Unity.Netcode
{
internal interface IRealTimeProvider
{
float RealTimeSinceStartup { get; }
float UnscaledTime { get; }
float UnscaledDeltaTime { get; }
float DeltaTime { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73bdda41e36846e893fd14dbd6de9978
timeCreated: 1679413210

View File

@@ -0,0 +1,12 @@
using UnityEngine;
namespace Unity.Netcode
{
internal class RealTimeProvider : IRealTimeProvider
{
public float RealTimeSinceStartup => Time.realtimeSinceStartup;
public float UnscaledTime => Time.unscaledTime;
public float UnscaledDeltaTime => Time.unscaledDeltaTime;
public float DeltaTime => Time.deltaTime;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5aa5470767d64d8e89ac69ff52a8c404
timeCreated: 1679413182

View File

@@ -149,7 +149,7 @@ namespace Unity.Netcode.Transports.UNET
var eventType = UnityEngine.Networking.NetworkTransport.Receive(out int hostId, out int connectionId, out _, m_MessageBuffer, m_MessageBuffer.Length, out int receivedSize, out byte error); var eventType = UnityEngine.Networking.NetworkTransport.Receive(out int hostId, out int connectionId, out _, m_MessageBuffer, m_MessageBuffer.Length, out int receivedSize, out byte error);
clientId = GetNetcodeClientId((byte)hostId, (ushort)connectionId, false); clientId = GetNetcodeClientId((byte)hostId, (ushort)connectionId, false);
receiveTime = Time.realtimeSinceStartup; receiveTime = NetworkManager.RealTimeProvider.RealTimeSinceStartup;
var networkError = (NetworkError)error; var networkError = (NetworkError)error;
if (networkError == NetworkError.MessageToLong) if (networkError == NetworkError.MessageToLong)
@@ -214,7 +214,7 @@ namespace Unity.Netcode.Transports.UNET
{ {
GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId); GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId);
UnityEngine.Networking.NetworkTransport.Disconnect((int)hostId, (int)connectionId, out byte error); UnityEngine.Networking.NetworkTransport.Disconnect(hostId, connectionId, out byte error);
} }
public override void DisconnectLocalClient() public override void DisconnectLocalClient()
@@ -226,7 +226,7 @@ namespace Unity.Netcode.Transports.UNET
{ {
GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId); GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId);
return (ulong)UnityEngine.Networking.NetworkTransport.GetCurrentRTT((int)hostId, (int)connectionId, out byte error); return (ulong)UnityEngine.Networking.NetworkTransport.GetCurrentRTT(hostId, connectionId, out byte error);
} }
public override void Shutdown() public override void Shutdown()

View File

@@ -226,7 +226,7 @@ namespace Unity.Netcode.Transports.UTP
{ {
writer.WriteInt(messageLength); writer.WriteInt(messageLength);
var messageOffset = HeadIndex + reader.GetBytesRead(); var messageOffset = reader.GetBytesRead();
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
writerAvailable -= sizeof(int) + messageLength; writerAvailable -= sizeof(int) + messageLength;

View File

@@ -10,8 +10,10 @@ using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent; using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent;
using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent; using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using Unity.Collections; using Unity.Collections;
using Unity.Jobs;
using Unity.Networking.Transport; using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay; using Unity.Networking.Transport.Relay;
using Unity.Networking.Transport.Utilities; using Unity.Networking.Transport.Utilities;
@@ -51,50 +53,48 @@ namespace Unity.Netcode.Transports.UTP
/// </summary> /// </summary>
public static class ErrorUtilities public static class ErrorUtilities
{ {
private const string k_NetworkSuccess = "Success"; private static readonly FixedString128Bytes k_NetworkSuccess = "Success";
private const string k_NetworkIdMismatch = "NetworkId is invalid, likely caused by stale connection {0}."; private static readonly FixedString128Bytes k_NetworkIdMismatch = "Invalid connection ID {0}.";
private const string k_NetworkVersionMismatch = "NetworkVersion is invalid, likely caused by stale connection {0}."; private static readonly FixedString128Bytes k_NetworkVersionMismatch = "Connection ID is invalid. Likely caused by sending on stale connection {0}.";
private const string k_NetworkStateMismatch = "Sending data while connecting on connection {0} is not allowed."; private static readonly FixedString128Bytes k_NetworkStateMismatch = "Connection state is invalid. Likely caused by sending on connection {0} which is stale or still connecting.";
private const string k_NetworkPacketOverflow = "Unable to allocate packet due to buffer overflow."; private static readonly FixedString128Bytes k_NetworkPacketOverflow = "Packet is too large to be allocated by the transport.";
private const string k_NetworkSendQueueFull = "Currently unable to queue packet as there is too many in-flight packets. This could be because the send queue size ('Max Send Queue Size') is too small."; private static readonly FixedString128Bytes k_NetworkSendQueueFull = "Unable to queue packet in the transport. Likely caused by send queue size ('Max Send Queue Size') being too small.";
private const string k_NetworkHeaderInvalid = "Invalid Unity Transport Protocol header.";
private const string k_NetworkDriverParallelForErr = "The parallel network driver needs to process a single unique connection per job, processing a single connection multiple times in a parallel for is not supported.";
private const string k_NetworkSendHandleInvalid = "Invalid NetworkInterface Send Handle. Likely caused by pipeline send data corruption.";
private const string k_NetworkArgumentMismatch = "Invalid NetworkEndpoint Arguments.";
/// <summary> /// <summary>
/// Convert error code to human readable error message. /// Convert a UTP error code to human-readable error message.
/// </summary> /// </summary>
/// <param name="error">Status code of the error</param> /// <param name="error">UTP error code.</param>
/// <param name="connectionId">Subject connection ID of the error</param> /// <param name="connectionId">ID of the connection on which the error occurred.</param>
/// <returns>Human readable error message.</returns> /// <returns>Human-readable error message.</returns>
public static string ErrorToString(Networking.Transport.Error.StatusCode error, ulong connectionId) public static string ErrorToString(Networking.Transport.Error.StatusCode error, ulong connectionId)
{ {
switch (error) return ErrorToString((int)error, connectionId);
}
internal static string ErrorToString(int error, ulong connectionId)
{
return ErrorToFixedString(error, connectionId).ToString();
}
internal static FixedString128Bytes ErrorToFixedString(int error, ulong connectionId)
{
switch ((Networking.Transport.Error.StatusCode)error)
{ {
case Networking.Transport.Error.StatusCode.Success: case Networking.Transport.Error.StatusCode.Success:
return k_NetworkSuccess; return k_NetworkSuccess;
case Networking.Transport.Error.StatusCode.NetworkIdMismatch: case Networking.Transport.Error.StatusCode.NetworkIdMismatch:
return string.Format(k_NetworkIdMismatch, connectionId); return FixedString.Format(k_NetworkIdMismatch, connectionId);
case Networking.Transport.Error.StatusCode.NetworkVersionMismatch: case Networking.Transport.Error.StatusCode.NetworkVersionMismatch:
return string.Format(k_NetworkVersionMismatch, connectionId); return FixedString.Format(k_NetworkVersionMismatch, connectionId);
case Networking.Transport.Error.StatusCode.NetworkStateMismatch: case Networking.Transport.Error.StatusCode.NetworkStateMismatch:
return string.Format(k_NetworkStateMismatch, connectionId); return FixedString.Format(k_NetworkStateMismatch, connectionId);
case Networking.Transport.Error.StatusCode.NetworkPacketOverflow: case Networking.Transport.Error.StatusCode.NetworkPacketOverflow:
return k_NetworkPacketOverflow; return k_NetworkPacketOverflow;
case Networking.Transport.Error.StatusCode.NetworkSendQueueFull: case Networking.Transport.Error.StatusCode.NetworkSendQueueFull:
return k_NetworkSendQueueFull; return k_NetworkSendQueueFull;
case Networking.Transport.Error.StatusCode.NetworkHeaderInvalid: default:
return k_NetworkHeaderInvalid; return FixedString.Format("Unknown error code {0}.", error);
case Networking.Transport.Error.StatusCode.NetworkDriverParallelForErr:
return k_NetworkDriverParallelForErr;
case Networking.Transport.Error.StatusCode.NetworkSendHandleInvalid:
return k_NetworkSendHandleInvalid;
case Networking.Transport.Error.StatusCode.NetworkArgumentMismatch:
return k_NetworkArgumentMismatch;
} }
return $"Unknown ErrorCode {Enum.GetName(typeof(Networking.Transport.Error.StatusCode), error)}";
} }
} }
@@ -145,7 +145,7 @@ namespace Unity.Netcode.Transports.UTP
// Maximum reliable throughput, assuming the full reliable window can be sent on every // Maximum reliable throughput, assuming the full reliable window can be sent on every
// frame at 60 FPS. This will be a large over-estimation in any realistic scenario. // frame at 60 FPS. This will be a large over-estimation in any realistic scenario.
private const int k_MaxReliableThroughput = (NetworkParameterConstants.MTU * 32 * 60) / 1000; // bytes per millisecond private const int k_MaxReliableThroughput = (NetworkParameterConstants.MTU * 64 * 60) / 1000; // bytes per millisecond
private static ConnectionAddressData s_DefaultConnectionAddressData = new ConnectionAddressData { Address = "127.0.0.1", Port = 7777, ServerListenAddress = string.Empty }; private static ConnectionAddressData s_DefaultConnectionAddressData = new ConnectionAddressData { Address = "127.0.0.1", Port = 7777, ServerListenAddress = string.Empty };
@@ -303,21 +303,24 @@ namespace Unity.Netcode.Transports.UTP
public ushort Port; public ushort Port;
/// <summary> /// <summary>
/// IP address the server will listen on. If not provided, will use 'Address'. /// IP address the server will listen on. If not provided, will use localhost.
/// </summary> /// </summary>
[Tooltip("IP address the server will listen on. If not provided, will use 'Address'.")] [Tooltip("IP address the server will listen on. If not provided, will use localhost.")]
[SerializeField] [SerializeField]
public string ServerListenAddress; public string ServerListenAddress;
private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port) private static NetworkEndpoint ParseNetworkEndpoint(string ip, ushort port, bool silent = false)
{ {
NetworkEndpoint endpoint = default; NetworkEndpoint endpoint = default;
if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) && if (!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv4) &&
!NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6)) !NetworkEndpoint.TryParse(ip, port, out endpoint, NetworkFamily.Ipv6))
{
if (!silent)
{ {
Debug.LogError($"Invalid network endpoint: {ip}:{port}."); Debug.LogError($"Invalid network endpoint: {ip}:{port}.");
} }
}
return endpoint; return endpoint;
} }
@@ -330,9 +333,34 @@ namespace Unity.Netcode.Transports.UTP
/// <summary> /// <summary>
/// Endpoint (IP address and port) server will listen/bind on. /// Endpoint (IP address and port) server will listen/bind on.
/// </summary> /// </summary>
public NetworkEndpoint ListenEndPoint => ParseNetworkEndpoint((ServerListenAddress?.Length == 0) ? Address : ServerListenAddress, Port); public NetworkEndpoint ListenEndPoint
{
get
{
if (string.IsNullOrEmpty(ServerListenAddress))
{
var ep = NetworkEndpoint.LoopbackIpv4;
// If an address was entered and it's IPv6, switch to using ::1 as the
// default listen address. (Otherwise we always assume IPv4.)
if (!string.IsNullOrEmpty(Address) && ServerEndPoint.Family == NetworkFamily.Ipv6)
{
ep = NetworkEndpoint.LoopbackIpv6;
} }
return ep.WithPort(Port);
}
else
{
return ParseNetworkEndpoint(ServerListenAddress, Port);
}
}
}
public bool IsIpv6 => !string.IsNullOrEmpty(Address) && ParseNetworkEndpoint(Address, Port, true).Family == NetworkFamily.Ipv6;
}
/// <summary> /// <summary>
/// The connection (address) data for this <see cref="UnityTransport"/> instance. /// The connection (address) data for this <see cref="UnityTransport"/> instance.
/// This is where you can change IP Address, Port, or server's listen address. /// This is where you can change IP Address, Port, or server's listen address.
@@ -422,6 +450,8 @@ namespace Unity.Netcode.Transports.UTP
internal NetworkManager NetworkManager; internal NetworkManager NetworkManager;
private IRealTimeProvider m_RealTimeProvider;
/// <summary> /// <summary>
/// SendQueue dictionary is used to batch events instead of sending them immediately. /// SendQueue dictionary is used to batch events instead of sending them immediately.
/// </summary> /// </summary>
@@ -529,14 +559,14 @@ namespace Unity.Netcode.Transports.UTP
int result = m_Driver.Bind(endPoint); int result = m_Driver.Bind(endPoint);
if (result != 0) if (result != 0)
{ {
Debug.LogError("Server failed to bind"); Debug.LogError("Server failed to bind. This is usually caused by another process being bound to the same port.");
return false; return false;
} }
result = m_Driver.Listen(); result = m_Driver.Listen();
if (result != 0) if (result != 0)
{ {
Debug.LogError("Server failed to listen"); Debug.LogError("Server failed to listen.");
return false; return false;
} }
@@ -609,7 +639,7 @@ namespace Unity.Netcode.Transports.UTP
{ {
Address = ipv4Address, Address = ipv4Address,
Port = port, Port = port,
ServerListenAddress = listenAddress ?? string.Empty ServerListenAddress = listenAddress ?? ipv4Address
}; };
SetProtocol(ProtocolType.UnityTransport); SetProtocol(ProtocolType.UnityTransport);
@@ -676,20 +706,26 @@ namespace Unity.Netcode.Transports.UTP
} }
} }
// Send as many batched messages from the queue as possible. [BurstCompile]
private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue) private struct SendBatchedMessagesJob : IJob
{ {
var clientId = sendTarget.ClientId; public NetworkDriver.Concurrent Driver;
var connection = ParseClientId(clientId); public SendTarget Target;
var pipeline = sendTarget.NetworkPipeline; public BatchedSendQueue Queue;
public NetworkPipeline ReliablePipeline;
while (!queue.IsEmpty) public void Execute()
{ {
var result = m_Driver.BeginSend(pipeline, connection, out var writer); var clientId = Target.ClientId;
var connection = ParseClientId(clientId);
var pipeline = Target.NetworkPipeline;
while (!Queue.IsEmpty)
{
var result = Driver.BeginSend(pipeline, connection, out var writer);
if (result != (int)Networking.Transport.Error.StatusCode.Success) if (result != (int)Networking.Transport.Error.StatusCode.Success)
{ {
Debug.LogError("Error sending the message: " + Debug.LogError($"Error sending message: {ErrorUtilities.ErrorToFixedString(result, clientId)}");
ErrorUtilities.ErrorToString((Networking.Transport.Error.StatusCode)result, clientId));
return; return;
} }
@@ -699,31 +735,48 @@ namespace Unity.Netcode.Transports.UTP
// in the stream (the send queue does that automatically) we are sure they'll be // in the stream (the send queue does that automatically) we are sure they'll be
// reassembled properly at the other end. This allows us to lift the limit of ~44KB // reassembled properly at the other end. This allows us to lift the limit of ~44KB
// on reliable payloads (because of the reliable window size). // on reliable payloads (because of the reliable window size).
var written = pipeline == m_ReliableSequencedPipeline ? queue.FillWriterWithBytes(ref writer) : queue.FillWriterWithMessages(ref writer); var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer) : Queue.FillWriterWithMessages(ref writer);
result = m_Driver.EndSend(writer); result = Driver.EndSend(writer);
if (result == written) if (result == written)
{ {
// Batched message was sent successfully. Remove it from the queue. // Batched message was sent successfully. Remove it from the queue.
queue.Consume(written); Queue.Consume(written);
} }
else else
{ {
// Some error occured. If it's just the UTP queue being full, then don't log // Some error occured. If it's just the UTP queue being full, then don't log
// anything since that's okay (the unsent message(s) are still in the queue // anything since that's okay (the unsent message(s) are still in the queue
// and we'll retry sending the later). Otherwise log the error and remove the // and we'll retry sending them later). Otherwise log the error and remove the
// message from the queue (we don't want to resend it again since we'll likely // message from the queue (we don't want to resend it again since we'll likely
// just get the same error again). // just get the same error again).
if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull) if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull)
{ {
Debug.LogError("Error sending the message: " + ErrorUtilities.ErrorToString((Networking.Transport.Error.StatusCode)result, clientId)); Debug.LogError($"Error sending the message: {ErrorUtilities.ErrorToFixedString(result, clientId)}");
queue.Consume(written); Queue.Consume(written);
} }
return; return;
} }
} }
} }
}
// Send as many batched messages from the queue as possible.
private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue)
{
if (!m_Driver.IsCreated)
{
return;
}
new SendBatchedMessagesJob
{
Driver = m_Driver.ToConcurrent(),
Target = sendTarget,
Queue = queue,
ReliablePipeline = m_ReliableSequencedPipeline
}.Run();
}
private bool AcceptConnection() private bool AcceptConnection()
{ {
@@ -737,7 +790,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect, InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
ParseClientId(connection), ParseClientId(connection),
default, default,
Time.realtimeSinceStartup); m_RealTimeProvider.RealTimeSinceStartup);
return true; return true;
@@ -772,7 +825,7 @@ namespace Unity.Netcode.Transports.UTP
break; break;
} }
InvokeOnTransportEvent(NetcodeNetworkEvent.Data, clientId, message, Time.realtimeSinceStartup); InvokeOnTransportEvent(NetcodeNetworkEvent.Data, clientId, message, m_RealTimeProvider.RealTimeSinceStartup);
} }
} }
@@ -788,7 +841,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect, InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
clientId, clientId,
default, default,
Time.realtimeSinceStartup); m_RealTimeProvider.RealTimeSinceStartup);
m_State = State.Connected; m_State = State.Connected;
return true; return true;
@@ -816,7 +869,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
clientId, clientId,
default, default,
Time.realtimeSinceStartup); m_RealTimeProvider.RealTimeSinceStartup);
return true; return true;
} }
@@ -846,7 +899,7 @@ namespace Unity.Netcode.Transports.UTP
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " + Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
"Use NetworkManager.OnTransportFailure to be notified of such events programmatically."); "Use NetworkManager.OnTransportFailure to be notified of such events programmatically.");
InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, Time.realtimeSinceStartup); InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, m_RealTimeProvider.RealTimeSinceStartup);
return; return;
} }
@@ -1069,7 +1122,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
m_ServerClientId, m_ServerClientId,
default, default,
Time.realtimeSinceStartup); m_RealTimeProvider.RealTimeSinceStartup);
} }
} }
} }
@@ -1132,19 +1185,24 @@ namespace Unity.Netcode.Transports.UTP
NetworkManager = networkManager; NetworkManager = networkManager;
m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider();
m_NetworkSettings = new NetworkSettings(Allocator.Persistent); m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
#if !UNITY_WEBGL
// If the user sends a message of exactly m_MaxPayloadSize in length, we need to // If the user sends a message of exactly m_MaxPayloadSize in length, we need to
// account for the overhead of its length when we store it in the send queue. // account for the overhead of its length when we store it in the send queue.
var fragmentationCapacity = m_MaxPayloadSize + BatchedSendQueue.PerMessageOverhead; var fragmentationCapacity = m_MaxPayloadSize + BatchedSendQueue.PerMessageOverhead;
m_NetworkSettings.WithFragmentationStageParameters(payloadCapacity: fragmentationCapacity); m_NetworkSettings.WithFragmentationStageParameters(payloadCapacity: fragmentationCapacity);
#if !UTP_TRANSPORT_2_0_ABOVE
// Bump the reliable window size to its maximum size of 64. Since NGO makes heavy use of
// reliable delivery, we're better off with the increased window size compared to the
// extra 4 bytes of header that this costs us.
m_NetworkSettings.WithReliableStageParameters(windowSize: 64);
#if !UTP_TRANSPORT_2_0_ABOVE && !UNITY_WEBGL
m_NetworkSettings.WithBaselibNetworkInterfaceParameters( m_NetworkSettings.WithBaselibNetworkInterfaceParameters(
receiveQueueCapacity: m_MaxPacketQueueSize, receiveQueueCapacity: m_MaxPacketQueueSize,
sendQueueCapacity: m_MaxPacketQueueSize); sendQueueCapacity: m_MaxPacketQueueSize);
#endif
#endif #endif
} }
@@ -1153,7 +1211,7 @@ namespace Unity.Netcode.Transports.UTP
/// </summary> /// </summary>
/// <param name="clientId">The clientId this event is for</param> /// <param name="clientId">The clientId this event is for</param>
/// <param name="payload">The incoming data payload</param> /// <param name="payload">The incoming data payload</param>
/// <param name="receiveTime">The time the event was received, as reported by Time.realtimeSinceStartup.</param> /// <param name="receiveTime">The time the event was received, as reported by m_RealTimeProvider.RealTimeSinceStartup.</param>
/// <returns>Returns the event type</returns> /// <returns>Returns the event type</returns>
public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime) public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
{ {
@@ -1226,7 +1284,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect, InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
clientId, clientId,
default(ArraySegment<byte>), default(ArraySegment<byte>),
Time.realtimeSinceStartup); m_RealTimeProvider.RealTimeSinceStartup);
} }
} }
else else
@@ -1374,6 +1432,10 @@ namespace Unity.Netcode.Transports.UTP
private string m_ClientCaCertificate; private string m_ClientCaCertificate;
/// <summary>Set the server parameters for encryption.</summary> /// <summary>Set the server parameters for encryption.</summary>
/// <remarks>
/// The public certificate and private key are expected to be in the PEM format, including
/// the begin/end markers like <c>-----BEGIN CERTIFICATE-----</c>.
/// </remarks>
/// <param name="serverCertificate">Public certificate for the server (PEM format).</param> /// <param name="serverCertificate">Public certificate for the server (PEM format).</param>
/// <param name="serverPrivateKey">Private key for the server (PEM format).</param> /// <param name="serverPrivateKey">Private key for the server (PEM format).</param>
public void SetServerSecrets(string serverCertificate, string serverPrivateKey) public void SetServerSecrets(string serverCertificate, string serverPrivateKey)
@@ -1384,9 +1446,15 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>Set the client parameters for encryption.</summary> /// <summary>Set the client parameters for encryption.</summary>
/// <remarks> /// <remarks>
/// <para>
/// If the CA certificate is not provided, validation will be done against the OS/browser /// If the CA certificate is not provided, validation will be done against the OS/browser
/// certificate store. This is what you'd want if using certificates from a known provider. /// certificate store. This is what you'd want if using certificates from a known provider.
/// For self-signed certificates, the CA certificate needs to be provided. /// For self-signed certificates, the CA certificate needs to be provided.
/// </para>
/// <para>
/// The CA certificate (if provided) is expected to be in the PEM format, including the
/// begin/end markers like <c>-----BEGIN CERTIFICATE-----</c>.
/// </para>
/// </remarks> /// </remarks>
/// <param name="serverCommonName">Common name of the server (typically hostname).</param> /// <param name="serverCommonName">Common name of the server (typically hostname).</param>
/// <param name="caCertificate">CA certificate used to validate the server's authenticity.</param> /// <param name="caCertificate">CA certificate used to validate the server's authenticity.</param>
@@ -1430,7 +1498,7 @@ namespace Unity.Netcode.Transports.UTP
heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS); heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS);
#if UNITY_WEBGL && !UNITY_EDITOR #if UNITY_WEBGL && !UNITY_EDITOR
if (NetworkManager.IsServer) if (NetworkManager.IsServer && m_ProtocolType != ProtocolType.RelayUnityTransport)
{ {
throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor."); throw new Exception("WebGL as a server is not supported by Unity Transport, outside the Editor.");
} }
@@ -1441,7 +1509,7 @@ namespace Unity.Netcode.Transports.UTP
{ {
if (m_ProtocolType == ProtocolType.RelayUnityTransport) if (m_ProtocolType == ProtocolType.RelayUnityTransport)
{ {
if (m_RelayServerData.IsSecure != 0) if (m_RelayServerData.IsSecure == 0)
{ {
// log an error because we have mismatched configuration // log an error because we have mismatched configuration
Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings"); Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings");
@@ -1451,24 +1519,23 @@ namespace Unity.Netcode.Transports.UTP
// configuration of the security parameters on its own. // configuration of the security parameters on its own.
} }
else else
{
try
{ {
if (NetworkManager.IsServer) if (NetworkManager.IsServer)
{ {
if (m_ServerCertificate.Length == 0 || m_ServerPrivateKey.Length == 0) if (string.IsNullOrEmpty(m_ServerCertificate) || string.IsNullOrEmpty(m_ServerPrivateKey))
{ {
throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key."); throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key.");
} }
m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey); m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey);
} }
else else
{ {
if (m_ServerCommonName.Length == 0) if (string.IsNullOrEmpty(m_ServerCommonName))
{ {
throw new Exception("In order to use encrypted communications, clients must set the server common name."); throw new Exception("In order to use encrypted communications, clients must set the server common name.");
} }
else if (m_ClientCaCertificate == null) else if (string.IsNullOrEmpty(m_ClientCaCertificate))
{ {
m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName); m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName);
} }
@@ -1478,11 +1545,6 @@ namespace Unity.Netcode.Transports.UTP
} }
} }
} }
catch(Exception e)
{
Debug.LogException(e, this);
}
}
} }
#endif #endif

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