5 Commits

Author SHA1 Message Date
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
Unity Technologies
1e7078c160 com.unity.netcode.gameobjects@1.1.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.1.0] - 2022-10-21

### Added

- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261)
- `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235)
- IPv6 is now supported for direct connections when using `UnityTransport`. (#2232)
- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201)
- Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146)
- Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.0. (#2231)
- The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212)
- As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212)
- The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196)
- `NetworkVariable<>` now supports managed `INetworkSerializable` types, as well as other managed types with serialization/deserialization delegates registered to `UserNetworkVariableSerialization<T>.WriteValue` and `UserNetworkVariableSerialization<T>.ReadValue` (#2219)
- `NetworkVariable<>` and `BufferSerializer<BufferSerializerReader>` now deserialize `INetworkSerializable` types in-place, rather than constructing new ones. (#2219)

### Fixed

- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261)
- Fixed issue caused when changing ownership of objects hidden to some clients (#2242)
- Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239)
- Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226)
- Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225)
- Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222)
- Fixed issue where `SceneEventProgress` would not complete if a client disconnects. (#2222)
- Fixed issues with detecting if a `SceneEventProgress` has timed out. (#2222)
- Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220)
- Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203)
- Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199)
- Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158)
- Fixed warning resulting from a stray NetworkAnimator.meta file (#2153)
- Fixed Connection Approval Timeout not working client side. (#2164)
- Fixed issue where the `WorldPositionStays` parenting parameter was not being synchronized with clients. (#2146)
- Fixed issue where parented in-scene placed `NetworkObject`s would fail for late joining clients. (#2146)
- Fixed issue where scale was not being synchronized which caused issues with nested parenting and scale when `WorldPositionStays` was true. (#2146)
- Fixed issue with `NetworkTransform.ApplyTransformToNetworkStateWithInfo` where it was not honoring axis sync settings when `NetworkTransformState.IsTeleportingNextFrame` was true. (#2146)
- Fixed issue with `NetworkTransform.TryCommitTransformToServer` where it was not honoring the `InLocalSpace` setting. (#2146)
- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144)
- Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List<int>) and extensions for multiple instantiations of that type have been defined (i.e., List<int> and List<string>) (#2142)
- Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127)
- Fixed issue where late-joining client transition synchronization could fail when more than one transition was occurring.(#2127)
- Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739)
- Fixed synchronization when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independent from the local time scale. (#2171)
- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987)
- Fixed IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership (#2211)
2022-10-21 00:00:00 +00:00
Unity Technologies
a6969670f5 com.unity.netcode.gameobjects@1.0.2
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.0.2] - 2022-09-12

- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170)
- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170)
- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170)
- Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170)
2022-09-12 00:00:00 +00:00
Unity Technologies
e15bd056c5 com.unity.netcode.gameobjects@1.0.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.0.1] - 2022-08-23

### Changed

- Changed version to 1.0.1. (#2131)
- Updated dependency on `com.unity.transport` to 1.2.0. (#2129)
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects

### Fixed

- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130)
- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110)
- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110)
- Fixed issue where the authoritative side was interpolating its transform. (#2110)
- Fixed Owner-written NetworkVariable infinitely write themselves (#2109)
- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099)
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084)
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067)
- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062)
2022-08-23 00:00:00 +00:00
Unity Technologies
18ffd5fdc8 com.unity.netcode.gameobjects@1.0.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.0.0] - 2022-06-27

### Changed

- Changed version to 1.0.0. (#2046)
2022-06-27 00:00:00 +00:00
207 changed files with 15599 additions and 5083 deletions

View File

@@ -6,6 +6,130 @@ 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.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)
## [1.1.0] - 2022-10-21
### Added
- Added `NetworkManager.IsApproved` flag that is set to `true` a client has been approved.(#2261)
- `UnityTransport` now provides a way to set the Relay server data directly from the `RelayServerData` structure (provided by the Unity Transport package) throuh its `SetRelayServerData` method. This allows making use of the new APIs in UTP 1.3 that simplify integration of the Relay SDK. (#2235)
- IPv6 is now supported for direct connections when using `UnityTransport`. (#2232)
- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201)
- Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146)
- Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146)
### Changed
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.0. (#2231)
- The send queues of `UnityTransport` are now dynamically-sized. This means that there shouldn't be any need anymore to tweak the 'Max Send Queue Size' value. In fact, this field is now removed from the inspector and will not be serialized anymore. It is still possible to set it manually using the `MaxSendQueueSize` property, but it is not recommended to do so aside from some specific needs (e.g. limiting the amount of memory used by the send queues in very constrained environments). (#2212)
- As a consequence of the above change, the `UnityTransport.InitialMaxSendQueueSize` field is now deprecated. There is no default value anymore since send queues are dynamically-sized. (#2212)
- The debug simulator in `UnityTransport` is now non-deterministic. Its random number generator used to be seeded with a constant value, leading to the same pattern of packet drops, delays, and jitter in every run. (#2196)
- `NetworkVariable<>` now supports managed `INetworkSerializable` types, as well as other managed types with serialization/deserialization delegates registered to `UserNetworkVariableSerialization<T>.WriteValue` and `UserNetworkVariableSerialization<T>.ReadValue` (#2219)
- `NetworkVariable<>` and `BufferSerializer<BufferSerializerReader>` now deserialize `INetworkSerializable` types in-place, rather than constructing new ones. (#2219)
### Fixed
- Fixed `NetworkManager.ApprovalTimeout` will not timeout due to slower client synchronization times as it now uses the added `NetworkManager.IsApproved` flag to determined if the client has been approved or not.(#2261)
- Fixed issue caused when changing ownership of objects hidden to some clients (#2242)
- Fixed issue where an in-scene placed NetworkObject would not invoke NetworkBehaviour.OnNetworkSpawn if the GameObject was disabled when it was despawned. (#2239)
- Fixed issue where clients were not rebuilding the `NetworkConfig` hash value for each unique connection request. (#2226)
- Fixed the issue where player objects were not taking the `DontDestroyWithOwner` property into consideration when a client disconnected. (#2225)
- Fixed issue where `SceneEventProgress` would not complete if a client late joins while it is still in progress. (#2222)
- Fixed issue where `SceneEventProgress` would not complete if a client disconnects. (#2222)
- Fixed issues with detecting if a `SceneEventProgress` has timed out. (#2222)
- Fixed issue #1924 where `UnityTransport` would fail to restart after a first failure (even if what caused the initial failure was addressed). (#2220)
- Fixed issue where `NetworkTransform.SetStateServerRpc` and `NetworkTransform.SetStateClientRpc` were not honoring local vs world space settings when applying the position and rotation. (#2203)
- Fixed ILPP `TypeLoadException` on WebGL on MacOS Editor and potentially other platforms. (#2199)
- Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158)
- Fixed warning resulting from a stray NetworkAnimator.meta file (#2153)
- Fixed Connection Approval Timeout not working client side. (#2164)
- Fixed issue where the `WorldPositionStays` parenting parameter was not being synchronized with clients. (#2146)
- Fixed issue where parented in-scene placed `NetworkObject`s would fail for late joining clients. (#2146)
- Fixed issue where scale was not being synchronized which caused issues with nested parenting and scale when `WorldPositionStays` was true. (#2146)
- Fixed issue with `NetworkTransform.ApplyTransformToNetworkStateWithInfo` where it was not honoring axis sync settings when `NetworkTransformState.IsTeleportingNextFrame` was true. (#2146)
- Fixed issue with `NetworkTransform.TryCommitTransformToServer` where it was not honoring the `InLocalSpace` setting. (#2146)
- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144)
- Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List<int>) and extensions for multiple instantiations of that type have been defined (i.e., List<int> and List<string>) (#2142)
- Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127)
- Fixed issue where late-joining client transition synchronization could fail when more than one transition was occurring.(#2127)
- Fixed throwing an exception in `OnNetworkUpdate` causing other `OnNetworkUpdate` calls to not be executed. (#1739)
- Fixed synchronization when Time.timeScale is set to 0. This changes timing update to use unscaled deltatime. Now network updates rate are independent from the local time scale. (#2171)
- Fixed not sending all NetworkVariables to all clients when a client connects to a server. (#1987)
- Fixed IsOwner/IsOwnedByServer being wrong on the server after calling RemoveOwnership (#2211)
## [1.0.2] - 2022-09-12
### Fixed
- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170)
- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170)
- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170)
- Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170)
## [1.0.1] - 2022-08-23
### Changed
- Changed version to 1.0.1. (#2131)
- Updated dependency on `com.unity.transport` to 1.2.0. (#2129)
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
- Performance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects
### Fixed
- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130)
- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110)
- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110)
- Fixed issue where the authoritative side was interpolating its transform. (#2110)
- Fixed Owner-written NetworkVariable infinitely write themselves (#2109)
- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099)
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084)
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067)
- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062)
## [1.0.0] - 2022-06-27
### Changed
- Changed version to 1.0.0. (#2046)
## [1.0.0-pre.10] - 2022-06-21 ## [1.0.0-pre.10] - 2022-06-21
### Added ### Added
@@ -18,7 +142,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Changed ### Changed
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025) - Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972) - (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now an `Action<>` taking a `ConnectionApprovalRequest` and a `ConnectionApprovalResponse` that the client code must fill (#1972) (#2002)
### Removed ### Removed
@@ -63,6 +187,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)
@@ -116,10 +241,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)
@@ -171,6 +298,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)
@@ -185,6 +313,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

@@ -8,6 +8,7 @@ namespace Unity.Netcode
/// Solves for incoming values that are jittered /// Solves for incoming values that are jittered
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely /// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
/// </summary> /// </summary>
/// <typeparam name="T">The type of interpolated value</typeparam>
public abstract class BufferedLinearInterpolator<T> where T : struct public abstract class BufferedLinearInterpolator<T> where T : struct
{ {
internal float MaxInterpolationBound = 3.0f; internal float MaxInterpolationBound = 3.0f;
@@ -24,7 +25,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Theres two factors affecting interpolation: buffering (set in NetworkManagers NetworkTimeSystem) and interpolation time, which is the amount of time itll take to reach the target. This is to affect the second one. /// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one.
/// </summary> /// </summary>
public float MaximumInterpolationTime = 0.1f; public float MaximumInterpolationTime = 0.1f;
@@ -73,7 +74,7 @@ namespace Unity.Netcode
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
/// <summary> /// <summary>
/// Resets Interpolator to initial state /// Resets interpolator to initial state
/// </summary> /// </summary>
public void Clear() public void Clear()
{ {
@@ -85,6 +86,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Teleports current interpolation value to targetValue. /// Teleports current interpolation value to targetValue.
/// </summary> /// </summary>
/// <param name="targetValue">The target value to teleport instantly</param>
/// <param name="serverTime">The current server time</param>
public void ResetTo(T targetValue, double serverTime) public void ResetTo(T targetValue, double serverTime)
{ {
m_LifetimeConsumedCount = 1; m_LifetimeConsumedCount = 1;
@@ -159,6 +162,7 @@ namespace Unity.Netcode
/// </summary> /// </summary>
/// <param name="deltaTime">time since call</param> /// <param name="deltaTime">time since call</param>
/// <param name="serverTime">current server time</param> /// <param name="serverTime">current server time</param>
/// <returns>The newly interpolated value of type 'T'</returns>
public T Update(float deltaTime, NetworkTime serverTime) public T Update(float deltaTime, NetworkTime serverTime)
{ {
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
@@ -170,6 +174,7 @@ namespace Unity.Netcode
/// <param name="deltaTime">time since last call</param> /// <param name="deltaTime">time since last call</param>
/// <param name="renderTime">our current time</param> /// <param name="renderTime">our current time</param>
/// <param name="serverTime">current server time</param> /// <param name="serverTime">current server time</param>
/// <returns>The newly interpolated value of type 'T'</returns>
public T Update(float deltaTime, double renderTime, double serverTime) public T Update(float deltaTime, double renderTime, double serverTime)
{ {
TryConsumeFromBuffer(renderTime, serverTime); TryConsumeFromBuffer(renderTime, serverTime);
@@ -222,6 +227,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value". /// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
/// </summary> /// </summary>
/// <param name="newMeasurement">The new measurement value to use</param>
/// <param name="sentTime">The time to record for measurement</param>
public void AddMeasurement(T newMeasurement, double sentTime) public void AddMeasurement(T newMeasurement, double sentTime)
{ {
m_NbItemsReceivedThisFrame++; m_NbItemsReceivedThisFrame++;
@@ -241,6 +248,8 @@ namespace Unity.Netcode
return; return;
} }
// Part the of reason for disabling extrapolation is how we add and use measurements over time.
// TODO: Add detailed description of this area in Jira ticket
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
{ {
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
@@ -251,6 +260,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets latest value from the interpolator. This is updated every update as time goes by. /// Gets latest value from the interpolator. This is updated every update as time goes by.
/// </summary> /// </summary>
/// <returns>The current interpolated value of type 'T'</returns>
public T GetInterpolatedValue() public T GetInterpolatedValue()
{ {
return m_CurrentInterpValue; return m_CurrentInterpValue;
@@ -259,36 +269,63 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped. /// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
/// </summary> /// </summary>
/// <param name="start">The start value (min)</param>
/// <param name="end">The end value (max)</param>
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
/// <returns>The interpolated value</returns>
protected abstract T Interpolate(T start, T end, float time); protected abstract T Interpolate(T start, T end, float time);
/// <summary> /// <summary>
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped. /// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
/// </summary> /// </summary>
/// <param name="start">The start value (min)</param>
/// <param name="end">The end value (max)</param>
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
/// <returns>The interpolated value</returns>
protected abstract T InterpolateUnclamped(T start, T end, float time); protected abstract T InterpolateUnclamped(T start, T end, float time);
} }
/// <inheritdoc />
/// <remarks>
/// This is a buffered linear interpolator for a <see cref="float"/> type value
/// </remarks>
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float> public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
{ {
/// <inheritdoc />
protected override float InterpolateUnclamped(float start, float end, float time) protected override float InterpolateUnclamped(float start, float end, float time)
{ {
return Mathf.LerpUnclamped(start, end, time); // Disabling Extrapolation:
// TODO: Add Jira Ticket
return Mathf.Lerp(start, end, time);
} }
/// <inheritdoc />
protected override float Interpolate(float start, float end, float time) protected override float Interpolate(float start, float end, float time)
{ {
return Mathf.Lerp(start, end, time); return Mathf.Lerp(start, end, time);
} }
} }
/// <inheritdoc />
/// <remarks>
/// This is a buffered linear interpolator for a <see cref="Quaternion"/> type value
/// </remarks>
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion> public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
{ {
/// <inheritdoc />
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
{ {
return Quaternion.SlerpUnclamped(start, end, time); // Disabling Extrapolation:
// TODO: Add Jira Ticket
return Quaternion.Slerp(start, end, time);
} }
/// <inheritdoc />
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
{ {
return Quaternion.SlerpUnclamped(start, end, time); // Disabling Extrapolation:
// TODO: Add Jira Ticket
return Quaternion.Slerp(start, end, time);
} }
} }
} }

View File

@@ -1,14 +1,19 @@
#if COM_UNITY_MODULES_ANIMATION #if COM_UNITY_MODULES_ANIMATION
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using UnityEngine; using UnityEngine;
#if UNITY_EDITOR
using UnityEditor.Animations;
#endif
namespace Unity.Netcode.Components namespace Unity.Netcode.Components
{ {
internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem
{ {
private NetworkAnimator m_NetworkAnimator; private NetworkAnimator m_NetworkAnimator;
private bool m_IsServer;
/// <summary> /// <summary>
/// This removes sending RPCs from within RPCs when the /// This removes sending RPCs from within RPCs when the
@@ -31,20 +36,28 @@ namespace Unity.Netcode.Components
m_SendParameterUpdates.Clear(); m_SendParameterUpdates.Clear();
foreach (var sendEntry in m_SendTriggerUpdates) foreach (var sendEntry in m_SendTriggerUpdates)
{
if (!sendEntry.SendToServer)
{ {
m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams); m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams);
} }
else
{
m_NetworkAnimator.SendAnimTriggerServerRpc(sendEntry.AnimationTriggerMessage);
}
}
m_SendTriggerUpdates.Clear(); m_SendTriggerUpdates.Clear();
} }
/// <inheritdoc />
public void NetworkUpdate(NetworkUpdateStage updateStage) public void NetworkUpdate(NetworkUpdateStage updateStage)
{ {
switch (updateStage) switch (updateStage)
{ {
case NetworkUpdateStage.PreUpdate: case NetworkUpdateStage.PreUpdate:
{ {
// Only the server forwards messages and synchronizes players // Only the owner or the server send messages
if (m_NetworkAnimator.NetworkManager.IsServer) if (m_NetworkAnimator.IsOwner || m_IsServer)
{ {
// Flush any pending messages // Flush any pending messages
FlushMessages(); FlushMessages();
@@ -124,6 +137,7 @@ namespace Unity.Netcode.Components
private struct TriggerUpdate private struct TriggerUpdate
{ {
public bool SendToServer;
public ClientRpcParams ClientRpcParams; public ClientRpcParams ClientRpcParams;
public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage; public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage;
} }
@@ -133,11 +147,23 @@ namespace Unity.Netcode.Components
/// <summary> /// <summary>
/// Invoked when a server needs to forward an update to a Trigger state /// Invoked when a server needs to forward an update to a Trigger state
/// </summary> /// </summary>
internal void SendTriggerUpdate(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) internal void QueueTriggerUpdateToClient(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{ {
m_SendTriggerUpdates.Add(new TriggerUpdate() { ClientRpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage }); m_SendTriggerUpdates.Add(new TriggerUpdate() { ClientRpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage });
} }
internal void QueueTriggerUpdateToServer(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage)
{
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);
@@ -146,33 +172,266 @@ namespace Unity.Netcode.Components
internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator)
{ {
m_NetworkAnimator = networkAnimator; m_NetworkAnimator = networkAnimator;
m_IsServer = networkAnimator.NetworkManager.IsServer;
NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate);
} }
} }
/// <summary> /// <summary>
/// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects. /// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects.
/// </summary> /// </summary>
[AddComponentMenu("Netcode/" + nameof(NetworkAnimator))] [AddComponentMenu("Netcode/Network Animator")]
[RequireComponent(typeof(Animator))] [RequireComponent(typeof(Animator))]
public class NetworkAnimator : NetworkBehaviour public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
{ {
internal struct AnimationMessage : INetworkSerializable [Serializable]
internal class TransitionStateinfo
{ {
// state hash per layer. if non-zero, then Play() this animation, skipping transitions public int Layer;
public int OriginatingState;
public int DestinationState;
public float TransitionDuration;
public int TriggerNameHash;
public int TransitionIndex;
}
/// <summary>
/// Used to build the destination state to transition info table
/// </summary>
[HideInInspector]
[SerializeField]
internal List<TransitionStateinfo> TransitionStateInfoList;
// Used to get the associated transition information required to synchronize late joining clients with transitions
// [Layer][DestinationState][TransitionStateInfo]
private Dictionary<int, Dictionary<int, TransitionStateinfo>> m_DestinationStateToTransitioninfo = new Dictionary<int, Dictionary<int, TransitionStateinfo>>();
/// <summary>
/// Builds the m_DestinationStateToTransitioninfo lookup table
/// </summary>
private void BuildDestinationToTransitionInfoTable()
{
foreach (var entry in TransitionStateInfoList)
{
if (!m_DestinationStateToTransitioninfo.ContainsKey(entry.Layer))
{
m_DestinationStateToTransitioninfo.Add(entry.Layer, new Dictionary<int, TransitionStateinfo>());
}
var destinationStateTransitionInfo = m_DestinationStateToTransitioninfo[entry.Layer];
if (!destinationStateTransitionInfo.ContainsKey(entry.DestinationState))
{
destinationStateTransitionInfo.Add(entry.DestinationState, entry);
}
}
}
/// <summary>
/// Creates the TransitionStateInfoList table
/// </summary>
private void BuildTransitionStateInfoList()
{
#if UNITY_EDITOR
if (UnityEditor.EditorApplication.isUpdating)
{
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 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++)
{
var transition = animatorState.transitions[z];
if (transition.conditions.Length == 0 && transition.isExit)
{
// We don't need to worry about exit transitions with no conditions
continue;
}
foreach (var condition in transition.conditions)
{
var parameterName = condition.parameter;
var parameters = animatorController.parameters;
foreach (var parameter in parameters)
{
switch (parameter.type)
{
case AnimatorControllerParameterType.Trigger:
{
// Match the condition with an existing trigger
if (parameterName == parameter.name)
{
var transitionInfo = new TransitionStateinfo()
{
Layer = x,
OriginatingState = animatorState.nameHash,
DestinationState = transition.destinationState.nameHash,
TransitionDuration = transition.duration,
TriggerNameHash = parameter.nameHash,
TransitionIndex = z
};
TransitionStateInfoList.Add(transitionInfo);
}
break;
}
default:
break;
}
}
}
}
}
}
#endif
}
public void OnAfterDeserialize()
{
BuildDestinationToTransitionInfoTable();
}
public void OnBeforeSerialize()
{
BuildTransitionStateInfoList();
}
internal struct AnimationState : INetworkSerializable
{
// Not to be serialized, used for processing the animation state
internal bool HasBeenProcessed;
internal int StateHash; internal int StateHash;
internal float NormalizedTime; internal float NormalizedTime;
internal int Layer; internal int Layer;
internal float Weight; internal float Weight;
// For synchronizing transitions
internal bool Transition;
// The StateHash is where the transition starts
// and the DestinationStateHash is the destination state
internal int DestinationStateHash;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{ {
serializer.SerializeValue(ref StateHash); if (serializer.IsWriter)
serializer.SerializeValue(ref NormalizedTime); {
serializer.SerializeValue(ref Layer); var writer = serializer.GetFastBufferWriter();
serializer.SerializeValue(ref Weight); var writeSize = FastBufferWriter.GetWriteSize(Transition);
writeSize += FastBufferWriter.GetWriteSize(StateHash);
writeSize += FastBufferWriter.GetWriteSize(NormalizedTime);
writeSize += FastBufferWriter.GetWriteSize(Layer);
writeSize += FastBufferWriter.GetWriteSize(Weight);
if (Transition)
{
writeSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
}
if (!writer.TryBeginWrite(writeSize))
{
throw new OverflowException($"[{GetType().Name}] Could not serialize: Out of buffer space.");
}
writer.WriteValue(Transition);
writer.WriteValue(StateHash);
writer.WriteValue(NormalizedTime);
writer.WriteValue(Layer);
writer.WriteValue(Weight);
if (Transition)
{
writer.WriteValue(DestinationStateHash);
}
}
else
{
var reader = serializer.GetFastBufferReader();
// Begin reading the Transition flag
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Transition)))
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
reader.ReadValue(out Transition);
// Now determine what remains to be read
var readSize = FastBufferWriter.GetWriteSize(StateHash);
readSize += FastBufferWriter.GetWriteSize(NormalizedTime);
readSize += FastBufferWriter.GetWriteSize(Layer);
readSize += FastBufferWriter.GetWriteSize(Weight);
if (Transition)
{
readSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
}
// Now read the remaining information about this AnimationState
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
reader.ReadValue(out StateHash);
reader.ReadValue(out NormalizedTime);
reader.ReadValue(out Layer);
reader.ReadValue(out Weight);
if (Transition)
{
reader.ReadValue(out DestinationStateHash);
}
}
}
}
internal struct AnimationMessage : INetworkSerializable
{
// Not to be serialized, used for processing the animation message
internal bool HasBeenProcessed;
// This is preallocated/populated in OnNetworkSpawn for all instances in the event ownership or
// authority changes. When serializing, IsDirtyCount determines how many AnimationState entries
// should be serialized from the list. When deserializing the list is created and populated with
// only the number of AnimationStates received which is dictated by the deserialized IsDirtyCount.
internal List<AnimationState> AnimationStates;
// Used to determine how many AnimationState entries we are sending or receiving
internal int IsDirtyCount;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
var animationState = new AnimationState();
if (serializer.IsReader)
{
AnimationStates = new List<AnimationState>();
serializer.SerializeValue(ref IsDirtyCount);
// Since we create a new AnimationMessage when deserializing
// we need to create new animation states for each incoming
// AnimationState being updated
for (int i = 0; i < IsDirtyCount; i++)
{
animationState = new AnimationState();
serializer.SerializeValue(ref animationState);
AnimationStates.Add(animationState);
}
}
else
{
// When writing, only send the counted dirty animation states
serializer.SerializeValue(ref IsDirtyCount);
for (int i = 0; i < IsDirtyCount; i++)
{
animationState = AnimationStates[i];
serializer.SerializeNetworkSerializable(ref animationState);
}
}
} }
} }
@@ -221,7 +480,8 @@ namespace Unity.Netcode.Components
return true; return true;
} }
// Animators only support up to 32 params // Animators only support up to 32 parameters
// TODO: Look into making this a range limited property
private const int k_MaxAnimationParams = 32; private const int k_MaxAnimationParams = 32;
private int[] m_TransitionHash; private int[] m_TransitionHash;
@@ -267,9 +527,9 @@ namespace Unity.Netcode.Components
m_NetworkAnimatorStateChangeHandler = null; m_NetworkAnimatorStateChangeHandler = null;
} }
if (IsServer) if (m_CachedNetworkManager != null)
{ {
NetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback; m_CachedNetworkManager.OnClientConnectedCallback -= OnClientConnectedCallback;
} }
if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated) if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated)
@@ -291,40 +551,64 @@ namespace Unity.Netcode.Components
private List<int> m_ParametersToUpdate; private List<int> m_ParametersToUpdate;
private List<ulong> m_ClientSendList; private List<ulong> m_ClientSendList;
private ClientRpcParams m_ClientRpcParams; 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() public override void OnNetworkSpawn()
{
if (IsOwner || IsServer)
{ {
int layers = m_Animator.layerCount; int layers = m_Animator.layerCount;
// Initializing the below arrays for everyone handles an issue
// 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)
{
NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback;
}
// Store off our current layer weights
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
}
}
if (IsServer) if (IsServer)
{ {
m_ClientSendList = new List<ulong>(128); m_ClientSendList = new List<ulong>(128);
m_ClientRpcParams = new ClientRpcParams(); m_ClientRpcParams = new ClientRpcParams();
m_ClientRpcParams.Send = new ClientRpcSendParams(); m_ClientRpcParams.Send = new ClientRpcSendParams();
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; 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
// ownership or authority changes during runtime.
m_AnimationMessage = new AnimationMessage();
m_AnimationMessage.AnimationStates = new List<AnimationState>();
// Store off our current layer weights and create our animation
// state entries per layer.
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
// We create an AnimationState per layer to preallocate the maximum
// number of possible AnimationState changes we could send in one
// AnimationMessage.
m_AnimationMessage.AnimationStates.Add(new AnimationState());
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
} }
} }
// Build our reference parameter values to detect when they change
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);
@@ -371,6 +655,7 @@ namespace Unity.Netcode.Components
m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this); m_NetworkAnimatorStateChangeHandler = new NetworkAnimatorStateChangeHandler(this);
} }
/// <inheritdoc/>
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
{ {
Cleanup(); Cleanup();
@@ -391,18 +676,25 @@ namespace Unity.Netcode.Components
m_ParametersToUpdate.Add(i); m_ParametersToUpdate.Add(i);
} }
SendParametersUpdate(m_ClientRpcParams); SendParametersUpdate(m_ClientRpcParams);
// Reset the dirty count before synchronizing the newly connected client with all layers
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); AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
var stateHash = st.fullPathHash; var stateHash = st.fullPathHash;
var normalizedTime = st.normalizedTime; var normalizedTime = st.normalizedTime;
var totalSpeed = st.speed * st.speedMultiplier; var isInTransition = m_Animator.IsInTransition(layer);
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
// NOTE: // Grab one of the available AnimationState entries so we can fill it with the current
// When synchronizing, for now we will just complete the transition and // layer's animation state.
// synchronize the player to the next state being transitioned into var animationState = m_AnimationMessage.AnimationStates[layer];
if (m_Animator.IsInTransition(layer))
// Synchronizing transitions with trigger conditions for late joining clients is now
// handled by cross fading between the late joining client's current layer's AnimationState
// and the transition's destination AnimationState.
if (isInTransition)
{ {
var tt = m_Animator.GetAnimatorTransitionInfo(layer); var tt = m_Animator.GetAnimatorTransitionInfo(layer);
var nextState = m_Animator.GetNextAnimatorStateInfo(layer); var nextState = m_Animator.GetNextAnimatorStateInfo(layer);
@@ -420,32 +712,47 @@ namespace Unity.Netcode.Components
{ {
normalizedTime = 0.0f; normalizedTime = 0.0f;
} }
stateHash = nextState.fullPathHash; stateHash = nextState.fullPathHash;
}
else
if (st.normalizedTime >= adjustedNormalizedMaxTime)
{
continue;
}
var animMsg = new AnimationMessage // Use the destination state to transition info lookup table to see if this is a transition we can
// synchronize using cross fading
if (m_DestinationStateToTransitioninfo.ContainsKey(layer))
{ {
StateHash = stateHash, if (m_DestinationStateToTransitioninfo[layer].ContainsKey(nextState.shortNameHash))
NormalizedTime = normalizedTime, {
Layer = layer, var destinationInfo = m_DestinationStateToTransitioninfo[layer][nextState.shortNameHash];
Weight = m_LayerWeights[layer] stateHash = destinationInfo.OriginatingState;
}; // Set the destination state to cross fade to from the originating state
// Server always send via client RPC animationState.DestinationStateHash = destinationInfo.DestinationState;
SendAnimStateClientRpc(animMsg, m_ClientRpcParams); }
} }
} }
animationState.Transition = isInTransition; // The only time this could be set to true
animationState.StateHash = stateHash; // When a transition, this is the originating/starting state
animationState.NormalizedTime = normalizedTime;
animationState.Layer = layer;
animationState.Weight = m_LayerWeights[layer];
// Apply the changes
m_AnimationMessage.AnimationStates[layer] = animationState;
}
// Send all animation states
m_AnimationMessage.IsDirtyCount = m_Animator.layerCount;
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
}
/// <summary>
/// Required for the server to synchronize newly joining players
/// </summary>
private void OnClientConnectedCallback(ulong playerId) private void OnClientConnectedCallback(ulong playerId)
{ {
m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId); m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId);
} }
/// <summary>
/// Checks for changes in both Animator parameters and state.
/// </summary>
internal void CheckForAnimatorChanges() internal void CheckForAnimatorChanges()
{ {
if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer) if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer)
@@ -460,45 +767,57 @@ namespace Unity.Netcode.Components
if (m_Animator.runtimeAnimatorController == null) if (m_Animator.runtimeAnimatorController == null)
{ {
if (NetworkManager.LogLevel == LogLevel.Developer)
{
Debug.LogError($"[{GetType().Name}] Could not find an assigned {nameof(RuntimeAnimatorController)}! Cannot check {nameof(Animator)} for changes in state!");
}
return; return;
} }
int stateHash; int stateHash;
float normalizedTime; float normalizedTime;
// This sends updates only if a layer change or transition is happening // Reset the dirty count before checking for AnimationState updates
m_AnimationMessage.IsDirtyCount = 0;
// This sends updates only if a layer's state has changed
for (int layer = 0; layer < m_Animator.layerCount; layer++) for (int layer = 0; layer < m_Animator.layerCount; layer++)
{ {
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;
// determine if we have reached the end of our state time, if so we can skip
if (st.normalizedTime >= adjustedNormalizedMaxTime)
{
continue;
}
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer)) if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{ {
continue; continue;
} }
var animMsg = new AnimationMessage // 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
StateHash = stateHash, // current layer's state.
NormalizedTime = normalizedTime, var animationState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
Layer = layer,
Weight = m_LayerWeights[layer]
};
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
if (m_AnimationMessage.IsDirtyCount > 0)
{
if (!IsServer && IsOwner) if (!IsServer && IsOwner)
{ {
SendAnimStateServerRpc(animMsg); SendAnimStateServerRpc(m_AnimationMessage);
} }
else else
{ {
SendAnimStateClientRpc(animMsg); SendAnimStateClientRpc(m_AnimationMessage);
} }
} }
} }
@@ -594,7 +913,7 @@ namespace Unity.Netcode.Components
/// <summary> /// <summary>
/// Checks if any of the Animator's states have changed /// Checks if any of the Animator's states have changed
/// </summary> /// </summary>
private unsafe bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer) private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{ {
stateHash = 0; stateHash = 0;
normalizedTime = 0; normalizedTime = 0;
@@ -744,14 +1063,57 @@ namespace Unity.Netcode.Components
} }
/// <summary> /// <summary>
/// Applies the AnimationMessage state to the Animator /// Applies the AnimationState state to the Animator
/// </summary> /// </summary>
private unsafe void UpdateAnimationState(AnimationMessage animationState) internal void UpdateAnimationState(AnimationState animationState)
{ {
if (animationState.StateHash != 0) if (animationState.StateHash == 0)
{
return;
}
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 (animationState.Transition)
{
// We should have all valid entries for any animation state transition update
// Verify the AnimationState's assigned Layer exists
if (m_DestinationStateToTransitioninfo.ContainsKey(animationState.Layer))
{
// Verify the inner-table has the destination AnimationState name hash
if (m_DestinationStateToTransitioninfo[animationState.Layer].ContainsKey(animationState.DestinationStateHash))
{
// Make sure we are on the originating/starting state we are going to cross fade into
if (currentState.shortNameHash == animationState.StateHash)
{
// Get the transition state information
var transitionStateInfo = m_DestinationStateToTransitioninfo[animationState.Layer][animationState.DestinationStateHash];
// Cross fade from the current to the destination state for the transitions duration while starting at the server's current normalized time of the transition
m_Animator.CrossFade(transitionStateInfo.DestinationState, transitionStateInfo.TransitionDuration, transitionStateInfo.Layer, 0.0f, animationState.NormalizedTime);
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"Current State Hash ({currentState.fullPathHash}) != AnimationState.StateHash ({animationState.StateHash})");
}
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) sub-table does not contain destination state ({animationState.DestinationStateHash})!");
}
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!");
}
}
else
{
if (currentState.fullPathHash != 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); m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight);
} }
@@ -773,7 +1135,7 @@ namespace Unity.Netcode.Components
return; return;
} }
UpdateParameters(parametersUpdate); UpdateParameters(parametersUpdate);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0) if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{ {
m_ClientSendList.Clear(); m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds); m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
@@ -803,11 +1165,11 @@ namespace Unity.Netcode.Components
/// The server sets its local state and then forwards the message to the remaining clients /// The server sets its local state and then forwards the message to the remaining clients
/// </summary> /// </summary>
[ServerRpc] [ServerRpc]
private unsafe void SendAnimStateServerRpc(AnimationMessage animSnapshot, ServerRpcParams serverRpcParams = default) private unsafe void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpcParams serverRpcParams = default)
{ {
if (IsServerAuthoritative()) if (IsServerAuthoritative())
{ {
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot); m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage);
} }
else else
{ {
@@ -815,15 +1177,21 @@ namespace Unity.Netcode.Components
{ {
return; return;
} }
UpdateAnimationState(animSnapshot);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0) foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
m_NetworkAnimatorStateChangeHandler.AddAnimationMessageToProcessQueue(animationMessage);
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(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId); m_ClientSendList.Remove(NetworkManager.ServerClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animSnapshot, m_ClientRpcParams); m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams);
} }
} }
} }
@@ -832,12 +1200,25 @@ 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 animSnapshot, ClientRpcParams clientRpcParams = default) private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
{ {
// This should never happen
if (IsHost)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning("Detected the Host is sending itself animation updates! Please report this issue.");
}
return;
}
var isServerAuthoritative = IsServerAuthoritative(); var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{ {
UpdateAnimationState(animSnapshot); foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
} }
} }
@@ -846,48 +1227,67 @@ namespace Unity.Netcode.Components
/// The server sets its local state and then forwards the message to the remaining clients /// The server sets its local state and then forwards the message to the remaining clients
/// </summary> /// </summary>
[ServerRpc] [ServerRpc]
private void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default) internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
{ {
// If it is server authoritative
if (IsServerAuthoritative()) if (IsServerAuthoritative())
{ {
m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage); // 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 else
{ {
// Ignore if a non-owner sent this.
if (serverRpcParams.Receive.SenderClientId != OwnerClientId) if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{ {
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Owner 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.");
}
return; return;
} }
// trigger the animation locally on the server...
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
if (NetworkManager.ConnectedClientsIds.Count - 2 > 0) // set the trigger locally on the server
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(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId); m_ClientSendList.Remove(NetworkManager.ServerClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.SendTriggerUpdate(animationTriggerMessage, m_ClientRpcParams); m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
} }
} }
/// <summary>
/// See above <see cref="m_LastTriggerHash"/>
/// </summary>
private void InternalSetTrigger(int hash, bool isSet = true)
{
m_Animator.SetBool(hash, isSet);
} }
/// <summary> /// <summary>
/// Internally-called RPC client receiving function to update a trigger when the server wants to forward /// Internally-called RPC client receiving function to update a trigger when the server wants to forward
/// a trigger for a client to play / reset /// a trigger for a client to play / reset
/// </summary> /// </summary>
/// <param name="animSnapshot">the payload containing the trigger data to apply</param> /// <param name="animationTriggerMessage">the payload containing the trigger data to apply</param>
/// <param name="clientRpcParams">unused</param> /// <param name="clientRpcParams">unused</param>
[ClientRpc] [ClientRpc]
internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{ {
var isServerAuthoritative = IsServerAuthoritative(); InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
}
} }
/// <summary> /// <summary>
@@ -904,20 +1304,33 @@ namespace Unity.Netcode.Components
/// <param name="setTrigger">sets (true) or resets (false) the trigger. The default is to set it (true).</param> /// <param name="setTrigger">sets (true) or resets (false) the trigger. The default is to set it (true).</param>
public void SetTrigger(int hash, bool setTrigger = true) public void SetTrigger(int hash, bool setTrigger = true)
{ {
var isServerAuthoritative = IsServerAuthoritative(); // MTT-3564:
if (IsOwner && !isServerAuthoritative || IsServer && isServerAuthoritative) // After fixing the issue with trigger controlled Transitions being synchronized twice,
// it exposed additional issues with this logic. Now, either the owner or the server can
// update triggers. Since server-side RPCs are immediately invoked, for a host a trigger
// will happen when SendAnimTriggerClientRpc is called. For a client owner, we call the
// SendAnimTriggerServerRpc and then trigger locally when running in owner authority mode.
if (IsOwner || IsServer)
{ {
var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger };
if (IsServer) if (IsServer)
{ {
SendAnimTriggerClientRpc(animTriggerMessage); /// <see cref="UpdatePendingTriggerStates"/> as to why we queue
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage);
if (!IsHost)
{
InternalSetTrigger(hash);
}
} }
else else
{ {
SendAnimTriggerServerRpc(animTriggerMessage); /// <see cref="UpdatePendingTriggerStates"/> as to why we queue
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToServer(animTriggerMessage);
if (!IsServerAuthoritative())
{
InternalSetTrigger(hash);
}
} }
// trigger the animation locally on the server...
m_Animator.SetBool(hash, setTrigger);
} }
} }

View File

@@ -9,6 +9,7 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
[RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(NetworkTransform))] [RequireComponent(typeof(NetworkTransform))]
[AddComponentMenu("Netcode/Network Rigidbody")]
public class NetworkRigidbody : NetworkBehaviour public class NetworkRigidbody : NetworkBehaviour
{ {
/// <summary> /// <summary>

View File

@@ -9,6 +9,7 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
[RequireComponent(typeof(Rigidbody2D))] [RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(NetworkTransform))] [RequireComponent(typeof(NetworkTransform))]
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
public class NetworkRigidbody2D : NetworkBehaviour public class NetworkRigidbody2D : NetworkBehaviour
{ {
private Rigidbody2D m_Rigidbody; private Rigidbody2D m_Rigidbody;

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,10 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
internal static class CodeGenHelpers internal static class CodeGenHelpers
{ {
public const string DotnetModuleName = "netstandard.dll";
public const string UnityModuleName = "UnityEngine.CoreModule.dll";
public const string NetcodeModuleName = "Unity.Netcode.Runtime.dll";
public const string RuntimeAssemblyName = "Unity.Netcode.Runtime"; public const string RuntimeAssemblyName = "Unity.Netcode.Runtime";
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName; public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName;
@@ -119,6 +123,19 @@ namespace Unity.Netcode.Editor.CodeGen
try try
{ {
var typeDef = typeReference.Resolve(); var typeDef = typeReference.Resolve();
// Note: this won't catch generics correctly.
//
// class Foo<T>: IInterface<T> {}
// class Bar: Foo<int> {}
//
// Bar.HasInterface(IInterface<int>) -> returns false even though it should be true.
//
// This can be fixed (see GetAllFieldsAndResolveGenerics() in NetworkBehaviourILPP to understand how)
// but right now we don't need that to work so it's left alone to reduce complexity
if (typeDef.BaseType.HasInterface(interfaceTypeFullName))
{
return true;
}
var typeFaces = typeDef.Interfaces; var typeFaces = typeDef.Interfaces;
return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName); return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName);
} }
@@ -380,5 +397,74 @@ namespace Unity.Netcode.Editor.CodeGen
return assemblyDefinition; return assemblyDefinition;
} }
private static void SearchForBaseModulesRecursive(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver, ref ModuleDefinition unityModule, ref ModuleDefinition netcodeModule, HashSet<string> visited)
{
foreach (var module in assemblyDefinition.Modules)
{
if (module == null)
{
continue;
}
if (unityModule != null && netcodeModule != null)
{
return;
}
if (unityModule == null && module.Name == UnityModuleName)
{
unityModule = module;
continue;
}
if (netcodeModule == null && module.Name == NetcodeModuleName)
{
netcodeModule = module;
continue;
}
}
if (unityModule != null && netcodeModule != null)
{
return;
}
foreach (var assemblyNameReference in assemblyDefinition.MainModule.AssemblyReferences)
{
if (assemblyNameReference == null)
{
continue;
}
if (visited.Contains(assemblyNameReference.Name))
{
continue;
}
visited.Add(assemblyNameReference.Name);
var assembly = assemblyResolver.Resolve(assemblyNameReference);
if (assembly == null)
{
continue;
}
SearchForBaseModulesRecursive(assembly, assemblyResolver, ref unityModule, ref netcodeModule, visited);
if (unityModule != null && netcodeModule != null)
{
return;
}
}
}
public static (ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver)
{
ModuleDefinition unityModule = null;
ModuleDefinition netcodeModule = null;
var visited = new HashSet<string>();
SearchForBaseModulesRecursive(assemblyDefinition, assemblyResolver, ref unityModule, ref netcodeModule, visited);
return (unityModule, netcodeModule);
}
} }
} }

View File

@@ -2,7 +2,6 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using Mono.Cecil.Rocks; using Mono.Cecil.Rocks;
@@ -17,9 +16,7 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
public override ILPPInterface GetInstance() => this; public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>(); private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
@@ -33,13 +30,22 @@ namespace Unity.Netcode.Editor.CodeGen
m_Diagnostics.Clear(); m_Diagnostics.Clear();
// read // read
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver); var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out m_AssemblyResolver);
if (assemblyDefinition == null) if (assemblyDefinition == null)
{ {
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}"); m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
return null; return null;
} }
// modules
(_, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver);
if (m_NetcodeModule == null)
{
m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}");
return null;
}
// process // process
var mainModule = assemblyDefinition.MainModule; var mainModule = assemblyDefinition.MainModule;
if (mainModule != null) if (mainModule != null)
@@ -61,7 +67,7 @@ namespace Unity.Netcode.Editor.CodeGen
} }
catch (Exception e) catch (Exception e)
{ {
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
} }
} }
else else
@@ -92,77 +98,136 @@ namespace Unity.Netcode.Editor.CodeGen
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
} }
private ModuleDefinition m_NetcodeModule;
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)
{ {
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]); // Different environments seem to have different situations...
// Some have these definitions in netstandard.dll...
// some seem to have them elsewhere...
// Since they're standard .net classes they're not going to cause
// the same issues as referencing other assemblies, in theory, since
// the definitions should be standard and consistent across platforms
// (i.e., there's no #if UNITY_EDITOR in them that could create
// invalid IL code)
TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve();
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler); TypeDefinition messageHandlerTypeDef = null;
m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerType); TypeDefinition versionGetterTypeDef = null;
foreach (var fieldInfo in messageWithHandlerType.GetFields()) TypeDefinition messageWithHandlerTypeDef = null;
TypeDefinition ilppMessageProviderTypeDef = null;
TypeDefinition messagingSystemTypeDef = null;
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
{ {
switch (fieldInfo.Name) if (messageHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageHandler))
{
messageHandlerTypeDef = netcodeTypeDef;
continue;
}
if (versionGetterTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.VersionGetter))
{
versionGetterTypeDef = netcodeTypeDef;
continue;
}
if (messageWithHandlerTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem.MessageWithHandler))
{
messageWithHandlerTypeDef = netcodeTypeDef;
continue;
}
if (ilppMessageProviderTypeDef == null && netcodeTypeDef.Name == nameof(ILPPMessageProvider))
{
ilppMessageProviderTypeDef = netcodeTypeDef;
continue;
}
if (messagingSystemTypeDef == null && netcodeTypeDef.Name == nameof(MessagingSystem))
{
messagingSystemTypeDef = netcodeTypeDef;
continue;
}
}
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(messageHandlerTypeDef.GetConstructors().First());
m_MessagingSystem_VersionGetter_Constructor_TypeRef = moduleDefinition.ImportReference(versionGetterTypeDef.GetConstructors().First());
m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerTypeDef);
foreach (var fieldDef in messageWithHandlerTypeDef.Fields)
{
switch (fieldDef.Name)
{ {
case nameof(MessagingSystem.MessageWithHandler.MessageType): case nameof(MessagingSystem.MessageWithHandler.MessageType):
m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
case nameof(MessagingSystem.MessageWithHandler.Handler): case nameof(MessagingSystem.MessageWithHandler.Handler):
m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
case nameof(MessagingSystem.MessageWithHandler.GetVersion):
m_MessagingSystem_MessageWithHandler_GetVersion_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
} }
} }
var typeType = typeof(Type); foreach (var methodDef in typeTypeDef.Methods)
foreach (var methodInfo in typeType.GetMethods())
{ {
switch (methodInfo.Name) switch (methodDef.Name)
{ {
case nameof(Type.GetTypeFromHandle): case nameof(Type.GetTypeFromHandle):
m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodInfo); m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
} }
} }
var ilppMessageProviderType = typeof(ILPPMessageProvider); foreach (var fieldDef in ilppMessageProviderTypeDef.Fields)
foreach (var fieldInfo in ilppMessageProviderType.GetFields(BindingFlags.Static | BindingFlags.NonPublic))
{ {
switch (fieldInfo.Name) switch (fieldDef.Name)
{ {
case nameof(ILPPMessageProvider.__network_message_types): case nameof(ILPPMessageProvider.__network_message_types):
m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
} }
} }
var listType = typeof(List<MessagingSystem.MessageWithHandler>); foreach (var methodDef in listTypeDef.Methods)
foreach (var methodInfo in listType.GetMethods())
{ {
switch (methodInfo.Name) switch (methodDef.Name)
{ {
case nameof(List<MessagingSystem.MessageWithHandler>.Add): case "Add":
m_List_Add_MethodRef = moduleDefinition.ImportReference(methodInfo); m_List_Add_MethodRef = methodDef;
m_List_Add_MethodRef.DeclaringType = listTypeDef.MakeGenericInstanceType(messageWithHandlerTypeDef);
m_List_Add_MethodRef = moduleDefinition.ImportReference(m_List_Add_MethodRef);
break; break;
} }
} }
var messagingSystemType = typeof(MessagingSystem); foreach (var methodDef in messagingSystemTypeDef.Methods)
foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{ {
switch (methodInfo.Name) switch (methodDef.Name)
{ {
case k_ReceiveMessageName: case k_ReceiveMessageName:
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo); m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
case k_CreateMessageAndGetVersionName:
m_MessagingSystem_CreateMessageAndGetVersion_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
} }
} }
@@ -189,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));
@@ -205,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));
@@ -213,15 +278,22 @@ 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));
} }
// Creates a static module constructor (which is executed when the module is loaded) that registers all the // Creates a static module constructor (which is executed when the module is loaded) that registers all the message types in the assembly with MessagingSystem.
// message types in the assembly with MessagingSystem. // This is the same behavior as annotating a static method with [ModuleInitializer] in standardized C# (that attribute doesn't exist in Unity, but the static module constructor still works).
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0 // https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx // https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkMessageTypes) private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkMessageTypes)
@@ -240,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

@@ -10,7 +10,6 @@ using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostPr
namespace Unity.Netcode.Editor.CodeGen namespace Unity.Netcode.Editor.CodeGen
{ {
internal sealed class INetworkSerializableILPP : ILPPInterface internal sealed class INetworkSerializableILPP : ILPPInterface
{ {
public override ILPPInterface GetInstance() => this; public override ILPPInterface GetInstance() => this;
@@ -92,7 +91,7 @@ namespace Unity.Netcode.Editor.CodeGen
} }
catch (Exception e) catch (Exception e)
{ {
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
} }
} }
else else

View File

@@ -2,7 +2,6 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
@@ -23,8 +22,7 @@ namespace Unity.Netcode.Editor.CodeGen
public override ILPPInterface GetInstance() => this; public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>(); private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
@@ -35,7 +33,6 @@ namespace Unity.Netcode.Editor.CodeGen
return null; return null;
} }
m_Diagnostics.Clear(); m_Diagnostics.Clear();
// read // read
@@ -46,11 +43,27 @@ namespace Unity.Netcode.Editor.CodeGen
return null; return null;
} }
// modules
(m_UnityModule, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver);
if (m_UnityModule == null)
{
m_Diagnostics.AddError($"Cannot find Unity module: {CodeGenHelpers.UnityModuleName}");
return null;
}
if (m_NetcodeModule == null)
{
m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}");
return null;
}
// process // process
var mainModule = assemblyDefinition.MainModule; var mainModule = assemblyDefinition.MainModule;
if (mainModule != null) if (mainModule != null)
{ {
m_MainModule = mainModule; m_MainModule = mainModule;
if (ImportReferences(mainModule)) if (ImportReferences(mainModule))
{ {
// process `NetworkBehaviour` types // process `NetworkBehaviour` types
@@ -60,10 +73,12 @@ namespace Unity.Netcode.Editor.CodeGen
.Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName)) .Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName))
.ToList() .ToList()
.ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines)); .ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines));
CreateNetworkVariableTypeInitializers(assemblyDefinition);
} }
catch (Exception e) catch (Exception e)
{ {
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|")); m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
} }
} }
else else
@@ -92,7 +107,135 @@ namespace Unity.Netcode.Editor.CodeGen
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
} }
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
{
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
if (staticCtorMethodDef == null)
{
staticCtorMethodDef = new MethodDefinition(
".cctor", // Static Constructor (constant-constructor)
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static,
typeDefinition.Module.TypeSystem.Void);
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
typeDefinition.Methods.Add(staticCtorMethodDef);
}
return staticCtorMethodDef;
}
private bool IsMemcpyableType(TypeReference type)
{
foreach (var supportedType in BaseSupportedTypes)
{
if (type.FullName == supportedType.FullName)
{
return true;
}
}
return false;
}
private bool IsSpecialCaseType(TypeReference type)
{
foreach (var supportedType in SpecialCaseTypes)
{
if (type.FullName == supportedType.FullName)
{
return true;
}
}
return false;
}
private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
{
foreach (var typeDefinition in assembly.MainModule.Types)
{
if (typeDefinition.FullName == "<Module>")
{
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
var processor = staticCtorMethodDef.Body.GetILProcessor();
var instructions = new List<Instruction>();
foreach (var type in m_WrappedNetworkVariableTypes)
{
if (IsSpecialCaseType(type))
{
continue;
}
// If a serializable type isn't found, FallbackSerializer will be used automatically, which will
// call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton
// for types that aren't in our official supported types list.
GenericInstanceMethod serializeMethod = null;
GenericInstanceMethod equalityMethod;
if (type.IsValueType)
{
if (type.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || type.Resolve().IsEnum || IsMemcpyableType(type))
{
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef);
}
else if (type.HasInterface(typeof(INetworkSerializable).FullName))
{
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef);
}
else if (type.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && type.HasInterface(k_INativeListBool_FullName))
{
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef);
}
if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">"))
{
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef);
}
else
{
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef);
}
}
else
{
if (type.HasInterface(typeof(INetworkSerializable).FullName))
{
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef);
}
if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">"))
{
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef);
}
else
{
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
}
}
if (serializeMethod != null)
{
serializeMethod.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod)));
}
equalityMethod.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(equalityMethod)));
}
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
break;
}
}
}
private ModuleDefinition m_MainModule; private ModuleDefinition m_MainModule;
private ModuleDefinition m_UnityModule;
private ModuleDefinition m_NetcodeModule;
private PostProcessorAssemblyResolver m_AssemblyResolver; private PostProcessorAssemblyResolver m_AssemblyResolver;
private MethodReference m_Debug_LogError_MethodRef; private MethodReference m_Debug_LogError_MethodRef;
@@ -123,14 +266,76 @@ namespace Unity.Netcode.Editor.CodeGen
private FieldReference m_ServerRpcParams_Receive_FieldRef; private FieldReference m_ServerRpcParams_Receive_FieldRef;
private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef; private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef;
private TypeReference m_ClientRpcParams_TypeRef; private TypeReference m_ClientRpcParams_TypeRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_UInt_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_Long_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_ULong_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;
private TypeReference m_FastBufferWriter_TypeRef; private TypeReference m_FastBufferWriter_TypeRef;
private 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 List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>(); private readonly List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
private TypeReference m_FastBufferReader_TypeRef; private TypeReference m_FastBufferReader_TypeRef;
private Dictionary<string, MethodReference> m_FastBufferReader_ReadValue_MethodRefs = new Dictionary<string, MethodReference>(); private readonly Dictionary<string, MethodReference> m_FastBufferReader_ReadValue_MethodRefs = new Dictionary<string, MethodReference>();
private List<MethodReference> m_FastBufferReader_ExtensionMethodRefs = new List<MethodReference>(); private readonly List<MethodReference> m_FastBufferReader_ExtensionMethodRefs = new List<MethodReference>();
private HashSet<TypeReference> m_WrappedNetworkVariableTypes = new HashSet<TypeReference>();
internal static readonly Type[] BaseSupportedTypes = new[]
{
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(float),
// the following types have special handling
/*typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),*/
typeof(Vector2),
typeof(Vector3),
typeof(Vector2Int),
typeof(Vector3Int),
typeof(Vector4),
typeof(Quaternion),
typeof(Color),
typeof(Color32),
typeof(Ray),
typeof(Ray2D)
};
internal static readonly Type[] SpecialCaseTypes = new[]
{
// the following types have special handling
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
};
private const string k_Debug_LogError = nameof(Debug.LogError); private const string k_Debug_LogError = nameof(Debug.LogError);
private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId); private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId);
@@ -157,160 +362,257 @@ namespace Unity.Netcode.Editor.CodeGen
private const string k_ServerRpcParams_Receive = nameof(ServerRpcParams.Receive); private const string k_ServerRpcParams_Receive = nameof(ServerRpcParams.Receive);
private const string k_ServerRpcReceiveParams_SenderClientId = nameof(ServerRpcReceiveParams.SenderClientId); private const string k_ServerRpcReceiveParams_SenderClientId = nameof(ServerRpcReceiveParams.SenderClientId);
// CodeGen cannot reference the collections assembly to do a typeof() on it due to a bug that causes that to crash.
private const string k_INativeListBool_FullName = "Unity.Collections.INativeList`1<System.Byte>";
private bool ImportReferences(ModuleDefinition moduleDefinition) private bool ImportReferences(ModuleDefinition moduleDefinition)
{ {
var debugType = typeof(Debug); TypeDefinition debugTypeDef = null;
foreach (var methodInfo in debugType.GetMethods()) foreach (var unityTypeDef in m_UnityModule.GetAllTypes())
{ {
switch (methodInfo.Name) if (debugTypeDef == null && unityTypeDef.FullName == typeof(Debug).FullName)
{
debugTypeDef = unityTypeDef;
continue;
}
}
TypeDefinition networkManagerTypeDef = null;
TypeDefinition networkBehaviourTypeDef = null;
TypeDefinition networkHandlerDelegateTypeDef = null;
TypeDefinition rpcParamsTypeDef = null;
TypeDefinition serverRpcParamsTypeDef = null;
TypeDefinition clientRpcParamsTypeDef = null;
TypeDefinition fastBufferWriterTypeDef = null;
TypeDefinition fastBufferReaderTypeDef = null;
TypeDefinition networkVariableSerializationTypesTypeDef = null;
TypeDefinition bytePackerTypeDef = null;
TypeDefinition byteUnpackerTypeDef = null;
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
{
if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager))
{
networkManagerTypeDef = netcodeTypeDef;
continue;
}
if (networkBehaviourTypeDef == null && netcodeTypeDef.Name == nameof(NetworkBehaviour))
{
networkBehaviourTypeDef = netcodeTypeDef;
continue;
}
if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler))
{
networkHandlerDelegateTypeDef = netcodeTypeDef;
continue;
}
if (rpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(__RpcParams))
{
rpcParamsTypeDef = netcodeTypeDef;
continue;
}
if (serverRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ServerRpcParams))
{
serverRpcParamsTypeDef = netcodeTypeDef;
continue;
}
if (clientRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ClientRpcParams))
{
clientRpcParamsTypeDef = netcodeTypeDef;
continue;
}
if (fastBufferWriterTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferWriter))
{
fastBufferWriterTypeDef = netcodeTypeDef;
continue;
}
if (fastBufferReaderTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferReader))
{
fastBufferReaderTypeDef = netcodeTypeDef;
continue;
}
if (networkVariableSerializationTypesTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableSerializationTypes))
{
networkVariableSerializationTypesTypeDef = netcodeTypeDef;
continue;
}
if (bytePackerTypeDef == null && netcodeTypeDef.Name == nameof(BytePacker))
{
bytePackerTypeDef = netcodeTypeDef;
continue;
}
if (byteUnpackerTypeDef == null && netcodeTypeDef.Name == nameof(ByteUnpacker))
{
byteUnpackerTypeDef = netcodeTypeDef;
continue;
}
}
foreach (var methodDef in debugTypeDef.Methods)
{
switch (methodDef.Name)
{ {
case k_Debug_LogError: case k_Debug_LogError:
if (methodInfo.GetParameters().Length == 1) if (methodDef.Parameters.Count == 1)
{ {
m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodInfo); m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodDef);
} }
break; break;
} }
} }
var networkManagerType = typeof(NetworkManager); m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerTypeDef);
m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerType); foreach (var propertyDef in networkManagerTypeDef.Properties)
foreach (var propertyInfo in networkManagerType.GetProperties())
{ {
switch (propertyInfo.Name) switch (propertyDef.Name)
{ {
case k_NetworkManager_LocalClientId: case k_NetworkManager_LocalClientId:
m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
case k_NetworkManager_IsListening: case k_NetworkManager_IsListening:
m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
case k_NetworkManager_IsHost: case k_NetworkManager_IsHost:
m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
case k_NetworkManager_IsServer: case k_NetworkManager_IsServer:
m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
case k_NetworkManager_IsClient: case k_NetworkManager_IsClient:
m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
} }
} }
foreach (var fieldInfo in networkManagerType.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) foreach (var fieldDef in networkManagerTypeDef.Fields)
{ {
switch (fieldInfo.Name) switch (fieldDef.Name)
{ {
case k_NetworkManager_LogLevel: case k_NetworkManager_LogLevel:
m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
case k_NetworkManager_rpc_func_table: case k_NetworkManager_rpc_func_table:
m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldDef);
m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add"));
m_NetworkManager_rpc_func_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add");
m_NetworkManager_rpc_func_table_Add_MethodRef.DeclaringType = fieldDef.FieldType;
m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_func_table_Add_MethodRef);
break; break;
case k_NetworkManager_rpc_name_table: case k_NetworkManager_rpc_name_table:
m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldDef);
m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add"));
m_NetworkManager_rpc_name_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add");
m_NetworkManager_rpc_name_table_Add_MethodRef.DeclaringType = fieldDef.FieldType;
m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_name_table_Add_MethodRef);
break; break;
} }
} }
var networkBehaviourType = typeof(NetworkBehaviour); m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourTypeDef);
m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourType); foreach (var propertyDef in networkBehaviourTypeDef.Properties)
foreach (var propertyInfo in networkBehaviourType.GetProperties())
{ {
switch (propertyInfo.Name) switch (propertyDef.Name)
{ {
case k_NetworkBehaviour_NetworkManager: case k_NetworkBehaviour_NetworkManager:
m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
case k_NetworkBehaviour_OwnerClientId: case k_NetworkBehaviour_OwnerClientId:
m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod); m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
break; break;
} }
} }
foreach (var methodInfo in networkBehaviourType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) foreach (var methodDef in networkBehaviourTypeDef.Methods)
{ {
switch (methodInfo.Name) switch (methodDef.Name)
{ {
case k_NetworkBehaviour_beginSendServerRpc: case k_NetworkBehaviour_beginSendServerRpc:
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
case k_NetworkBehaviour_endSendServerRpc: case k_NetworkBehaviour_endSendServerRpc:
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
case k_NetworkBehaviour_beginSendClientRpc: case k_NetworkBehaviour_beginSendClientRpc:
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
case k_NetworkBehaviour_endSendClientRpc: case k_NetworkBehaviour_endSendClientRpc:
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
break; break;
} }
} }
foreach (var fieldInfo in networkBehaviourType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) foreach (var fieldDef in networkBehaviourTypeDef.Fields)
{ {
switch (fieldInfo.Name) switch (fieldDef.Name)
{ {
case k_NetworkBehaviour_rpc_exec_stage: case k_NetworkBehaviour_rpc_exec_stage:
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
} }
} }
var networkHandlerDelegateType = typeof(NetworkManager.RpcReceiveHandler); foreach (var ctor in networkHandlerDelegateTypeDef.Resolve().GetConstructors())
m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(networkHandlerDelegateType.GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
var rpcParamsType = typeof(__RpcParams);
m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsType);
foreach (var fieldInfo in rpcParamsType.GetFields())
{ {
switch (fieldInfo.Name) if (ctor.HasParameters &&
ctor.Parameters.Count == 2 &&
ctor.Parameters[0].ParameterType.Name == nameof(System.Object) &&
ctor.Parameters[1].ParameterType.Name == nameof(IntPtr))
{
m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(ctor);
break;
}
}
m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsTypeDef);
foreach (var fieldDef in rpcParamsTypeDef.Fields)
{
switch (fieldDef.Name)
{ {
case k_RpcParams_Server: case k_RpcParams_Server:
m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
case k_RpcParams_Client: case k_RpcParams_Client:
m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
} }
} }
var serverRpcParamsType = typeof(ServerRpcParams); m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsTypeDef);
m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsType); foreach (var fieldDef in serverRpcParamsTypeDef.Fields)
foreach (var fieldInfo in serverRpcParamsType.GetFields())
{ {
switch (fieldInfo.Name) switch (fieldDef.Name)
{ {
case k_ServerRpcParams_Receive: case k_ServerRpcParams_Receive:
foreach (var recvFieldInfo in fieldInfo.FieldType.GetFields()) foreach (var recvFieldDef in fieldDef.FieldType.Resolve().Fields)
{ {
switch (recvFieldInfo.Name) switch (recvFieldDef.Name)
{ {
case k_ServerRpcReceiveParams_SenderClientId: case k_ServerRpcReceiveParams_SenderClientId:
m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldInfo); m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldDef);
break; break;
} }
} }
m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldInfo); m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldDef);
break; break;
} }
} }
var clientRpcParamsType = typeof(ClientRpcParams); m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsTypeDef);
m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsType); m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterTypeDef);
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderTypeDef);
var fastBufferWriterType = typeof(FastBufferWriter); // Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented methods to be called
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType);
var fastBufferReaderType = typeof(FastBufferReader);
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType);
// Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented
// methods to be called.
var assemblies = new List<AssemblyDefinition> { m_MainModule.Assembly }; var assemblies = new List<AssemblyDefinition> { m_MainModule.Assembly };
foreach (var reference in m_MainModule.AssemblyReferences) foreach (var reference in m_MainModule.AssemblyReferences)
{ {
@@ -371,9 +673,170 @@ namespace Unity.Netcode.Editor.CodeGen
} }
} }
foreach (var method in networkVariableSerializationTypesTypeDef.Methods)
{
if (!method.IsStatic)
{
continue;
}
switch (method.Name)
{
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy):
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable):
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable):
m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString):
m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method;
break;
}
}
foreach (var method in bytePackerTypeDef.Methods)
{
if (!method.IsStatic)
{
continue;
}
switch (method.Name)
{
case nameof(BytePacker.WriteValueBitPacked):
if (method.Parameters[1].ParameterType.FullName == typeof(short).FullName)
{
m_BytePacker_WriteValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).FullName)
{
m_BytePacker_WriteValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(int).FullName)
{
m_BytePacker_WriteValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(uint).FullName)
{
m_BytePacker_WriteValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(long).FullName)
{
m_BytePacker_WriteValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).FullName)
{
m_BytePacker_WriteValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method);
}
break;
}
}
foreach (var method in byteUnpackerTypeDef.Methods)
{
if (!method.IsStatic)
{
continue;
}
switch (method.Name)
{
case nameof(ByteUnpacker.ReadValueBitPacked):
if (method.Parameters[1].ParameterType.FullName == typeof(short).MakeByRefType().FullName)
{
m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).MakeByRefType().FullName)
{
m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(int).MakeByRefType().FullName)
{
m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(uint).MakeByRefType().FullName)
{
m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(long).MakeByRefType().FullName)
{
m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method);
}
else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).MakeByRefType().FullName)
{
m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method);
}
break;
}
}
return true; return true;
} }
// This gets all fields from this type as well as any parent types, up to (but not including) the base NetworkBehaviour class
// Importantly... this also resolves any generics, so if the base class is Foo<T> and contains a field of NetworkVariable<T>,
// and this class is Bar : Foo<int>, it will properly resolve NetworkVariable<T> to NetworkVariable<int>.
private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List<TypeReference> fieldTypes, Dictionary<string, TypeReference> genericParameters = null)
{
foreach (var field in type.Fields)
{
if (field.FieldType.IsGenericInstance)
{
var genericType = (GenericInstanceType)field.FieldType;
var newGenericType = new GenericInstanceType(field.FieldType.Resolve());
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
var argument = genericType.GenericArguments[i];
if (genericParameters != null && genericParameters.ContainsKey(argument.Name))
{
newGenericType.GenericArguments.Add(genericParameters[argument.Name]);
}
else
{
newGenericType.GenericArguments.Add(argument);
}
}
fieldTypes.Add(newGenericType);
}
else
{
fieldTypes.Add(field.FieldType);
}
}
if (type.BaseType == null || type.BaseType.Name == nameof(NetworkBehaviour))
{
return;
}
var genericParams = new Dictionary<string, TypeReference>();
var resolved = type.BaseType.Resolve();
if (type.BaseType.IsGenericInstance)
{
var genericType = (GenericInstanceType)type.BaseType;
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
}
}
GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams);
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines) private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines)
{ {
var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>(); var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>();
@@ -416,6 +879,28 @@ namespace Unity.Netcode.Editor.CodeGen
} }
} }
if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance)
{
var fieldTypes = new List<TypeReference>();
GetAllFieldsAndResolveGenerics(typeDefinition, ref fieldTypes);
foreach (var type in fieldTypes)
{
//var type = field.FieldType;
if (type.IsGenericInstance)
{
if (type.Resolve().Name == typeof(NetworkVariable<>).Name || type.Resolve().Name == typeof(NetworkList<>).Name)
{
var genericInstanceType = (GenericInstanceType)type;
var wrappedType = genericInstanceType.GenericArguments[0];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}
}
}
if (rpcHandlers.Count > 0 || rpcNames.Count > 0) if (rpcHandlers.Count > 0 || rpcNames.Count > 0)
{ {
var staticCtorMethodDef = typeDefinition.GetStaticConstructor(); var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
@@ -656,6 +1141,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);
@@ -669,7 +1184,7 @@ namespace Unity.Netcode.Editor.CodeGen
{ {
if (parameters[1].IsIn) if (parameters[1].IsIn)
{ {
if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() && if (((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName &&
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
{ {
methodRef = method; methodRef = method;
@@ -679,8 +1194,7 @@ namespace Unity.Netcode.Editor.CodeGen
} }
else else
{ {
if (parameters[1].ParameterType.FullName == paramType.FullName &&
if (parameters[1].ParameterType.Resolve() == paramType.Resolve() &&
parameters[1].ParameterType.IsArray == paramType.IsArray) parameters[1].ParameterType.IsArray == paramType.IsArray)
{ {
methodRef = method; methodRef = method;
@@ -803,6 +1317,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);
@@ -813,7 +1357,7 @@ namespace Unity.Netcode.Editor.CodeGen
var parameters = method.Resolve().Parameters; var parameters = method.Resolve().Parameters;
if (method.Name == k_ReadValueMethodName && if (method.Name == k_ReadValueMethodName &&
parameters[1].IsOut && parameters[1].IsOut &&
parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() && ((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName &&
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
{ {
methodRef = method; methodRef = method;

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 2c61e8fe9a68a486fbbc3128d233ded2 guid: 52153943c346dd04e8712ab540ab9c22
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -0,0 +1,39 @@
using UnityEditor;
namespace Unity.Netcode.Editor.Configuration
{
internal class NetcodeForGameObjectsSettings
{
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);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c2e5a740c1abd4315801e3f26ecf8adb guid: 2f9c9b10bc41a0e46ab71324dd0ac6e1
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -0,0 +1,94 @@
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor.Configuration
{
internal static class NetcodeSettingsProvider
{
[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 = new NetcodeSettingsLabel("NetworkObject Helper Settings", 20);
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObjects", "When enabled, NetworkObjects are automatically added to GameObjects when NetworkBehaviours are added first.", 20);
internal static NetcodeSettingsLabel MultiplayerToolsLabel = new NetcodeSettingsLabel("Multiplayer Tools", 20);
internal static NetcodeSettingsToggle 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)
{
var autoAddNetworkObjectSetting = NetcodeForGameObjectsSettings.GetAutoAddNetworkObjectSetting();
var multiplayerToolsTipStatus = NetcodeForGameObjectsSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
EditorGUI.BeginChangeCheck();
NetworkObjectsSectionLabel.DrawLabel();
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
MultiplayerToolsLabel.DrawLabel();
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);
if (EditorGUI.EndChangeCheck())
{
NetcodeForGameObjectsSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
NetcodeForGameObjectsSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
}
}
}
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;
AdjustLableSize(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)
{
AdjustLableSize(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 AdjustLableSize(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,81 @@
using Unity.Netcode.Components;
#if UNITY_UNET_PRESENT
using Unity.Netcode.Transports.UNET;
#endif
using Unity.Netcode.Transports.UTP;
using UnityEditor;
namespace Unity.Netcode.Editor
{
/// <summary>
/// Internal use. Hides the script field for the given component.
/// </summary>
public class HiddenScriptEditor : UnityEditor.Editor
{
private static readonly string[] k_HiddenFields = { "m_Script" };
/// <summary>
/// Draws inspector properties without the script field.
/// </summary>
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
serializedObject.UpdateIfRequiredOrScript();
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
}
}
#if UNITY_UNET_PRESENT
/// <summary>
/// Internal use. Hides the script field for UNetTransport.
/// </summary>
[CustomEditor(typeof(UNetTransport), true)]
public class UNetTransportEditor : HiddenScriptEditor
{
}
#endif
/// <summary>
/// Internal use. Hides the script field for UnityTransport.
/// </summary>
[CustomEditor(typeof(UnityTransport), true)]
public class UnityTransportEditor : HiddenScriptEditor
{
}
#if COM_UNITY_MODULES_ANIMATION
/// <summary>
/// Internal use. Hides the script field for NetworkAnimator.
/// </summary>
[CustomEditor(typeof(NetworkAnimator), true)]
public class NetworkAnimatorEditor : HiddenScriptEditor
{
}
#endif
#if COM_UNITY_MODULES_PHYSICS
/// <summary>
/// Internal use. Hides the script field for NetworkRigidbody.
/// </summary>
[CustomEditor(typeof(NetworkRigidbody), true)]
public class NetworkRigidbodyEditor : HiddenScriptEditor
{
}
#endif
#if COM_UNITY_MODULES_PHYSICS2D
/// <summary>
/// Internal use. Hides the script field for NetworkRigidbody2D.
/// </summary>
[CustomEditor(typeof(NetworkRigidbody2D), true)]
public class NetworkRigidbody2DEditor : HiddenScriptEditor
{
}
#endif
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ebf622cc80e94f488e59caf8b7419f50
timeCreated: 1661959406

View File

@@ -3,9 +3,13 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
using Unity.Netcode.Editor.Configuration;
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
@@ -33,8 +37,8 @@ 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]);
} }
} }
} }
@@ -151,6 +155,8 @@ namespace Unity.Netcode.Editor
} }
} }
/// <inheritdoc/>
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
if (!m_Initialized) if (!m_Initialized)
@@ -228,8 +234,11 @@ namespace Unity.Netcode.Editor
CheckForNetworkObject((target as NetworkBehaviour).gameObject); CheckForNetworkObject((target as NetworkBehaviour).gameObject);
} }
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist"; /// <summary>
/// Recursively finds the root parent of a <see cref="Transform"/>
/// </summary>
/// <param name="transform">The current <see cref="Transform"/> we are inspecting for a parent</param>
/// <returns>the root parent for the first <see cref="Transform"/> passed into the method</returns>
public static Transform GetRootParentTransform(Transform transform) public static Transform GetRootParentTransform(Transform transform)
{ {
if (transform.parent == null || transform.parent == transform) if (transform.parent == null || transform.parent == transform)
@@ -244,6 +253,8 @@ namespace Unity.Netcode.Editor
/// does not already have a NetworkObject component. If not it will notify /// does not already have a NetworkObject component. If not it will notify
/// the user that NetworkBehaviours require a NetworkObject. /// the user that NetworkBehaviours require a NetworkObject.
/// </summary> /// </summary>
/// <param name="gameObject"><see cref="GameObject"/> to start checking for a <see cref="NetworkObject"/></param>
/// <param name="networkObjectRemoved">used internally</param>
public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false) public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false)
{ {
// If there are no NetworkBehaviours or no gameObject, then exit early // If there are no NetworkBehaviours or no gameObject, then exit early
@@ -254,8 +265,7 @@ namespace Unity.Netcode.Editor
// Now get the root parent transform to the current GameObject (or itself) // Now get the root parent transform to the current GameObject (or itself)
var rootTransform = GetRootParentTransform(gameObject.transform); var rootTransform = GetRootParentTransform(gameObject.transform);
var networkManager = rootTransform.GetComponent<NetworkManager>(); if (!rootTransform.TryGetComponent<NetworkManager>(out var networkManager))
if (networkManager == null)
{ {
networkManager = rootTransform.GetComponentInChildren<NetworkManager>(); networkManager = rootTransform.GetComponentInChildren<NetworkManager>();
} }
@@ -290,8 +300,7 @@ namespace Unity.Netcode.Editor
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children. // Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component. // If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
var networkObject = rootTransform.GetComponent<NetworkObject>(); if (!rootTransform.TryGetComponent<NetworkObject>(out var networkObject))
if (networkObject == null)
{ {
networkObject = rootTransform.GetComponentInChildren<NetworkObject>(); networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
@@ -301,7 +310,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 && NetcodeForGameObjectsSettings.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.");
@@ -310,7 +319,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, NetcodeForGameObjectsSettings.AutoAddNetworkObjectIfNoneExists))
{ {
gameObject.AddComponent<NetworkObject>(); gameObject.AddComponent<NetworkObject>();
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
@@ -320,20 +329,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

@@ -3,14 +3,18 @@ using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEditorInternal; using UnityEditorInternal;
using Unity.Netcode.Editor.Configuration;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
/// <summary>
/// This <see cref="CustomEditor"/> handles the translation between the <see cref="NetworkConfig"/> and
/// the <see cref="NetworkManager"/> properties.
/// </summary>
[CustomEditor(typeof(NetworkManager), true)] [CustomEditor(typeof(NetworkManager), true)]
[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;
@@ -200,6 +204,7 @@ namespace Unity.Netcode.Editor
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs"); m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
} }
/// <inheritdoc/>
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
Initialize(); Initialize();
@@ -209,18 +214,6 @@ namespace Unity.Netcode.Editor
DrawInstallMultiplayerToolsTip(); DrawInstallMultiplayerToolsTip();
#endif #endif
{
var iterator = serializedObject.GetIterator();
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
{
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
{
EditorGUILayout.PropertyField(iterator, false);
}
}
}
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
{ {
serializedObject.Update(); serializedObject.Update();
@@ -366,7 +359,7 @@ 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 (NetcodeForGameObjectsSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
{ {
return; return;
} }
@@ -412,7 +405,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); NetcodeForGameObjectsSettings.SetNetcodeInstallMultiplayerToolTips(1);
} }
EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link); EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();

View File

@@ -64,8 +64,12 @@ 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.Where((c) => c.path == activeScene.path).Count() == 1; var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1;
#if UNITY_2023_1_OR_NEWER
var networkManager = Object.FindFirstObjectByType<NetworkManager>();
#else
var networkManager = Object.FindObjectOfType<NetworkManager>(); 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)
@@ -109,9 +113,8 @@ namespace Unity.Netcode.Editor
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false) public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
{ {
// Check for any NetworkObject at the same gameObject relative layer // Check for any NetworkObject at the same gameObject relative layer
var networkObject = networkManager.gameObject.GetComponent<NetworkObject>();
if (networkObject == null) if (!networkManager.gameObject.TryGetComponent<NetworkObject>(out var networkObject))
{ {
// if none is found, check to see if any children have a NetworkObject // if none is found, check to see if any children have a NetworkObject
networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>(); networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>();

View File

@@ -4,6 +4,9 @@ using UnityEditor;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="NetworkObject"/>
/// </summary>
[CustomEditor(typeof(NetworkObject), true)] [CustomEditor(typeof(NetworkObject), true)]
[CanEditMultipleObjects] [CanEditMultipleObjects]
public class NetworkObjectEditor : UnityEditor.Editor public class NetworkObjectEditor : UnityEditor.Editor
@@ -12,6 +15,8 @@ namespace Unity.Netcode.Editor
private NetworkObject m_NetworkObject; private NetworkObject m_NetworkObject;
private bool m_ShowObservers; private bool m_ShowObservers;
private static readonly string[] k_HiddenFields = { "m_Script" };
private void Initialize() private void Initialize()
{ {
if (m_Initialized) if (m_Initialized)
@@ -23,6 +28,7 @@ namespace Unity.Netcode.Editor
m_NetworkObject = (NetworkObject)target; m_NetworkObject = (NetworkObject)target;
} }
/// <inheritdoc/>
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
Initialize(); Initialize();
@@ -91,7 +97,11 @@ namespace Unity.Netcode.Editor
} }
else else
{ {
base.OnInspectorGUI(); EditorGUI.BeginChangeCheck();
serializedObject.UpdateIfRequiredOrScript();
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
var guiEnabled = GUI.enabled; var guiEnabled = GUI.enabled;
GUI.enabled = false; GUI.enabled = false;

View File

@@ -4,6 +4,9 @@ using Unity.Netcode.Components;
namespace Unity.Netcode.Editor namespace Unity.Netcode.Editor
{ {
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="NetworkTransform"/>
/// </summary>
[CustomEditor(typeof(NetworkTransform), true)] [CustomEditor(typeof(NetworkTransform), true)]
public class NetworkTransformEditor : UnityEditor.Editor public class NetworkTransformEditor : UnityEditor.Editor
{ {
@@ -28,6 +31,7 @@ namespace Unity.Netcode.Editor
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation"); private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation");
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale"); private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");
/// <inheritdoc/>
public void OnEnable() public void OnEnable()
{ {
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX)); m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
@@ -46,6 +50,7 @@ namespace Unity.Netcode.Editor
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
} }
/// <inheritdoc/>
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel); EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel);

View File

@@ -13,6 +13,26 @@
"name": "com.unity.multiplayer.tools", "name": "com.unity.multiplayer.tools",
"expression": "", "expression": "",
"define": "MULTIPLAYER_TOOLS" "define": "MULTIPLAYER_TOOLS"
},
{
"name": "Unity",
"expression": "(0,2022.2.0a5)",
"define": "UNITY_UNET_PRESENT"
},
{
"name": "com.unity.modules.animation",
"expression": "",
"define": "COM_UNITY_MODULES_ANIMATION"
},
{
"name": "com.unity.modules.physics",
"expression": "",
"define": "COM_UNITY_MODULES_PHYSICS"
},
{
"name": "com.unity.modules.physics2d",
"expression": "",
"define": "COM_UNITY_MODULES_PHYSICS2D"
} }
] ]
} }

View File

@@ -9,7 +9,7 @@ Netcode for GameObjects is a Unity package that provides networking capabilities
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package. Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks. You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld) guide for a taste of how to use the framework for basic networked tasks.
### Community and Feedback ### Community and Feedback

View File

@@ -12,3 +12,4 @@ using System.Runtime.CompilerServices;
[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("Unity.Netcode.Adapter.UTP")]
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")]

View File

@@ -150,8 +150,16 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public bool EnableNetworkLogs = true; public bool EnableNetworkLogs = true;
/// <summary>
/// The number of RTT samples that is kept as an average for calculations
/// </summary>
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one) public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
/// <summary>
/// The number of slots used for RTT calculations. This is the maximum amount of in-flight messages
/// </summary>
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets) public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
/// <summary> /// <summary>
/// Returns a base64 encoded version of the configuration /// Returns a base64 encoded version of the configuration
/// </summary> /// </summary>

View File

@@ -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);
@@ -235,16 +236,44 @@ namespace Unity.Netcode
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{ {
foreach (var client in NetworkManager.ConnectedClients) if (clientRpcParams.Send.TargetClientIds != null)
{
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
{ {
NetworkManager.NetworkMetrics.TrackRpcSent( NetworkManager.NetworkMetrics.TrackRpcSent(
client.Key, targetClientId,
NetworkObject, NetworkObject,
rpcMethodName, rpcMethodName,
__getTypeName(), __getTypeName(),
rpcWriteSize); rpcWriteSize);
} }
} }
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
{
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
targetClientId,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
else
{
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
while (observerEnumerator.MoveNext())
{
NetworkManager.NetworkMetrics.TrackRpcSent(
observerEnumerator.Current,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
}
#endif #endif
} }
@@ -258,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
@@ -307,31 +347,38 @@ 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
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?) // in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress)) // We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy)
if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
@@ -435,15 +482,42 @@ namespace Unity.Netcode
IsSpawned = true; IsSpawned = true;
InitializeVariables(); InitializeVariables();
UpdateNetworkProperties(); UpdateNetworkProperties();
}
internal void VisibleOnNetworkSpawn()
{
try
{
OnNetworkSpawn(); OnNetworkSpawn();
} }
catch (Exception e)
{
Debug.LogException(e);
}
InitializeVariables();
if (IsServer)
{
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
// NetworkList, we need to mark the object as free of updates.
// This should happen for all objects on the machine triggering the spawn.
PostNetworkVariableWrite(true);
}
}
internal void InternalOnNetworkDespawn() internal void InternalOnNetworkDespawn()
{ {
IsSpawned = false; IsSpawned = false;
UpdateNetworkProperties(); UpdateNetworkProperties();
try
{
OnNetworkDespawn(); OnNetworkDespawn();
} }
catch (Exception e)
{
Debug.LogException(e);
}
}
/// <summary> /// <summary>
/// Gets called when the local client gains ownership of this object /// Gets called when the local client gains ownership of this object
@@ -470,6 +544,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed /// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
/// </summary> /// </summary>
/// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param>
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
private bool m_VarInit = false; private bool m_VarInit = false;
@@ -574,7 +649,18 @@ namespace Unity.Netcode
NetworkVariableIndexesToResetSet.Clear(); NetworkVariableIndexesToResetSet.Clear();
} }
internal void PostNetworkVariableWrite() internal void PostNetworkVariableWrite(bool forced = false)
{
if (forced)
{
// Mark every variable as no longer dirty. We just spawned the object and whatever the game code did
// during OnNetworkSpawn has been sent and needs to be cleared
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].ResetDirty();
}
}
else
{ {
// mark any variables we wrote as no longer dirty // mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++) for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
@@ -583,7 +669,10 @@ namespace Unity.Netcode
} }
} }
internal void VariableUpdate(ulong targetClientId) MarkVariablesDirty(false);
}
internal void PreVariableUpdate()
{ {
if (!m_VarInit) if (!m_VarInit)
{ {
@@ -591,6 +680,10 @@ namespace Unity.Netcode
} }
PreNetworkVariableWrite(); PreNetworkVariableWrite();
}
internal void VariableUpdate(ulong targetClientId)
{
NetworkVariableUpdate(targetClientId, NetworkBehaviourId); NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
} }
@@ -637,7 +730,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
@@ -662,14 +755,22 @@ namespace Unity.Netcode
return false; return false;
} }
internal void MarkVariablesDirty() internal void MarkVariablesDirty(bool dirty)
{ {
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
NetworkVariableFields[j].SetDirty(true); NetworkVariableFields[j].SetDirty(dirty);
} }
} }
/// <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)
@@ -679,11 +780,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);
@@ -693,13 +800,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)
{ {
@@ -708,13 +829,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)
@@ -751,8 +882,153 @@ 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>
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
}
/// <summary>
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
/// </summary>
/// <remarks>
/// This includes try-catch blocks to recover from exceptions that might occur and continue to
/// synchronize any remaining NetworkBehaviours.
/// </remarks>
/// <returns>true if it wrote synchronization data and false if it did not</returns>
internal bool Synchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsWriter)
{
// Get the writer to handle seeking and determining how many bytes were written
var writer = serializer.GetFastBufferWriter();
// Save our position before we attempt to write anything so we can seek back to it (i.e. error occurs)
var positionBeforeWrite = writer.Position;
writer.WriteValueSafe(NetworkBehaviourId);
// Save our position where we will write the final size being written so we can skip over it in the
// event an exception occurs when deserializing.
var sizePosition = writer.Position;
writer.WriteValueSafe((ushort)0);
// Save our position before synchronizing to determine how much was written
var positionBeforeSynchronize = writer.Position;
var threwException = false;
try
{
OnSynchronize(ref serializer);
}
catch (Exception ex)
{
threwException = true;
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} threw an exception during synchronization serialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
}
}
}
var finalPosition = writer.Position;
// If we wrote nothing then skip writing anything for this NetworkBehaviour
if (finalPosition == positionBeforeSynchronize || threwException)
{
writer.Seek(positionBeforeWrite);
return false;
}
else
{
// Write the number of bytes serialized to handle exceptions on the deserialization side
var bytesWritten = finalPosition - positionBeforeSynchronize;
writer.Seek(sizePosition);
writer.WriteValueSafe((ushort)bytesWritten);
writer.Seek(finalPosition);
}
return true;
}
else
{
var reader = serializer.GetFastBufferReader();
// We will always read the expected byte count
reader.ReadValueSafe(out ushort expectedBytesToRead);
// Save our position before we begin synchronization deserialization
var positionBeforeSynchronize = reader.Position;
var synchronizationError = false;
try
{
// Invoke synchronization
OnSynchronize(ref serializer);
}
catch (Exception ex)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} threw an exception during synchronization deserialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
}
}
synchronizationError = true;
}
var totalBytesRead = reader.Position - positionBeforeSynchronize;
if (totalBytesRead != expectedBytesToRead)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
}
synchronizationError = true;
}
// Skip over the entry if deserialization fails
if (synchronizationError)
{
var skipToPosition = positionBeforeSynchronize + expectedBytesToRead;
reader.Seek(skipToPosition);
return false;
}
return true;
}
}
/// <summary>
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to.
/// NOTE: If you override this, you will want to always invoke this base class version of this
/// <see cref="OnDestroy"/> method!!
/// </summary>
public virtual void OnDestroy() public virtual void OnDestroy()
{ {
if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned)
{
// If the associated NetworkObject is still spawned then this
// NetworkBehaviour will be removed from the NetworkObject's
// ChildNetworkBehaviours list.
NetworkObject.OnNetworkBehaviourDestroyed(this);
}
// this seems odd to do here, but in fact especially in tests we can find ourselves // this seems odd to do here, but in fact especially in tests we can find ourselves
// here without having called InitializedVariables, which causes problems if any // here without having called InitializedVariables, which causes problems if any
// of those variables use native containers (e.g. NetworkList) as they won't be // of those variables use native containers (e.g. NetworkList) as they won't be
@@ -764,6 +1040,7 @@ namespace Unity.Netcode
InitializeVariables(); InitializeVariables();
} }
for (int i = 0; i < NetworkVariableFields.Count; i++) for (int i = 0; i < NetworkVariableFields.Count; i++)
{ {
NetworkVariableFields[i].Dispose(); NetworkVariableFields[i].Dispose();

View File

@@ -3,14 +3,22 @@ using Unity.Profiling;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// An helper class that helps NetworkManager update NetworkBehaviours and replicate them down to connected clients.
/// </summary>
public class NetworkBehaviourUpdater public class NetworkBehaviourUpdater
{ {
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>(); private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}"); private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
#endif #endif
internal void AddForUpdate(NetworkObject networkObject)
{
m_DirtyNetworkObjects.Add(networkObject);
}
internal void NetworkBehaviourUpdate(NetworkManager networkManager) internal void NetworkBehaviourUpdate(NetworkManager networkManager)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -18,59 +26,58 @@ namespace Unity.Netcode
#endif #endif
try try
{ {
// NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point
// trying to process them, even if they were previously marked as dirty.
m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null);
if (networkManager.IsServer) if (networkManager.IsServer)
{ {
m_Touched.Clear(); foreach (var dirtyObj in m_DirtyNetworkObjects)
{
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
{
dirtyObj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++) for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
{ {
var client = networkManager.ConnectedClientsList[i]; var client = networkManager.ConnectedClientsList[i];
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList;
m_Touched.UnionWith(spawnedObjs); if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
foreach (var sobj in spawnedObjs)
{
if (sobj.IsNetworkVisibleTo(client.ClientId))
{ {
// Sync just the variables for just the objects this client sees // Sync just the variables for just the objects this client sees
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
{ {
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId); dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
} }
} }
} }
} }
// Now, reset all the no-longer-dirty variables
foreach (var sobj in m_Touched)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
} }
else else
{ {
// when client updates the server, it tells it about all its objects // when client updates the server, it tells it about all its objects
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList) foreach (var sobj in m_DirtyNetworkObjects)
{ {
if (sobj.IsOwner) if (sobj.IsOwner)
{ {
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{ {
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId); sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
} }
} }
} }
}
// Now, reset all the no-longer-dirty variables // Now, reset all the no-longer-dirty variables
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList) foreach (var dirtyobj in m_DirtyNetworkObjects)
{ {
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) dirtyobj.PostNetworkVariableWrite();
{
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
} }
m_DirtyNetworkObjects.Clear();
} }
finally finally
{ {

View File

@@ -20,7 +20,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// The main component of the library /// The main component of the library
/// </summary> /// </summary>
[AddComponentMenu("Netcode/" + nameof(NetworkManager), -100)] [AddComponentMenu("Netcode/Network Manager", -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{ {
#pragma warning disable IDE1006 // disable naming rule violation check #pragma warning disable IDE1006 // disable naming rule violation check
@@ -51,10 +51,15 @@ namespace Unity.Netcode
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab) internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
{ {
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\""; return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\"";
} }
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; } internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
internal void MarkNetworkObjectDirty(NetworkObject networkObject)
{
BehaviourUpdater.AddForUpdate(networkObject);
}
internal MessagingSystem MessagingSystem { get; private set; } internal MessagingSystem MessagingSystem { get; private set; }
@@ -62,6 +67,9 @@ namespace Unity.Netcode
internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>(); internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>();
/// <summary>
/// The <see cref="NetworkPrefabHandler"/> instance created after starting the <see cref="NetworkManager"/>
/// </summary>
public NetworkPrefabHandler PrefabHandler public NetworkPrefabHandler PrefabHandler
{ {
get get
@@ -78,6 +86,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;
@@ -179,8 +193,7 @@ namespace Unity.Netcode
/// <returns>a <see cref="GameObject"/> that is either the override or if no overrides exist it returns the same as the one passed in as a parameter</returns> /// <returns>a <see cref="GameObject"/> that is either the override or if no overrides exist it returns the same as the one passed in as a parameter</returns>
public GameObject GetNetworkPrefabOverride(GameObject gameObject) public GameObject GetNetworkPrefabOverride(GameObject gameObject)
{ {
var networkObject = gameObject.GetComponent<NetworkObject>(); if (gameObject.TryGetComponent<NetworkObject>(out var networkObject))
if (networkObject != null)
{ {
if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash)) if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{ {
@@ -197,12 +210,25 @@ namespace Unity.Netcode
return gameObject; return gameObject;
} }
/// <summary>
/// Accessor for the <see cref="NetworkTimeSystem"/> of the NetworkManager.
/// Prefer the use of the LocalTime and ServerTime properties
/// </summary>
public NetworkTimeSystem NetworkTimeSystem { get; private set; } public NetworkTimeSystem NetworkTimeSystem { get; private set; }
/// <summary>
/// Accessor for the <see cref="NetworkTickSystem"/> of the NetworkManager.
/// </summary>
public NetworkTickSystem NetworkTickSystem { get; private set; } public NetworkTickSystem NetworkTickSystem { get; private set; }
/// <summary>
/// The local <see cref="NetworkTime"/>
/// </summary>
public NetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default; public NetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default;
/// <summary>
/// The <see cref="NetworkTime"/> on the server
/// </summary>
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default; public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
/// <summary> /// <summary>
@@ -229,10 +255,19 @@ namespace Unity.Netcode
internal IDeferredMessageManager DeferredMessageManager { get; private set; } internal IDeferredMessageManager DeferredMessageManager { get; private set; }
/// <summary>
/// Gets the CustomMessagingManager for this NetworkManager
/// </summary>
public CustomMessagingManager CustomMessagingManager { get; private set; } public CustomMessagingManager CustomMessagingManager { get; private set; }
/// <summary>
/// The <see cref="NetworkSceneManager"/> instance created after starting the <see cref="NetworkManager"/>
/// </summary>
public NetworkSceneManager SceneManager { get; private set; } public NetworkSceneManager SceneManager { get; private set; }
/// <summary>
/// The client id used to represent the server
/// </summary>
public const ulong ServerClientId = 0; public const ulong ServerClientId = 0;
/// <summary> /// <summary>
@@ -337,11 +372,25 @@ namespace Unity.Netcode
public bool IsListening { get; internal set; } public bool IsListening { get; internal set; }
/// <summary> /// <summary>
/// Gets if we are connected as a client /// When true, the client is connected, approved, and synchronized with
/// the server.
/// </summary> /// </summary>
public bool IsConnectedClient { get; internal set; } public bool IsConnectedClient { get; internal set; }
/// <summary>
/// Is true when the client has been approved.
/// </summary>
/// <remarks>
/// This only reflects the client's approved status and does not mean the client
/// has finished the connection and synchronization process. The server-host will
/// always be approved upon being starting the <see cref="NetworkManager"/>
/// <see cref="IsConnectedClient"/>
/// </remarks>
public bool IsApproved { get; internal set; }
/// <summary>
/// Can be used to determine if the <see cref="NetworkManager"/> is currently shutting itself down
/// </summary>
public bool ShutdownInProgress { get { return m_ShuttingDown; } } public bool ShutdownInProgress { get { return m_ShuttingDown; } }
/// <summary> /// <summary>
@@ -374,30 +423,52 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Connection Approval Response /// Connection Approval Response
/// </summary> /// </summary>
/// <param name="Approved">Whether or not the client was approved</param>
/// <param name="CreatePlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
/// <param name="PlayerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
/// <param name="Position">The position to spawn the client at. If null, the prefab position is used.</param>
/// <param name="Rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
/// <param name="Pending">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.</param>
public class ConnectionApprovalResponse public class ConnectionApprovalResponse
{ {
/// <summary>
/// Whether or not the client was approved
/// </summary>
public bool Approved; public bool Approved;
/// <summary>
/// If true, a player object will be created. Otherwise the client will have no object.
/// </summary>
public bool CreatePlayerObject; public bool CreatePlayerObject;
/// <summary>
/// The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.
/// </summary>
public uint? PlayerPrefabHash; public uint? PlayerPrefabHash;
/// <summary>
/// The position to spawn the client at. If null, the prefab position is used.
/// </summary>
public Vector3? Position; public Vector3? Position;
/// <summary>
/// The rotation to spawn the client with. If null, the prefab position is used.
/// </summary>
public Quaternion? Rotation; public Quaternion? Rotation;
/// <summary>
/// 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>
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>
/// Connection Approval Request /// Connection Approval Request
/// </summary> /// </summary>
/// <param name="Payload">The connection data payload</param>
/// <param name="ClientNetworkId">The Network Id of the client we are about to handle</param>
public struct ConnectionApprovalRequest public struct ConnectionApprovalRequest
{ {
/// <summary>
/// The connection data payload
/// </summary>
public byte[] Payload; public byte[] Payload;
/// <summary>
/// The Network Id of the client we are about to handle
/// </summary>
public ulong ClientNetworkId; public ulong ClientNetworkId;
} }
@@ -471,8 +542,7 @@ namespace Unity.Netcode
var networkPrefabGo = networkPrefab?.Prefab; var networkPrefabGo = networkPrefab?.Prefab;
if (networkPrefabGo != null) if (networkPrefabGo != null)
{ {
var networkObject = networkPrefabGo.GetComponent<NetworkObject>(); if (!networkPrefabGo.TryGetComponent<NetworkObject>(out var networkObject))
if (networkObject == null)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
@@ -647,8 +717,7 @@ namespace Unity.Netcode
} }
else if (networkPrefab.Override == NetworkPrefabOverride.None) else if (networkPrefab.Override == NetworkPrefabOverride.None)
{ {
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>(); if (!networkPrefab.Prefab.TryGetComponent(out networkObject))
if (networkObject == null)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{ {
@@ -694,8 +763,7 @@ namespace Unity.Netcode
} }
else else
{ {
networkObject = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>(); if (!networkPrefab.SourcePrefabToOverride.TryGetComponent(out networkObject))
if (networkObject == null)
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{ {
@@ -833,6 +901,9 @@ namespace Unity.Netcode
return; return;
} }
DisconnectReason = string.Empty;
IsApproved = false;
ComponentFactory.SetDefaults(); ComponentFactory.SetDefaults();
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -920,8 +991,7 @@ namespace Unity.Netcode
// 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)
{ {
var playerPrefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>(); if (NetworkConfig.PlayerPrefab.TryGetComponent<NetworkObject>(out var playerPrefabNetworkObject))
if (playerPrefabNetworkObject != null)
{ {
//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.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject
@@ -961,6 +1031,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Starts a server /// Starts a server
/// </summary> /// </summary>
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in server mode successfully.</returns>
public bool StartServer() public bool StartServer()
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -974,25 +1045,39 @@ namespace Unity.Netcode
} }
Initialize(true); Initialize(true);
// If we failed to start then shutdown and notify user that the transport failed to start
if (NetworkConfig.NetworkTransport.StartServer())
{
IsServer = true; IsServer = true;
IsClient = false; IsClient = false;
IsListening = true; IsListening = true;
try
{
// If we failed to start then shutdown and notify user that the transport failed to start
if (NetworkConfig.NetworkTransport.StartServer())
{
SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
OnServerStarted?.Invoke(); OnServerStarted?.Invoke();
IsApproved = true;
return true; return true;
} }
else else
{ {
IsServer = false;
IsClient = false;
IsListening = false;
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!"); Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
OnTransportFailure?.Invoke(); OnTransportFailure?.Invoke();
Shutdown(); Shutdown();
} }
}
catch (Exception)
{
IsServer = false;
IsClient = false;
IsListening = false;
throw;
}
return false; return false;
} }
@@ -1000,6 +1085,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Starts a client /// Starts a client
/// </summary> /// </summary>
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in client mode successfully.</returns>
public bool StartClient() public bool StartClient()
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -1033,6 +1119,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Starts a Host /// Starts a Host
/// </summary> /// </summary>
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in host mode successfully.</returns>
public bool StartHost() public bool StartHost()
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
@@ -1047,23 +1134,38 @@ namespace Unity.Netcode
Initialize(true); Initialize(true);
IsServer = true;
IsClient = true;
IsListening = true;
try
{
// If we failed to start then shutdown and notify user that the transport failed to start // If we failed to start then shutdown and notify user that the transport failed to start
if (!NetworkConfig.NetworkTransport.StartServer()) if (!NetworkConfig.NetworkTransport.StartServer())
{ {
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!"); Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
OnTransportFailure?.Invoke(); OnTransportFailure?.Invoke();
Shutdown(); Shutdown();
IsServer = false;
IsClient = false;
IsListening = false;
return false; return false;
} }
}
catch (Exception)
{
IsServer = false;
IsClient = false;
IsListening = false;
throw;
}
MessagingSystem.ClientConnected(ServerClientId); MessagingSystem.ClientConnected(ServerClientId);
LocalClientId = ServerClientId; LocalClientId = ServerClientId;
NetworkMetrics.SetConnectionId(LocalClientId); NetworkMetrics.SetConnectionId(LocalClientId);
IsServer = true;
IsClient = true;
IsListening = true;
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null) if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
{ {
var response = new ConnectionApprovalResponse(); var response = new ConnectionApprovalResponse();
@@ -1077,6 +1179,7 @@ namespace Unity.Netcode
} }
response.Approved = true; response.Approved = true;
IsApproved = true;
HandleConnectionApproval(ServerClientId, response); HandleConnectionApproval(ServerClientId, response);
} }
else else
@@ -1091,6 +1194,11 @@ namespace Unity.Netcode
SpawnManager.ServerSpawnSceneObjectsOnStartSweep(); SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
// 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);
OnServerStarted?.Invoke(); OnServerStarted?.Invoke();
return true; return true;
@@ -1144,6 +1252,9 @@ namespace Unity.Netcode
return true; return true;
} }
/// <summary>
/// Set this NetworkManager instance as the static NetworkManager singleton
/// </summary>
public void SetSingleton() public void SetSingleton()
{ {
Singleton = this; Singleton = this;
@@ -1248,6 +1359,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);
} }
@@ -1328,13 +1440,27 @@ namespace Unity.Netcode
} }
} }
if (IsClient && IsConnectedClient) if (IsClient && IsListening)
{ {
// Client only, send disconnect to server // Client only, send disconnect to server
NetworkConfig.NetworkTransport.DisconnectLocalClient(); NetworkConfig.NetworkTransport.DisconnectLocalClient();
} }
IsConnectedClient = false; IsConnectedClient = false;
IsApproved = false;
// We need to clean up NetworkObjects before we reset the IsServer
// and IsClient properties. This provides consistency of these two
// property values for NetworkObjects that are still spawned when
// the shutdown cycle begins.
if (SpawnManager != null)
{
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
IsServer = false; IsServer = false;
IsClient = false; IsClient = false;
@@ -1357,14 +1483,6 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll; NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
} }
if (SpawnManager != null)
{
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
if (DeferredMessageManager != null) if (DeferredMessageManager != null)
{ {
DeferredMessageManager.CleanupAllTriggers(); DeferredMessageManager.CleanupAllTriggers();
@@ -1405,7 +1523,7 @@ namespace Unity.Netcode
ClearClients(); ClearClients();
} }
// INetworkUpdateSystem /// <inheritdoc />
public void NetworkUpdate(NetworkUpdateStage updateStage) public void NetworkUpdate(NetworkUpdateStage updateStage)
{ {
switch (updateStage) switch (updateStage)
@@ -1480,6 +1598,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();
@@ -1501,7 +1620,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.deltaTime); var reset = NetworkTimeSystem.Advance(Time.unscaledDeltaTime);
if (reset) if (reset)
{ {
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime); NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
@@ -1510,7 +1629,7 @@ namespace Unity.Netcode
if (IsServer == false) if (IsServer == false)
{ {
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.deltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d); NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.unscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
} }
} }
@@ -1555,33 +1674,101 @@ namespace Unity.Netcode
{ {
var message = new ConnectionRequestMessage var message = new ConnectionRequestMessage
{ {
ConfigHash = NetworkConfig.GetConfig(), // Since only a remote client will send a connection request,
// we should always force the rebuilding of the NetworkConfig hash value
ConfigHash = NetworkConfig.GetConfig(false),
ShouldSendConnectionData = NetworkConfig.ConnectionApproval, ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData ConnectionData = NetworkConfig.ConnectionData
}; };
message.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)
{ {
NetworkTime timeStarted = LocalTime; var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
var timedOut = false;
var connectionApproved = false;
var connectionNotApproved = false;
var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout;
//We yield every frame incase a pending client disconnects and someone else gets its connection id while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved)
while ((LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId))
{ {
yield return null; yield return null;
// Check if we timed out
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
if (IsServer)
{
// When the client is no longer in the pending clients list and is in the connected clients list
// it has been approved
connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
// For the server side, if the client is in neither list then it was declined or the client disconnected
connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
}
else
{
connectionApproved = IsApproved;
}
} }
if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId)) // Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
if (!IsListening || ShutdownInProgress)
{
yield break;
}
// If the client timed out or was not approved
if (timedOut || connectionNotApproved)
{ {
// Timeout // Timeout
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{ {
NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out"); if (timedOut)
{
if (IsServer)
{
// Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
// (hacking or something happened to the server's network connection)
NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
}
else
{
// We only provide informational logging for the client side
NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
}
}
else if (connectionNotApproved)
{
NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
}
} }
if (IsServer)
{
DisconnectClient(clientId); DisconnectClient(clientId);
} }
else
{
Shutdown(true);
}
}
} }
internal ulong TransportIdToClientId(ulong transportId) internal ulong TransportIdToClientId(ulong transportId)
@@ -1670,7 +1857,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)
{ {
@@ -1836,12 +2034,31 @@ 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();
disconnectReason.Reason = reason;
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
}
MessagingSystem.ProcessSendQueues();
OnClientDisconnectFromServer(clientId); OnClientDisconnectFromServer(clientId);
DisconnectRemoteClient(clientId); DisconnectRemoteClient(clientId);
} }
@@ -1856,6 +2073,8 @@ namespace Unity.Netcode
{ {
var playerObject = networkClient.PlayerObject; var playerObject = networkClient.PlayerObject;
if (playerObject != null) if (playerObject != null)
{
if (!playerObject.DontDestroyWithOwner)
{ {
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash)) if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
{ {
@@ -1866,6 +2085,11 @@ namespace Unity.Netcode
Destroy(playerObject.gameObject); Destroy(playerObject.gameObject);
} }
} }
else
{
playerObject.RemoveOwnership();
}
}
// Get the NetworkObjects owned by the disconnected client // Get the NetworkObjects owned by the disconnected client
var clientOwnedObjects = SpawnManager.GetClientOwnedObjects(clientId); var clientOwnedObjects = SpawnManager.GetClientOwnedObjects(clientId);
@@ -1974,14 +2198,28 @@ namespace Unity.Netcode
if (response.CreatePlayerObject) if (response.CreatePlayerObject)
{ {
var networkObject = SpawnManager.CreateLocalNetworkObject( var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
isSceneObject: false,
response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, // Generate a SceneObject for the player object to spawn
ownerClientId, var sceneObject = new NetworkObject.SceneObject
parentNetworkId: null, {
networkSceneHandle: null, OwnerClientId = ownerClientId,
response.Position, IsPlayerObject = true,
response.Rotation); IsSceneObject = false,
HasTransform = true,
Hash = playerPrefabHash,
TargetClientId = ownerClientId,
Transform = new NetworkObject.SceneObject.TransformData
{
Position = response.Position.GetValueOrDefault(),
Rotation = response.Rotation.GetValueOrDefault()
}
};
// Create the player NetworkObject locally
var networkObject = SpawnManager.CreateLocalNetworkObject(sceneObject);
// Spawn the player NetworkObject locally
SpawnManager.SpawnNetworkObjectLocally( SpawnManager.SpawnNetworkObjectLocally(
networkObject, networkObject,
SpawnManager.GetNetworkObjectId(), SpawnManager.GetNetworkObjectId(),
@@ -2009,7 +2247,22 @@ namespace Unity.Netcode
} }
} }
message.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.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)
@@ -2025,7 +2278,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))
@@ -2038,6 +2290,15 @@ namespace Unity.Netcode
} }
else else
{ {
if (!string.IsNullOrEmpty(response.Reason))
{
var disconnectReason = new DisconnectReasonMessage();
disconnectReason.Reason = response.Reason;
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
MessagingSystem.ProcessSendQueues();
}
PendingClients.Remove(ownerClientId); PendingClients.Remove(ownerClientId);
DisconnectRemoteClient(ownerClientId); DisconnectRemoteClient(ownerClientId);
} }
@@ -2064,11 +2325,11 @@ 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);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,25 +7,55 @@ using UnityEngine.PlayerLoop;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// Defines the required interface of a network update system being executed by the network update loop. /// Defines the required interface of a network update system being executed by the <see cref="NetworkUpdateLoop"/>.
/// </summary> /// </summary>
public interface INetworkUpdateSystem public interface INetworkUpdateSystem
{ {
/// <summary>
/// The update method that is being executed in the context of related <see cref="NetworkUpdateStage"/>.
/// </summary>
/// <param name="updateStage">The <see cref="NetworkUpdateStage"/> that is being executed.</param>
void NetworkUpdate(NetworkUpdateStage updateStage); void NetworkUpdate(NetworkUpdateStage updateStage);
} }
/// <summary> /// <summary>
/// Defines network update stages being executed by the network update loop. /// Defines network update stages being executed by the network update loop.
/// See for more details on update stages:
/// https://docs.unity3d.com/ScriptReference/PlayerLoop.Initialization.html
/// </summary> /// </summary>
public enum NetworkUpdateStage : byte public enum NetworkUpdateStage : byte
{ {
Unset = 0, // Default /// <summary>
/// Default value
/// </summary>
Unset = 0,
/// <summary>
/// Very first initialization update
/// </summary>
Initialization = 1, Initialization = 1,
/// <summary>
/// Invoked before Fixed update
/// </summary>
EarlyUpdate = 2, EarlyUpdate = 2,
/// <summary>
/// Fixed Update (i.e. state machine, physics, animations, etc)
/// </summary>
FixedUpdate = 3, FixedUpdate = 3,
/// <summary>
/// Updated before the Monobehaviour.Update for all components is invoked
/// </summary>
PreUpdate = 4, PreUpdate = 4,
/// <summary>
/// Updated when the Monobehaviour.Update for all components is invoked
/// </summary>
Update = 5, Update = 5,
/// <summary>
/// Updated before the Monobehaviour.LateUpdate for all components is invoked
/// </summary>
PreLateUpdate = 6, PreLateUpdate = 6,
/// <summary>
/// Updated after the Monobehaviour.LateUpdate for all components is invoked
/// </summary>
PostLateUpdate = 7 PostLateUpdate = 7
} }
@@ -53,6 +83,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Registers a network update system to be executed in all network update stages. /// Registers a network update system to be executed in all network update stages.
/// </summary> /// </summary>
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to register for all network updates</param>
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem) public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
{ {
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage))) foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
@@ -64,6 +95,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Registers a network update system to be executed in a specific network update stage. /// Registers a network update system to be executed in a specific network update stage.
/// </summary> /// </summary>
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to register for all network updates</param>
/// <param name="updateStage">The <see cref="NetworkUpdateStage"/> being registered for the <see cref="INetworkUpdateSystem"/> implementation</param>
public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update) public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
{ {
var sysSet = s_UpdateSystem_Sets[updateStage]; var sysSet = s_UpdateSystem_Sets[updateStage];
@@ -94,6 +127,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Unregisters a network update system from all network update stages. /// Unregisters a network update system from all network update stages.
/// </summary> /// </summary>
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to deregister from all network updates</param>
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem) public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
{ {
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage))) foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
@@ -105,6 +139,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Unregisters a network update system from a specific network update stage. /// Unregisters a network update system from a specific network update stage.
/// </summary> /// </summary>
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to deregister from all network updates</param>
/// <param name="updateStage">The <see cref="NetworkUpdateStage"/> to be deregistered from the <see cref="INetworkUpdateSystem"/> implementation</param>
public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update) public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
{ {
var sysSet = s_UpdateSystem_Sets[updateStage]; var sysSet = s_UpdateSystem_Sets[updateStage];

View File

@@ -7,8 +7,18 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public class InvalidParentException : Exception public class InvalidParentException : Exception
{ {
/// <summary>
/// Constructor for <see cref="InvalidParentException"/>
/// </summary>
public InvalidParentException() { } public InvalidParentException() { }
/// <inheritdoc/>
/// <param name="message"></param>
public InvalidParentException(string message) : base(message) { } public InvalidParentException(string message) : base(message) { }
/// <inheritdoc/>
/// <param name="message"></param>
/// <param name="innerException"></param>
public InvalidParentException(string message, Exception innerException) : base(message, innerException) { } public InvalidParentException(string message, Exception innerException) : base(message, innerException) { }
} }
} }

View File

@@ -26,8 +26,15 @@ namespace Unity.Netcode
public SpawnStateException(string message, Exception inner) : base(message, inner) { } public SpawnStateException(string message, Exception inner) : base(message, inner) { }
} }
/// <summary>
/// Exception thrown when a specified network channel is invalid
/// </summary>
public class InvalidChannelException : Exception public class InvalidChannelException : Exception
{ {
/// <summary>
/// Constructs an InvalidChannelException with a message
/// </summary>
/// <param name="message">the message</param>
public InvalidChannelException(string message) : base(message) { } public InvalidChannelException(string message) : base(message) { }
} }
} }

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

@@ -0,0 +1,248 @@
using System;
using System.Text;
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
internal static class XXHash
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe uint Hash32(byte* input, int length, uint seed = 0)
{
unchecked
{
const uint prime1 = 2654435761u;
const uint prime2 = 2246822519u;
const uint prime3 = 3266489917u;
const uint prime4 = 0668265263u;
const uint prime5 = 0374761393u;
uint hash = seed + prime5;
if (length >= 16)
{
uint val0 = seed + prime1 + prime2;
uint val1 = seed + prime2;
uint val2 = seed + 0;
uint val3 = seed - prime1;
int count = length >> 4;
for (int i = 0; i < count; i++)
{
var pos0 = *(uint*)(input + 0);
var pos1 = *(uint*)(input + 4);
var pos2 = *(uint*)(input + 8);
var pos3 = *(uint*)(input + 12);
val0 += pos0 * prime2;
val0 = (val0 << 13) | (val0 >> (32 - 13));
val0 *= prime1;
val1 += pos1 * prime2;
val1 = (val1 << 13) | (val1 >> (32 - 13));
val1 *= prime1;
val2 += pos2 * prime2;
val2 = (val2 << 13) | (val2 >> (32 - 13));
val2 *= prime1;
val3 += pos3 * prime2;
val3 = (val3 << 13) | (val3 >> (32 - 13));
val3 *= prime1;
input += 16;
}
hash = ((val0 << 01) | (val0 >> (32 - 01))) +
((val1 << 07) | (val1 >> (32 - 07))) +
((val2 << 12) | (val2 >> (32 - 12))) +
((val3 << 18) | (val3 >> (32 - 18)));
}
hash += (uint)length;
length &= 15;
while (length >= 4)
{
hash += *(uint*)input * prime3;
hash = ((hash << 17) | (hash >> (32 - 17))) * prime4;
input += 4;
length -= 4;
}
while (length > 0)
{
hash += *input * prime5;
hash = ((hash << 11) | (hash >> (32 - 11))) * prime1;
++input;
--length;
}
hash ^= hash >> 15;
hash *= prime2;
hash ^= hash >> 13;
hash *= prime3;
hash ^= hash >> 16;
return hash;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ulong Hash64(byte* input, int length, uint seed = 0)
{
unchecked
{
const ulong prime1 = 11400714785074694791ul;
const ulong prime2 = 14029467366897019727ul;
const ulong prime3 = 01609587929392839161ul;
const ulong prime4 = 09650029242287828579ul;
const ulong prime5 = 02870177450012600261ul;
ulong hash = seed + prime5;
if (length >= 32)
{
ulong val0 = seed + prime1 + prime2;
ulong val1 = seed + prime2;
ulong val2 = seed + 0;
ulong val3 = seed - prime1;
int count = length >> 5;
for (int i = 0; i < count; i++)
{
var pos0 = *(ulong*)(input + 0);
var pos1 = *(ulong*)(input + 8);
var pos2 = *(ulong*)(input + 16);
var pos3 = *(ulong*)(input + 24);
val0 += pos0 * prime2;
val0 = (val0 << 31) | (val0 >> (64 - 31));
val0 *= prime1;
val1 += pos1 * prime2;
val1 = (val1 << 31) | (val1 >> (64 - 31));
val1 *= prime1;
val2 += pos2 * prime2;
val2 = (val2 << 31) | (val2 >> (64 - 31));
val2 *= prime1;
val3 += pos3 * prime2;
val3 = (val3 << 31) | (val3 >> (64 - 31));
val3 *= prime1;
input += 32;
}
hash = ((val0 << 01) | (val0 >> (64 - 01))) +
((val1 << 07) | (val1 >> (64 - 07))) +
((val2 << 12) | (val2 >> (64 - 12))) +
((val3 << 18) | (val3 >> (64 - 18)));
val0 *= prime2;
val0 = (val0 << 31) | (val0 >> (64 - 31));
val0 *= prime1;
hash ^= val0;
hash = hash * prime1 + prime4;
val1 *= prime2;
val1 = (val1 << 31) | (val1 >> (64 - 31));
val1 *= prime1;
hash ^= val1;
hash = hash * prime1 + prime4;
val2 *= prime2;
val2 = (val2 << 31) | (val2 >> (64 - 31));
val2 *= prime1;
hash ^= val2;
hash = hash * prime1 + prime4;
val3 *= prime2;
val3 = (val3 << 31) | (val3 >> (64 - 31));
val3 *= prime1;
hash ^= val3;
hash = hash * prime1 + prime4;
}
hash += (ulong)length;
length &= 31;
while (length >= 8)
{
ulong lane = *(ulong*)input * prime2;
lane = ((lane << 31) | (lane >> (64 - 31))) * prime1;
hash ^= lane;
hash = ((hash << 27) | (hash >> (64 - 27))) * prime1 + prime4;
input += 8;
length -= 8;
}
if (length >= 4)
{
hash ^= *(uint*)input * prime1;
hash = ((hash << 23) | (hash >> (64 - 23))) * prime2 + prime3;
input += 4;
length -= 4;
}
while (length > 0)
{
hash ^= *input * prime5;
hash = ((hash << 11) | (hash >> (64 - 11))) * prime1;
++input;
--length;
}
hash ^= hash >> 33;
hash *= prime2;
hash ^= hash >> 29;
hash *= prime3;
hash ^= hash >> 32;
return hash;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32(this byte[] buffer)
{
int length = buffer.Length;
unsafe
{
fixed (byte* pointer = buffer)
{
return Hash32(pointer, length);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32(this string text) => Hash32(Encoding.UTF8.GetBytes(text));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32(this Type type) => Hash32(type.FullName);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32<T>() => Hash32(typeof(T));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(this byte[] buffer)
{
int length = buffer.Length;
unsafe
{
fixed (byte* pointer = buffer)
{
return Hash64(pointer, length);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(this string text) => Hash64(Encoding.UTF8.GetBytes(text));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(this Type type) => Hash64(type.FullName);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64<T>() => Hash64(typeof(T));
}
}

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,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

@@ -14,8 +14,23 @@ namespace Unity.Netcode
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel; public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
// internal logging // internal logging
/// <summary>
/// Locally logs a info log with Netcode prefixing.
/// </summary>
/// <param name="message">The message to log</param>
public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}"); public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
/// <summary>
/// Locally logs a warning log with Netcode prefixing.
/// </summary>
/// <param name="message">The message to log</param>
public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}"); public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
/// <summary>
/// Locally logs a error log with Netcode prefixing.
/// </summary>
/// <param name="message">The message to log</param>
public static void LogError(string message) => Debug.LogError($"[Netcode] {message}"); public static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
/// <summary> /// <summary>

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
@@ -193,6 +220,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Sends a named message to all clients /// Sends a named message to all clients
/// </summary> /// </summary>
/// <param name="messageName">The message name to send</param>
/// <param name="messageStream">The message stream containing the data</param> /// <param name="messageStream">The message stream containing the data</param>
/// <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 SendNamedMessageToAll(string messageName, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced) public void SendNamedMessageToAll(string messageName, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
@@ -219,6 +247,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
{ {
@@ -250,7 +292,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;
@@ -263,6 +305,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

@@ -0,0 +1,50 @@
namespace Unity.Netcode
{
internal struct DisconnectReasonMessage : INetworkMessage
{
public string Reason;
public int Version => 0;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
string reasonSent = Reason;
if (reasonSent == null)
{
reasonSent = string.Empty;
}
// Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message
// itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion
// on this side of things - we just have to make sure the receiving side knows what version we sent it,
// since whoever has the higher version number is responsible for versioning and they may be the one
// with the higher version number.
BytePacker.WriteValueBitPacked(writer, Version);
if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent)))
{
writer.WriteValue(reasonSent);
}
else
{
writer.WriteValueSafe(string.Empty);
NetworkLog.LogWarning(
"Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string.");
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
// Since we don't get a ConnectionApprovedMessage, the version for this message is encded with the message
// itself. This will override what we got from MessagingSystem... which will always be 0 here.
ByteUnpacker.ReadValueBitPacked(reader, out receivedMessageVersion);
reader.ReadValueSafe(out Reason);
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).DisconnectReason = Reason;
}
};
}

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;
} }
@@ -79,12 +118,13 @@ namespace Unity.Netcode
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime); networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId }; networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
networkManager.IsApproved = true;
// Only if scene management is disabled do we handle NetworkObject synchronization at this point // Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement) if (!networkManager.NetworkConfig.EnableSceneManagement)
{ {
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
{ {
@@ -66,7 +68,7 @@ namespace Unity.Netcode
{ {
if (!shouldWrite) if (!shouldWrite)
{ {
BytePacker.WriteValueBitPacked(writer, 0); BytePacker.WriteValueBitPacked(writer, (ushort)0);
} }
} }
else else
@@ -110,15 +112,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,32 +1,65 @@
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal struct ParentSyncMessage : INetworkMessage internal struct ParentSyncMessage : INetworkMessage
{ {
public int Version => 0;
public ulong NetworkObjectId; public ulong NetworkObjectId;
public bool IsReparented; private byte m_BitField;
public bool WorldPositionStays
{
get => ByteUtility.GetBit(m_BitField, 0);
set => ByteUtility.SetBit(ref m_BitField, 0, value);
}
//If(Metadata.IsReparented) //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;
public void Serialize(FastBufferWriter writer) // Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
public bool RemoveParent
{ {
writer.WriteValueSafe(NetworkObjectId); get => ByteUtility.GetBit(m_BitField, 2);
writer.WriteValueSafe(IsReparented); set => ByteUtility.SetBit(ref m_BitField, 2, value);
if (IsReparented) }
// These additional properties are used to synchronize clients with the current position,
// rotation, and scale after parenting/de-parenting (world/local space relative). This
// allows users to control the final child's transform values without having to have a
// NetworkTransform component on the child. (i.e. picking something up)
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValueSafe(m_BitField);
if (!RemoveParent)
{ {
writer.WriteValueSafe(IsLatestParentSet);
if (IsLatestParentSet) if (IsLatestParentSet)
{ {
writer.WriteValueSafe((ulong)LatestParent); BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
}
} }
} }
public bool Deserialize(FastBufferReader reader, ref NetworkContext context) // Whether parenting or removing a parent, we always update the position, rotation, and scale
writer.WriteValueSafe(Position);
writer.WriteValueSafe(Rotation);
writer.WriteValueSafe(Scale);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{ {
var networkManager = (NetworkManager)context.SystemOwner; var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient) if (!networkManager.IsClient)
@@ -34,24 +67,27 @@ namespace Unity.Netcode
return false; return false;
} }
reader.ReadValueSafe(out NetworkObjectId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out IsReparented); reader.ReadValueSafe(out m_BitField);
if (IsReparented) if (!RemoveParent)
{ {
reader.ReadValueSafe(out IsLatestParentSet);
if (IsLatestParentSet) if (IsLatestParentSet)
{ {
reader.ReadValueSafe(out ulong latestParent); ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
LatestParent = latestParent; LatestParent = latestParent;
} }
} }
// Whether parenting or removing a parent, we always update the position, rotation, and scale
reader.ReadValueSafe(out Position);
reader.ReadValueSafe(out Rotation);
reader.ReadValueSafe(out Scale);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) 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);
return false; return false;
} }
return true; return true;
} }
@@ -59,8 +95,22 @@ namespace Unity.Netcode
{ {
var networkManager = (NetworkManager)context.SystemOwner; var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
networkObject.SetNetworkParenting(IsReparented, LatestParent); networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
networkObject.ApplyNetworkParenting(); networkObject.ApplyNetworkParenting(RemoveParent);
// We set all of the transform values after parenting as they are
// the values of the server-side post-parenting transform values
if (!WorldPositionStays)
{
networkObject.transform.localPosition = Position;
networkObject.transform.localRotation = Rotation;
}
else
{
networkObject.transform.position = Position;
networkObject.transform.rotation = Rotation;
}
networkObject.transform.localScale = Scale;
} }
} }
} }

View File

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

@@ -8,6 +8,11 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal class HandlerNotRegisteredException : SystemException
{
public HandlerNotRegisteredException() { }
public HandlerNotRegisteredException(string issue) : base(issue) { }
}
internal class InvalidMessageStructureException : SystemException internal class InvalidMessageStructureException : SystemException
{ {
@@ -41,15 +46,22 @@ namespace Unity.Netcode
} }
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);
private MessageHandler[] m_MessageHandlers = new MessageHandler[255]; // These array will grow as we need more message handlers. 4 is just a starting size.
private Type[] m_ReverseTypeMap = new Type[255]; private MessageHandler[] m_MessageHandlers = new MessageHandler[4];
private Type[] m_ReverseTypeMap = new Type[4];
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;
@@ -59,6 +71,7 @@ namespace Unity.Netcode
internal Type[] MessageTypes => m_ReverseTypeMap; internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers; internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal uint MessageHandlerCount => m_HighMessageType; internal uint MessageHandlerCount => m_HighMessageType;
internal uint GetMessageType(Type t) internal uint GetMessageType(Type t)
@@ -67,12 +80,40 @@ 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)
{
var prioritizedTypes = new List<MessageWithHandler>();
// first pass puts the priority message in the first indices
// Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName == typeof(ConnectionRequestMessage).FullName ||
t.MessageType.FullName == typeof(ConnectionApprovedMessage).FullName)
{
prioritizedTypes.Add(t);
}
}
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName != typeof(ConnectionRequestMessage).FullName &&
t.MessageType.FullName != typeof(ConnectionApprovedMessage).FullName)
{
prioritizedTypes.Add(t);
}
}
return prioritizedTypes;
} }
public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null) public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
@@ -89,6 +130,7 @@ namespace Unity.Netcode
var allowedTypes = provider.GetMessages(); var allowedTypes = provider.GetMessages();
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName)); allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
allowedTypes = PrioritizeMessageOrder(allowedTypes);
foreach (var type in allowedTypes) foreach (var type in allowedTypes)
{ {
RegisterMessageType(type); RegisterMessageType(type);
@@ -143,9 +185,23 @@ namespace Unity.Netcode
private void RegisterMessageType(MessageWithHandler messageWithHandler) private void RegisterMessageType(MessageWithHandler messageWithHandler)
{ {
// if we are out of space, perform amortized linear growth
if (m_HighMessageType == m_MessageHandlers.Length)
{
Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length);
Array.Resize(ref m_ReverseTypeMap, 2 * m_ReverseTypeMap.Length);
}
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 void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float receiveTime) internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float receiveTime)
@@ -226,6 +282,55 @@ namespace Unity.Netcode
return true; return true;
} }
internal Type GetMessageForHash(uint messageHash)
{
if (!m_MessagesByHash.ContainsKey(messageHash))
{
return null;
}
return m_MessagesByHash[messageHash];
}
internal void SetVersion(ulong clientId, uint messageHash, int version)
{
if (!m_MessagesByHash.ContainsKey(messageHash))
{
return;
}
var messageType = m_MessagesByHash[messageHash];
if (!m_PerClientMessageVersions.ContainsKey(clientId))
{
m_PerClientMessageVersions[clientId] = new Dictionary<Type, int>();
}
m_PerClientMessageVersions[clientId][messageType] = version;
}
internal void SetServerMessageOrder(NativeArray<uint> messagesInIdOrder)
{
var oldHandlers = m_MessageHandlers;
var oldTypes = m_MessageTypes;
m_ReverseTypeMap = new Type[messagesInIdOrder.Length];
m_MessageHandlers = new MessageHandler[messagesInIdOrder.Length];
m_MessageTypes = new Dictionary<Type, uint>();
for (var i = 0; i < messagesInIdOrder.Length; ++i)
{
if (!m_MessagesByHash.ContainsKey(messagesInIdOrder[i]))
{
continue;
}
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;
}
}
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)
{ {
if (header.MessageType >= m_HighMessageType) if (header.MessageType >= m_HighMessageType)
@@ -258,6 +363,16 @@ namespace Unity.Netcode
var handler = m_MessageHandlers[header.MessageType]; var handler = m_MessageHandlers[header.MessageType];
using (reader) using (reader)
{
// 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
// two connecting builds know about different messages, the server should not send a message to a client
// that doesn't know about it
if (handler == null)
{
Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString()));
}
else
{ {
// No user-land message handler exceptions should escape the receive loop. // No user-land message handler exceptions should escape the receive loop.
// If an exception is throw, the message is ignored. // If an exception is throw, the message is ignored.
@@ -273,6 +388,7 @@ 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>());
@@ -314,7 +430,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)
@@ -325,10 +441,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)
{ {
@@ -366,16 +539,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);
@@ -390,6 +594,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))
@@ -440,8 +663,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>

View File

@@ -3,21 +3,52 @@ using Unity.Collections;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Server-Side RPC
/// Place holder. <see cref="ServerRpcParams"/>
/// Note: Clients always send to one destination when sending RPCs to the server
/// so this structure is a place holder
/// </summary>
public struct ServerRpcSendParams public struct ServerRpcSendParams
{ {
} }
/// <summary>
/// The receive parameters for server-side remote procedure calls
/// </summary>
public struct ServerRpcReceiveParams public struct ServerRpcReceiveParams
{ {
/// <summary>
/// Server-Side RPC
/// The client identifier of the sender
/// </summary>
public ulong SenderClientId; public ulong SenderClientId;
} }
/// <summary>
/// Server-Side RPC
/// Can be used with any sever-side remote procedure call
/// Note: typically this is use primarily for the <see cref="ServerRpcReceiveParams"/>
/// </summary>
public struct ServerRpcParams public struct ServerRpcParams
{ {
/// <summary>
/// The server RPC send parameters (currently a place holder)
/// </summary>
public ServerRpcSendParams Send; public ServerRpcSendParams Send;
/// <summary>
/// The client RPC receive parameters provides you with the sender's identifier
/// </summary>
public ServerRpcReceiveParams Receive; public ServerRpcReceiveParams Receive;
} }
/// <summary>
/// Client-Side RPC
/// The send parameters, when sending client RPCs, provides you wil the ability to
/// target specific clients as a managed or unmanaged list:
/// <see cref="TargetClientIds"/> and <see cref="TargetClientIdsNativeArray"/>
/// </summary>
public struct ClientRpcSendParams public struct ClientRpcSendParams
{ {
/// <summary> /// <summary>
@@ -34,13 +65,32 @@ namespace Unity.Netcode
public NativeArray<ulong>? TargetClientIdsNativeArray; public NativeArray<ulong>? TargetClientIdsNativeArray;
} }
/// <summary>
/// Client-Side RPC
/// Place holder. <see cref="ServerRpcParams"/>
/// Note: Server will always be the sender, so this structure is a place holder
/// </summary>
public struct ClientRpcReceiveParams public struct ClientRpcReceiveParams
{ {
} }
/// <summary>
/// Client-Side RPC
/// Can be used with any client-side remote procedure call
/// Note: Typically this is used primarily for sending to a specific list
/// of clients as opposed to the default (all).
/// <see cref="ClientRpcSendParams"/>
/// </summary>
public struct ClientRpcParams public struct ClientRpcParams
{ {
/// <summary>
/// The client RPC send parameters provides you with the ability to send to a specific list of clients
/// </summary>
public ClientRpcSendParams Send; public ClientRpcSendParams Send;
/// <summary>
/// The client RPC receive parameters (currently a place holder)
/// </summary>
public ClientRpcReceiveParams Receive; public ClientRpcReceiveParams Receive;
} }

View File

@@ -5,17 +5,14 @@ using Unity.Multiplayer.Tools;
using Unity.Multiplayer.Tools.MetricTypes; using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats; using Unity.Multiplayer.Tools.NetStats;
using Unity.Profiling; using Unity.Profiling;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal class NetworkMetrics : INetworkMetrics internal class NetworkMetrics : INetworkMetrics
{ {
const ulong k_MaxMetricsPerFrame = 1000L; private const ulong k_MaxMetricsPerFrame = 1000L;
private static Dictionary<uint, string> s_SceneEventTypeNames;
static Dictionary<uint, string> s_SceneEventTypeNames; private static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
static NetworkMetrics() static NetworkMetrics()
{ {

View File

@@ -4,7 +4,7 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
class NetworkObjectProvider : INetworkObjectProvider internal class NetworkObjectProvider : INetworkObjectProvider
{ {
private readonly NetworkManager m_NetworkManager; private readonly NetworkManager m_NetworkManager;

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Unity.Collections; using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -11,7 +12,6 @@ namespace Unity.Netcode
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T> public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
{ {
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent); private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent); private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
/// <summary> /// <summary>
@@ -25,18 +25,29 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public event OnListChangedDelegate OnListChanged; public event OnListChangedDelegate OnListChanged;
/// <summary>
/// Constructor method for <see cref="NetworkList"/>
/// </summary>
public NetworkList() { } public NetworkList() { }
/// <inheritdoc/>
/// <param name="values"></param>
/// <param name="readPerm"></param>
/// <param name="writePerm"></param>
public NetworkList(IEnumerable<T> values = default, public NetworkList(IEnumerable<T> values = default,
NetworkVariableReadPermission readPerm = DefaultReadPerm, NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm) NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm) : base(readPerm, writePerm)
{
// allow null IEnumerable<T> to mean "no values"
if (values != null)
{ {
foreach (var value in values) foreach (var value in values)
{ {
m_List.Add(value); m_List.Add(value);
} }
} }
}
/// <inheritdoc /> /// <inheritdoc />
public override void ResetDirty() public override void ResetDirty()
@@ -45,7 +56,6 @@ namespace Unity.Netcode
if (m_DirtyEvents.Length > 0) if (m_DirtyEvents.Length > 0)
{ {
m_DirtyEvents.Clear(); m_DirtyEvents.Clear();
m_ListAtLastReset.CopyFrom(m_List);
} }
} }
@@ -56,6 +66,18 @@ namespace Unity.Netcode
return base.IsDirty() || m_DirtyEvents.Length > 0; return base.IsDirty() || m_DirtyEvents.Length > 0;
} }
internal void MarkNetworkObjectDirty()
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkList before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
/// <inheritdoc /> /// <inheritdoc />
public override void WriteDelta(FastBufferWriter writer) public override void WriteDelta(FastBufferWriter writer)
{ {
@@ -115,10 +137,10 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public override void WriteField(FastBufferWriter writer) public override void WriteField(FastBufferWriter writer)
{ {
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length); writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_ListAtLastReset.Length; i++) for (int i = 0; i < m_List.Length; i++)
{ {
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i)); NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
} }
} }
@@ -129,7 +151,8 @@ namespace Unity.Netcode
reader.ReadValueSafe(out ushort count); reader.ReadValueSafe(out ushort count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
NetworkVariableSerialization<T>.Read(reader, out T value); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
m_List.Add(value); m_List.Add(value);
} }
} }
@@ -145,7 +168,8 @@ namespace Unity.Netcode
{ {
case NetworkListEvent<T>.EventType.Add: case NetworkListEvent<T>.EventType.Add:
{ {
NetworkVariableSerialization<T>.Read(reader, out T value); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
m_List.Add(value); m_List.Add(value);
if (OnListChanged != null) if (OnListChanged != null)
@@ -166,15 +190,25 @@ namespace Unity.Netcode
Index = m_List.Length - 1, Index = m_List.Length - 1,
Value = m_List[m_List.Length - 1] Value = m_List[m_List.Length - 1]
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
case NetworkListEvent<T>.EventType.Insert: case NetworkListEvent<T>.EventType.Insert:
{ {
reader.ReadValueSafe(out int index); reader.ReadValueSafe(out int index);
NetworkVariableSerialization<T>.Read(reader, out T value); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
if (index < m_List.Length)
{
m_List.InsertRangeWithBeginEnd(index, index + 1); m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value; m_List[index] = value;
}
else
{
m_List.Add(value);
}
if (OnListChanged != null) if (OnListChanged != null)
{ {
@@ -194,12 +228,14 @@ namespace Unity.Netcode
Index = index, Index = index,
Value = m_List[index] Value = m_List[index]
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
case NetworkListEvent<T>.EventType.Remove: case NetworkListEvent<T>.EventType.Remove:
{ {
NetworkVariableSerialization<T>.Read(reader, out T value); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
int index = m_List.IndexOf(value); int index = m_List.IndexOf(value);
if (index == -1) if (index == -1)
{ {
@@ -226,6 +262,7 @@ namespace Unity.Netcode
Index = index, Index = index,
Value = value Value = value
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -253,13 +290,15 @@ namespace Unity.Netcode
Index = index, Index = index,
Value = value Value = value
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
case NetworkListEvent<T>.EventType.Value: case NetworkListEvent<T>.EventType.Value:
{ {
reader.ReadValueSafe(out int index); reader.ReadValueSafe(out int index);
NetworkVariableSerialization<T>.Read(reader, out T value); var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
if (index >= m_List.Length) if (index >= m_List.Length)
{ {
throw new Exception("Shouldn't be here, index is higher than list length"); throw new Exception("Shouldn't be here, index is higher than list length");
@@ -288,6 +327,7 @@ namespace Unity.Netcode
Value = value, Value = value,
PreviousValue = previousValue PreviousValue = previousValue
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -310,6 +350,7 @@ namespace Unity.Netcode
{ {
Type = eventType Type = eventType
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -332,6 +373,12 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public void Add(T item) public void Add(T item)
{ {
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.Add(item); m_List.Add(item);
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
@@ -347,6 +394,12 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public void Clear() public void Clear()
{ {
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.Clear(); m_List.Clear();
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
@@ -360,14 +413,20 @@ 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;
} }
/// <inheritdoc /> /// <inheritdoc />
public bool Remove(T item) public bool Remove(T item)
{ {
int index = NativeArrayExtensions.IndexOf(m_List, item); // check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
int index = m_List.IndexOf(item);
if (index == -1) if (index == -1)
{ {
return false; return false;
@@ -395,9 +454,22 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public void Insert(int index, T item) public void Insert(int index, T item)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
if (index < m_List.Length)
{ {
m_List.InsertRangeWithBeginEnd(index, index + 1); m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = item; m_List[index] = item;
}
else
{
m_List.Add(item);
}
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
{ {
@@ -412,6 +484,12 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public void RemoveAt(int index) public void RemoveAt(int index)
{ {
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.RemoveAt(index); m_List.RemoveAt(index);
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
@@ -429,13 +507,21 @@ namespace Unity.Netcode
get => m_List[index]; get => m_List[index];
set set
{ {
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
var previousValue = m_List[index];
m_List[index] = value; m_List[index] = value;
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
{ {
Type = NetworkListEvent<T>.EventType.Value, Type = NetworkListEvent<T>.EventType.Value,
Index = index, Index = index,
Value = value Value = value,
PreviousValue = previousValue
}; };
HandleAddListEvent(listEvent); HandleAddListEvent(listEvent);
@@ -445,9 +531,13 @@ namespace Unity.Netcode
private void HandleAddListEvent(NetworkListEvent<T> listEvent) private void HandleAddListEvent(NetworkListEvent<T> listEvent)
{ {
m_DirtyEvents.Add(listEvent); m_DirtyEvents.Add(listEvent);
MarkNetworkObjectDirty();
OnListChanged?.Invoke(listEvent); OnListChanged?.Invoke(listEvent);
} }
/// <summary>
/// This is actually unused left-over from a previous interface
/// </summary>
public int LastModifiedTick public int LastModifiedTick
{ {
get get
@@ -457,10 +547,14 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Overridden <see cref="IDisposable"/> implementation.
/// CAUTION: If you derive from this class and override the <see cref="Dispose"/> method,
/// you **must** always invoke the base.Dispose() method!
/// </summary>
public override void Dispose() public override void Dispose()
{ {
m_List.Dispose(); m_List.Dispose();
m_ListAtLastReset.Dispose();
m_DirtyEvents.Dispose(); m_DirtyEvents.Dispose();
} }
} }

View File

@@ -1,15 +1,14 @@
using UnityEngine; using UnityEngine;
using System; using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// A variable that can be synchronized over the network. /// A variable that can be synchronized over the network.
/// </summary> /// </summary>
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
[Serializable] [Serializable]
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged public class NetworkVariable<T> : NetworkVariableBase
{ {
/// <summary> /// <summary>
/// Delegate type for value changed event /// Delegate type for value changed event
@@ -22,7 +21,12 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public OnValueChangedDelegate OnValueChanged; public OnValueChangedDelegate OnValueChanged;
/// <summary>
/// Constructor for <see cref="NetworkVariable{T}"/>
/// </summary>
/// <param name="value">initial value set that is of type T</param>
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> for this <see cref="NetworkVariable{T}"/></param>
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> for this <see cref="NetworkVariable{T}"/></param>
public NetworkVariable(T value = default, public NetworkVariable(T value = default,
NetworkVariableReadPermission readPerm = DefaultReadPerm, NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm) NetworkVariableWritePermission writePerm = DefaultWritePerm)
@@ -31,6 +35,9 @@ namespace Unity.Netcode
m_InternalValue = value; m_InternalValue = value;
} }
/// <summary>
/// The internal value of the NetworkVariable
/// </summary>
[SerializeField] [SerializeField]
private protected T m_InternalValue; private protected T m_InternalValue;
@@ -43,7 +50,7 @@ namespace Unity.Netcode
set set
{ {
// Compare bitwise // Compare bitwise
if (ValueEquals(ref m_InternalValue, ref value)) if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
{ {
return; return;
} }
@@ -57,24 +64,14 @@ namespace Unity.Netcode
} }
} }
// Compares two values of the same unmanaged type by underlying memory /// <summary>
// Ignoring any overriden value checks /// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
// Size is fixed /// if there are subscribers to that event.
[MethodImpl(MethodImplOptions.AggressiveInlining)] /// </summary>
private static unsafe bool ValueEquals(ref T a, ref T b) /// <param name="value">the new value of type `T` to be set/></param>
{
// get unmanaged pointers
var aptr = UnsafeUtility.AddressOf(ref a);
var bptr = UnsafeUtility.AddressOf(ref b);
// compare addresses
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
}
private protected void Set(T value) private protected void Set(T value)
{ {
m_IsDirty = true; SetDirty(true);
T previousValue = m_InternalValue; T previousValue = m_InternalValue;
m_InternalValue = value; m_InternalValue = value;
OnValueChanged?.Invoke(previousValue, m_InternalValue); OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -102,11 +99,11 @@ namespace Unity.Netcode
// would be stored in different fields // would be stored in different fields
T previousValue = m_InternalValue; T previousValue = m_InternalValue;
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue); NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
if (keepDirtyDelta) if (keepDirtyDelta)
{ {
m_IsDirty = true; SetDirty(true);
} }
OnValueChanged?.Invoke(previousValue, m_InternalValue); OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -115,7 +112,7 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public override void ReadField(FastBufferReader reader) public override void ReadField(FastBufferReader reader)
{ {
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue); NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -1,4 +1,5 @@
using System; using System;
using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
@@ -12,16 +13,36 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced; internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced;
/// <summary>
/// Maintains a link to the associated NetworkBehaviour
/// </summary>
private protected NetworkBehaviour m_NetworkBehaviour; private protected NetworkBehaviour m_NetworkBehaviour;
/// <summary>
/// Initializes the NetworkVariable
/// </summary>
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
public void Initialize(NetworkBehaviour networkBehaviour) public void Initialize(NetworkBehaviour networkBehaviour)
{ {
m_NetworkBehaviour = networkBehaviour; m_NetworkBehaviour = networkBehaviour;
} }
/// <summary>
/// The default read permissions
/// </summary>
public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone; public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone;
/// <summary>
/// The default write permissions
/// </summary>
public const NetworkVariableWritePermission DefaultWritePerm = NetworkVariableWritePermission.Server; public const NetworkVariableWritePermission DefaultWritePerm = NetworkVariableWritePermission.Server;
/// <summary>
/// The default constructor for <see cref="NetworkVariableBase"/> that can be used to create a
/// custom NetworkVariable.
/// </summary>
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> access settings</param>
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> access settings</param>
protected NetworkVariableBase( protected NetworkVariableBase(
NetworkVariableReadPermission readPerm = DefaultReadPerm, NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm) NetworkVariableWritePermission writePerm = DefaultWritePerm)
@@ -30,7 +51,11 @@ namespace Unity.Netcode
WritePerm = writePerm; WritePerm = writePerm;
} }
private protected bool m_IsDirty; /// <summary>
/// The <see cref="m_IsDirty"/> property is used to determine if the
/// value of the `NetworkVariable` has changed.
/// </summary>
private bool m_IsDirty;
/// <summary> /// <summary>
/// Gets or sets the name of the network variable's instance /// Gets or sets the name of the network variable's instance
@@ -43,14 +68,29 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public readonly NetworkVariableReadPermission ReadPerm; public readonly NetworkVariableReadPermission ReadPerm;
/// <summary>
/// The write permission for this var
/// </summary>
public readonly NetworkVariableWritePermission WritePerm; public readonly NetworkVariableWritePermission WritePerm;
/// <summary> /// <summary>
/// Sets whether or not the variable needs to be delta synced /// Sets whether or not the variable needs to be delta synced
/// </summary> /// </summary>
/// <param name="isDirty">Whether or not the var is dirty</param>
public virtual void SetDirty(bool isDirty) public virtual void SetDirty(bool isDirty)
{ {
m_IsDirty = isDirty; m_IsDirty = isDirty;
if (m_IsDirty)
{
if (m_NetworkBehaviour == null)
{
Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " +
"Are you modifying a NetworkVariable before the NetworkObject is spawned?");
return;
}
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
} }
/// <summary> /// <summary>
@@ -70,6 +110,11 @@ namespace Unity.Netcode
return m_IsDirty; return m_IsDirty;
} }
/// <summary>
/// Gets if a specific client has permission to read the var or not
/// </summary>
/// <param name="clientId">The client id</param>
/// <returns>Whether or not the client has permission to read</returns>
public bool CanClientRead(ulong clientId) public bool CanClientRead(ulong clientId)
{ {
switch (ReadPerm) switch (ReadPerm)
@@ -78,10 +123,15 @@ namespace Unity.Netcode
case NetworkVariableReadPermission.Everyone: case NetworkVariableReadPermission.Everyone:
return true; return true;
case NetworkVariableReadPermission.Owner: case NetworkVariableReadPermission.Owner:
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId; return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId;
} }
} }
/// <summary>
/// Gets if a specific client has permission to write the var or not
/// </summary>
/// <param name="clientId">The client id</param>
/// <returns>Whether or not the client has permission to write</returns>
public bool CanClientWrite(ulong clientId) public bool CanClientWrite(ulong clientId)
{ {
switch (WritePerm) switch (WritePerm)
@@ -127,6 +177,9 @@ namespace Unity.Netcode
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param> /// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta); public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
/// <summary>
/// Virtual <see cref="IDisposable"/> implementation
/// </summary>
public virtual void Dispose() public virtual void Dispose()
{ {
} }

View File

@@ -1,14 +1,32 @@
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// The permission types for reading a var
/// </summary>
public enum NetworkVariableReadPermission public enum NetworkVariableReadPermission
{ {
/// <summary>
/// Everyone can read
/// </summary>
Everyone, Everyone,
/// <summary>
/// Only the owner and the server can read
/// </summary>
Owner, Owner,
} }
/// <summary>
/// The permission types for writing a var
/// </summary>
public enum NetworkVariableWritePermission public enum NetworkVariableWritePermission
{ {
/// <summary>
/// Only the server can write
/// </summary>
Server, Server,
/// <summary>
/// Only the owner can write
/// </summary>
Owner Owner
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic;
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
using UnityEngine; using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
@@ -18,7 +19,97 @@ namespace Unity.Netcode
// Taking T as an in parameter like we do in other places would require making a copy // Taking T as an in parameter like we do in other places would require making a copy
// of it to pass it as a ref parameter. // of it to pass it as a ref parameter.
public void Write(FastBufferWriter writer, ref T value); public void Write(FastBufferWriter writer, ref T value);
public void Read(FastBufferReader reader, out T value); public void Read(FastBufferReader reader, ref T value);
}
/// <summary>
/// Packing serializer for shorts
/// </summary>
internal class ShortSerializer : INetworkVariableSerializer<short>
{
public void Write(FastBufferWriter writer, ref short value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref short value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for shorts
/// </summary>
internal class UshortSerializer : INetworkVariableSerializer<ushort>
{
public void Write(FastBufferWriter writer, ref ushort value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref ushort value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for ints
/// </summary>
internal class IntSerializer : INetworkVariableSerializer<int>
{
public void Write(FastBufferWriter writer, ref int value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref int value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for ints
/// </summary>
internal class UintSerializer : INetworkVariableSerializer<uint>
{
public void Write(FastBufferWriter writer, ref uint value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref uint value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for longs
/// </summary>
internal class LongSerializer : INetworkVariableSerializer<long>
{
public void Write(FastBufferWriter writer, ref long value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref long value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for longs
/// </summary>
internal class UlongSerializer : INetworkVariableSerializer<ulong>
{
public void Write(FastBufferWriter writer, ref ulong value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref ulong value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
} }
/// <summary> /// <summary>
@@ -35,79 +126,80 @@ namespace Unity.Netcode
{ {
writer.WriteUnmanagedSafe(value); writer.WriteUnmanagedSafe(value);
} }
public void Read(FastBufferReader reader, out T value) public void Read(FastBufferReader reader, ref T value)
{ {
reader.ReadUnmanagedSafe(out value); reader.ReadUnmanagedSafe(out value);
} }
} }
/// <summary> /// <summary>
/// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do, /// Serializer for FixedStrings
/// but is implemented to get the data it needs using open instance delegates that are passed in
/// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable
/// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates
/// circumvent that.)
///
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
/// aren't known to actually contain those methods.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes
{ {
internal delegate int GetLengthDelegate(ref T value); public void Write(FastBufferWriter writer, ref T value)
internal delegate void SetLengthDelegate(ref T value, int length);
internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value);
internal GetLengthDelegate GetLength;
internal SetLengthDelegate SetLength;
internal GetUnsafePtrDelegate GetUnsafePtr;
public unsafe void Write(FastBufferWriter writer, ref T value)
{ {
int length = GetLength(ref value); writer.WriteValueSafe(value);
byte* data = GetUnsafePtr(ref value);
writer.WriteUnmanagedSafe(length);
writer.WriteBytesSafe(data, length);
} }
public unsafe void Read(FastBufferReader reader, out T value) public void Read(FastBufferReader reader, ref T value)
{ {
value = new T(); reader.ReadValueSafeInPlace(ref value);
reader.ReadValueSafe(out int length);
SetLength(ref value, length);
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
} }
} }
/// <summary> /// <summary>
/// Serializer for INetworkSerializable types, which does the same thing /// Serializer for unmanaged INetworkSerializable types
/// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method
/// via open instance delegates passed in via reflection. This prevents needing T to meet any interface
/// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read -
/// reflection + Open Instance Delegates circumvent that.)
///
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
/// aren't known to actually contain those methods.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable
{ {
internal delegate void WriteValueDelegate(ref T value, BufferSerializer<BufferSerializerWriter> serializer);
internal delegate void ReadValueDelegate(ref T value, BufferSerializer<BufferSerializerReader> serializer);
internal WriteValueDelegate WriteValue;
internal ReadValueDelegate ReadValue;
public void Write(FastBufferWriter writer, ref T value) public void Write(FastBufferWriter writer, ref T value)
{ {
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer)); var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
WriteValue(ref value, bufferSerializer); value.NetworkSerialize(bufferSerializer);
} }
public void Read(FastBufferReader reader, out T value) public void Read(FastBufferReader reader, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
value.NetworkSerialize(bufferSerializer);
}
}
/// <summary>
/// Serializer for managed INetworkSerializable types, which differs from the unmanaged implementation in that it
/// has to be null-aware
/// <typeparam name="T"></typeparam>
internal class ManagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : class, INetworkSerializable, new()
{
public void Write(FastBufferWriter writer, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
bool isNull = (value == null);
bufferSerializer.SerializeValue(ref isNull);
if (!isNull)
{
value.NetworkSerialize(bufferSerializer);
}
}
public void Read(FastBufferReader reader, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
bool isNull = false;
bufferSerializer.SerializeValue(ref isNull);
if (isNull)
{
value = null;
}
else
{
if (value == null)
{ {
value = new T(); value = new T();
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader)); }
ReadValue(ref value, bufferSerializer); value.NetworkSerialize(bufferSerializer);
}
} }
} }
@@ -120,10 +212,28 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public class UserNetworkVariableSerialization<T> public class UserNetworkVariableSerialization<T>
{ {
/// <summary>
/// The write value delegate handler definition
/// </summary>
/// <param name="writer">The <see cref="FastBufferWriter"/> to write the value of type `T`</param>
/// <param name="value">The value of type `T` to be written</param>
public delegate void WriteValueDelegate(FastBufferWriter writer, in T value); public delegate void WriteValueDelegate(FastBufferWriter writer, in T value);
/// <summary>
/// The read value delegate handler definition
/// </summary>
/// <param name="reader">The <see cref="FastBufferReader"/> to read the value of type `T`</param>
/// <param name="value">The value of type `T` to be read</param>
public delegate void ReadValueDelegate(FastBufferReader reader, out T value); public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
/// <summary>
/// The <see cref="WriteValueDelegate"/> delegate handler declaration
/// </summary>
public static WriteValueDelegate WriteValue; public static WriteValueDelegate WriteValue;
/// <summary>
/// The <see cref="ReadValueDelegate"/> delegate handler declaration
/// </summary>
public static ReadValueDelegate ReadValue; public static ReadValueDelegate ReadValue;
} }
@@ -146,7 +256,7 @@ namespace Unity.Netcode
} }
UserNetworkVariableSerialization<T>.WriteValue(writer, value); UserNetworkVariableSerialization<T>.WriteValue(writer, value);
} }
public void Read(FastBufferReader reader, out T value) public void Read(FastBufferReader reader, ref T value)
{ {
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null) if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
{ {
@@ -156,34 +266,115 @@ namespace Unity.Netcode
} }
} }
internal static class NetworkVariableSerializationTypes /// <summary>
/// This class contains initialization functions for various different types used in NetworkVariables.
/// Generally speaking, these methods are called by a module initializer created by codegen (NetworkBehaviourILPP)
/// and do not need to be called manually.
///
/// There are two types of initializers: Serializers and EqualityCheckers. Every type must have an EqualityChecker
/// registered to it in order to be used in NetworkVariable; however, not all types need a Serializer. Types without
/// a serializer registered will fall back to using the delegates in <see cref="UserNetworkVariableSerialization{T}"/>.
/// If no such delegate has been registered, a type without a serializer will throw an exception on the first attempt
/// to serialize or deserialize it. (Again, however, codegen handles this automatically and this registration doesn't
/// typically need to be performed manually.)
/// </summary>
public static class NetworkVariableSerializationTypes
{ {
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type> [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
#if UNITY_EDITOR
[InitializeOnLoadMethod]
#endif
internal static void InitializeIntegerSerialization()
{ {
typeof(bool), NetworkVariableSerialization<short>.Serializer = new ShortSerializer();
typeof(byte), NetworkVariableSerialization<short>.AreEqual = NetworkVariableSerialization<short>.ValueEquals;
typeof(sbyte), NetworkVariableSerialization<ushort>.Serializer = new UshortSerializer();
typeof(char), NetworkVariableSerialization<ushort>.AreEqual = NetworkVariableSerialization<ushort>.ValueEquals;
typeof(decimal), NetworkVariableSerialization<int>.Serializer = new IntSerializer();
typeof(double), NetworkVariableSerialization<int>.AreEqual = NetworkVariableSerialization<int>.ValueEquals;
typeof(float), NetworkVariableSerialization<uint>.Serializer = new UintSerializer();
typeof(int), NetworkVariableSerialization<uint>.AreEqual = NetworkVariableSerialization<uint>.ValueEquals;
typeof(uint), NetworkVariableSerialization<long>.Serializer = new LongSerializer();
typeof(long), NetworkVariableSerialization<long>.AreEqual = NetworkVariableSerialization<long>.ValueEquals;
typeof(ulong), NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer();
typeof(short), NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableSerialization<ulong>.ValueEquals;
typeof(ushort), }
typeof(Vector2),
typeof(Vector3), /// <summary>
typeof(Vector2Int), /// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
typeof(Vector3Int), /// </summary>
typeof(Vector4), /// <typeparam name="T"></typeparam>
typeof(Quaternion), public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged
typeof(Color), {
typeof(Color32), NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>();
typeof(Ray), }
typeof(Ray2D)
}; /// <summary>
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_UnmanagedINetworkSerializable<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariableSerialization<T>.Serializer = new UnmanagedNetworkSerializableSerializer<T>();
}
/// <summary>
/// Registers a managed type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_ManagedINetworkSerializable<T>() where T : class, INetworkSerializable, new()
{
NetworkVariableSerialization<T>.Serializer = new ManagedNetworkSerializableSerializer<T>();
}
/// <summary>
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
/// serializers
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_FixedString<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
NetworkVariableSerialization<T>.Serializer = new FixedStringSerializer<T>();
}
/// <summary>
/// Registers a managed type that will be checked for equality using T.Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_ManagedIEquatable<T>() where T : class, IEquatable<T>
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsObject;
}
/// <summary>
/// Registers an unmanaged type that will be checked for equality using T.Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_UnmanagedIEquatable<T>() where T : unmanaged, IEquatable<T>
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEquals;
}
/// <summary>
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
/// equal if they are bitwise equivalent in memory
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_UnmanagedValueEquals<T>() where T : unmanaged
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ValueEquals;
}
/// <summary>
/// Registers a managed type that will be checked for equality using the == operator
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_ManagedClassEquals<T>() where T : class
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ClassEquals;
}
} }
/// <summary> /// <summary>
@@ -192,57 +383,61 @@ namespace Unity.Netcode
/// but there's no way to achieve the same thing with a class, this sets up various read/write schemes /// but there's no way to achieve the same thing with a class, this sets up various read/write schemes
/// based on which constraints are met by `T` using reflection, which is done at module load time. /// based on which constraints are met by `T` using reflection, which is done at module load time.
/// </summary> /// </summary>
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
[Serializable] [Serializable]
public static class NetworkVariableSerialization<T> where T : unmanaged public static class NetworkVariableSerialization<T>
{ {
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer(); internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
private static INetworkVariableSerializer<T> GetSerializer() internal delegate bool EqualsDelegate(ref T a, ref T b);
internal static EqualsDelegate AreEqual;
// Compares two values of the same unmanaged type by underlying memory
// Ignoring any overridden value checks
// Size is fixed
internal static unsafe bool ValueEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged
{ {
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T))) // get unmanaged pointers
{ var aptr = UnsafeUtility.AddressOf(ref a);
return new UnmanagedTypeSerializer<T>(); var bptr = UnsafeUtility.AddressOf(ref b);
}
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T))) // compare addresses
{ return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0;
return new UnmanagedTypeSerializer<T>();
}
if (typeof(Enum).IsAssignableFrom(typeof(T)))
{
return new UnmanagedTypeSerializer<T>();
} }
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T))) internal static bool EqualityEqualsObject<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
{ {
// Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods - if (a == null)
// one for an instance of the generic method taking BufferSerializerWriter as T, {
// one for an instance of the generic method taking BufferSerializerReader as T return b == null;
var writeMethod = (NetworkSerializableSerializer<T>.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter)));
var readMethod = (NetworkSerializableSerializer<T>.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader)));
return new NetworkSerializableSerializer<T> { WriteValue = writeMethod, ReadValue = readMethod };
} }
if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList<byte>).IsAssignableFrom(typeof(T))) if (b == null)
{ {
// Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed return false;
// with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr()
var getLength = (FixedStringSerializer<T>.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList<byte>.Length)));
var setLength = (FixedStringSerializer<T>.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList<byte>.Length)));
var getUnsafePtr = (FixedStringSerializer<T>.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr)));
return new FixedStringSerializer<T> { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr };
} }
return new FallbackSerializer<T>(); return a.Equals(b);
}
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
{
return a.Equals(b);
}
internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
{
return a == b;
} }
internal static void Write(FastBufferWriter writer, ref T value) internal static void Write(FastBufferWriter writer, ref T value)
{ {
s_Serializer.Write(writer, ref value); Serializer.Write(writer, ref value);
} }
internal static void Read(FastBufferReader reader, out T value) internal static void Read(FastBufferReader reader, ref T value)
{ {
s_Serializer.Read(reader, out value); Serializer.Read(reader, ref value);
} }
} }
} }

View File

@@ -1,4 +1,3 @@
using System;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
@@ -10,25 +9,8 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal interface ISceneManagerHandler internal interface ISceneManagerHandler
{ {
// Generic action to call when a scene is finished loading/unloading AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
struct SceneEventAction
{
internal uint SceneEventId;
internal Action<uint> EventAction;
/// <summary>
/// Used server-side for integration testing in order to
/// invoke the SceneEventProgress once done loading
/// </summary>
internal Action Completed;
internal void Invoke()
{
Completed?.Invoke();
EventAction.Invoke(SceneEventId);
}
}
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction); AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction);
} }
} }

View File

@@ -338,17 +338,17 @@ namespace Unity.Netcode
/// </summary> /// </summary>
private class DefaultSceneManagerHandler : ISceneManagerHandler private class DefaultSceneManagerHandler : ISceneManagerHandler
{ {
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction) public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
{ {
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); }); sceneEventProgress.SetAsyncOperation(operation);
return operation; return operation;
} }
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction) public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
{ {
var operation = SceneManager.UnloadSceneAsync(scene); var operation = SceneManager.UnloadSceneAsync(scene);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); }); sceneEventProgress.SetAsyncOperation(operation);
return operation; return operation;
} }
} }
@@ -887,12 +887,14 @@ namespace Unity.Netcode
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress) private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
{ {
var sceneEventData = BeginSceneEvent(); var sceneEventData = BeginSceneEvent();
var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true);
var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false);
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid; sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
sceneEventData.SceneHash = sceneEventProgress.SceneHash; sceneEventData.SceneHash = sceneEventProgress.SceneHash;
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType; sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients; sceneEventData.ClientsCompleted = clientsThatCompleted;
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode; sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList(); sceneEventData.ClientsTimedOut = clientsThatTimedOut;
var message = new SceneEventMessage var message = new SceneEventMessage
{ {
@@ -913,8 +915,8 @@ namespace Unity.Netcode
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash), SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
ClientId = NetworkManager.ServerClientId, ClientId = NetworkManager.ServerClientId,
LoadSceneMode = sceneEventProgress.LoadSceneMode, LoadSceneMode = sceneEventProgress.LoadSceneMode,
ClientsThatCompleted = sceneEventProgress.DoneClients, ClientsThatCompleted = clientsThatCompleted,
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(), ClientsThatTimedOut = clientsThatTimedOut,
}); });
if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted) if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted)
@@ -935,7 +937,7 @@ namespace Unity.Netcode
/// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene. /// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene.
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/> /// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/>
/// </summary> /// </summary>
/// <param name="sceneName">scene name to unload</param> /// <param name="scene"></param>
/// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns> /// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns>
public SceneEventProgressStatus UnloadScene(Scene scene) public SceneEventProgressStatus UnloadScene(Scene scene)
{ {
@@ -969,18 +971,9 @@ namespace Unity.Netcode
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted; sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
ScenesLoaded.Remove(scene.handle); ScenesLoaded.Remove(scene.handle);
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded }; sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction); sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
// If integration testing, IntegrationTestSceneHandler returns null
if (sceneUnload == null)
{
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
}
else
{
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
}
// Notify local server that a scene is going to be unloaded // Notify local server that a scene is going to be unloaded
OnSceneEvent?.Invoke(new SceneEvent() OnSceneEvent?.Invoke(new SceneEvent()
@@ -1024,9 +1017,10 @@ namespace Unity.Netcode
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!"); $"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
} }
m_IsSceneEventActive = true; m_IsSceneEventActive = true;
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded }); sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress);
ScenesLoaded.Remove(sceneHandle); ScenesLoaded.Remove(sceneHandle);
@@ -1070,7 +1064,7 @@ namespace Unity.Netcode
//Only if we are a host do we want register having loaded for the associated SceneEventProgress //Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
} }
} }
@@ -1119,8 +1113,10 @@ namespace Unity.Netcode
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex) // Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0) if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
{ {
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation }); sceneEventProgress.SceneEventId = sceneEventId;
sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress);
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload); SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
} }
} }
@@ -1134,6 +1130,7 @@ namespace Unity.Netcode
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/> /// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/>
/// </summary> /// </summary>
/// <param name="sceneName">the name of the scene to be loaded</param> /// <param name="sceneName">the name of the scene to be loaded</param>
/// <param name="loadSceneMode">how the scene will be loaded (single or additive mode)</param>
/// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns> /// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns>
public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSceneMode) public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSceneMode)
{ {
@@ -1179,18 +1176,9 @@ namespace Unity.Netcode
} }
// Now start loading the scene // Now start loading the scene
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded }; sceneEventProgress.SceneEventId = sceneEventId;
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction); sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
// If integration testing, IntegrationTestSceneHandler returns null var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
if (sceneLoad == null)
{
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
}
else
{
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
}
// Notify the local server that a scene loading event has begun // Notify the local server that a scene loading event has begun
OnSceneEvent?.Invoke(new SceneEvent() OnSceneEvent?.Invoke(new SceneEvent()
{ {
@@ -1354,9 +1342,10 @@ namespace Unity.Netcode
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single); SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
} }
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress.SceneEventId = sceneEventId;
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded }); sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress);
OnSceneEvent?.Invoke(new SceneEvent() OnSceneEvent?.Invoke(new SceneEvent()
{ {
@@ -1452,6 +1441,9 @@ namespace Unity.Netcode
} }
} }
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
sceneEventData.AddDespawnedInSceneNetworkObjects();
// Set the server's scene's handle so the client can build a look up table // Set the server's scene's handle so the client can build a look up table
sceneEventData.SceneHandle = scene.handle; sceneEventData.SceneHandle = scene.handle;
@@ -1487,7 +1479,7 @@ namespace Unity.Netcode
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress //Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
} }
EndSceneEvent(sceneEventId); EndSceneEvent(sceneEventId);
} }
@@ -1661,8 +1653,10 @@ namespace Unity.Netcode
if (!shouldPassThrough) if (!shouldPassThrough)
{ {
// If not, then load the scene // If not, then load the scene
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization }); sceneEventProgress.SceneEventId = sceneEventId;
sceneEventProgress.OnSceneEventCompleted = ClientLoadedSynchronization;
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
// Notify local client that a scene load has begun // Notify local client that a scene load has begun
OnSceneEvent?.Invoke(new SceneEvent() OnSceneEvent?.Invoke(new SceneEvent()
@@ -1879,7 +1873,7 @@ namespace Unity.Netcode
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
} }
EndSceneEvent(sceneEventId); EndSceneEvent(sceneEventId);
break; break;
@@ -1888,7 +1882,7 @@ namespace Unity.Netcode
{ {
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId)) if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
{ {
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId); SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
} }
// Notify the local server that the client has finished unloading a scene // Notify the local server that the client has finished unloading a scene
OnSceneEvent?.Invoke(new SceneEvent() OnSceneEvent?.Invoke(new SceneEvent()
@@ -2024,7 +2018,11 @@ namespace Unity.Netcode
ScenePlacedObjects.Clear(); ScenePlacedObjects.Clear();
} }
#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
// Just add every NetworkObject found that isn't already in the list // Just add every NetworkObject found that isn't already in the list
// With additive scenes, we can have multiple in-scene placed NetworkObjects with the same GlobalObjectIdHash value // With additive scenes, we can have multiple in-scene placed NetworkObjects with the same GlobalObjectIdHash value

View File

@@ -243,13 +243,38 @@ namespace Unity.Netcode
m_NetworkObjectsSync.Add(sobj); m_NetworkObjectsSync.Add(sobj);
} }
} }
// Sort by parents before children
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
// Sort by INetworkPrefabInstanceHandler implementation before the
// NetworkObjects spawned by the implementation
m_NetworkObjectsSync.Sort(SortNetworkObjects); m_NetworkObjectsSync.Sort(SortNetworkObjects);
// This is useful to know what NetworkObjects a client is going to be synchronized with
// as well as the order in which they will be deserialized
if (m_NetworkManager.LogLevel == LogLevel.Developer)
{
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
foreach (var networkObject in m_NetworkObjectsSync)
{
messageBuilder.Append($"{networkObject.name}");
}
NetworkLog.LogInfo(messageBuilder.ToString());
}
} }
internal void AddDespawnedInSceneNetworkObjects() internal void AddDespawnedInSceneNetworkObjects()
{ {
m_DespawnedInSceneObjectsSync.Clear(); m_DespawnedInSceneObjectsSync.Clear();
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager); // Find all active and non-active in-scene placed NetworkObjects
#if UNITY_2023_1_OR_NEWER
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager);
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
#endif
foreach (var sobj in inSceneNetworkObjects) foreach (var sobj in inSceneNetworkObjects)
{ {
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned) if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
@@ -323,6 +348,32 @@ namespace Unity.Netcode
return 0; return 0;
} }
/// <summary>
/// Sorts the synchronization order of the NetworkObjects to be serialized
/// by parents before children.
/// </summary>
/// <remarks>
/// This only handles late joining players. Spawning and nesting several children
/// dynamically is still handled by the orphaned child list when deserialized out of
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
/// dropped and re-sent but child object is received and processed)
/// </remarks>
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
{
// If the first has a parent, move the first down
if (first.transform.parent != null)
{
return 1;
}
else // If the second has a parent and the first does not, then move the first up
if (second.transform.parent != null)
{
return -1;
}
return 0;
}
/// <summary> /// <summary>
/// Client and Server Side: /// Client and Server Side:
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>) /// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
@@ -334,7 +385,7 @@ namespace Unity.Netcode
writer.WriteValueSafe(SceneEventType); writer.WriteValueSafe(SceneEventType);
// 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)
@@ -398,27 +449,26 @@ namespace Unity.Netcode
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 += (int)(noStop - noStart);
} }
// Write the number of despawned in-scene placed NetworkObjects // Write the number of despawned in-scene placed NetworkObjects
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count()); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
// Write the scene handle and GlobalObjectIdHash value // Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i) for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{ {
var noStart = writer.Position; var noStart = writer.Position;
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position; var noStop = writer.Position;
totalBytes += (int)(noStop - noStart); totalBytes += (int)(noStop - noStart);
} }
@@ -452,8 +502,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);
@@ -462,6 +510,15 @@ namespace Unity.Netcode
} }
} }
// Write the number of despawned in-scene placed NetworkObjects
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
// Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
}
var tailPosition = writer.Position; var tailPosition = writer.Position;
// Reposition to our count position to the head before we wrote our object count // Reposition to our count position to the head before we wrote our object count
writer.Seek(headPosition); writer.Seek(headPosition);
@@ -479,7 +536,8 @@ 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); reader.ReadValueSafe(out byte loadSceneMode);
LoadSceneMode = (LoadSceneMode)loadSceneMode;
if (SceneEventType != SceneEventType.Synchronize) if (SceneEventType != SceneEventType.Synchronize)
{ {
@@ -570,15 +628,19 @@ 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)
DeserializeDespawnedInScenePlacedNetworkObjects();
} }
finally finally
{ {
@@ -600,7 +662,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)
{ {
@@ -702,38 +768,11 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Client Side: /// For synchronizing any despawned in-scene placed NetworkObjects that were
/// During the processing of a server sent Event_Sync, this method will be called for each scene once /// despawned by the server during synchronization or scene loading
/// it is finished loading. The client will also build a list of NetworkObjects that it spawned during
/// this process which will be used as part of the Event_Sync_Complete response.
/// </summary> /// </summary>
/// <param name="networkManager"></param> private void DeserializeDespawnedInScenePlacedNetworkObjects()
internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
{ {
try
{
// Process all spawned NetworkObjects for this network session
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
for (int i = 0; i < newObjectsCount; i++)
{
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer);
var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager);
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
{
m_NetworkObjectsSync.Add(spawnedNetworkObject);
}
}
// Process all de-spawned in-scene NetworkObjects for this network session // Process all de-spawned in-scene NetworkObjects for this network session
m_DespawnedInSceneObjects.Clear(); m_DespawnedInSceneObjects.Clear();
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
@@ -742,8 +781,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))
{ {
@@ -753,13 +792,24 @@ namespace Unity.Netcode
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle)) if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
{ {
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle]; var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
// 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(); c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#endif
foreach (var inSceneObject in inSceneNetworkObjects) foreach (var inSceneObject in inSceneNetworkObjects)
{
if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash))
{ {
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject); sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
} }
}
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time // Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects); sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
} }
@@ -793,7 +843,6 @@ namespace Unity.Netcode
{ {
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]); m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
} }
} }
else else
{ {
@@ -801,6 +850,46 @@ namespace Unity.Netcode
} }
} }
} }
/// <summary>
/// Client Side:
/// During the processing of a server sent Event_Sync, this method will be called for each scene once
/// it is finished loading. The client will also build a list of NetworkObjects that it spawned during
/// this process which will be used as part of the Event_Sync_Complete response.
/// </summary>
/// <param name="networkManager"></param>
internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
{
try
{
// Process all spawned NetworkObjects for this network session
InternalBuffer.ReadValueSafe(out int newObjectsCount);
for (int i = 0; i < newObjectsCount; i++)
{
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer);
// If the sceneObject is in-scene placed, then set the scene being synchronized
if (sceneObject.IsSceneObject)
{
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
}
var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager);
// If we failed to deserialize the NetowrkObject then don't add null to the list
if (spawnedNetworkObject != null)
{
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
{
m_NetworkObjectsSync.Add(spawnedNetworkObject);
}
}
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
}
finally finally
{ {
InternalBuffer.Dispose(); InternalBuffer.Dispose();

View File

@@ -58,12 +58,13 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// List of clientIds of those clients that is done loading the scene. /// List of clientIds of those clients that is done loading the scene.
/// </summary> /// </summary>
internal List<ulong> DoneClients { get; } = new List<ulong>(); internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
internal List<ulong> ClientsThatDisconnected = new List<ulong>();
/// <summary> /// <summary>
/// The local time when the scene event was "roughly started" /// This is when the current scene event will have timed out
/// </summary> /// </summary>
internal float TimeAtInitiation { get; } internal float WhenSceneEventHasTimedOut;
/// <summary> /// <summary>
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out. /// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
@@ -75,17 +76,15 @@ namespace Unity.Netcode
/// </summary> /// </summary>
internal OnCompletedDelegate OnComplete; internal OnCompletedDelegate OnComplete;
/// <summary> internal Action<uint> OnSceneEventCompleted;
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
/// </summary>
internal bool IsCompleted { get; private set; }
internal bool TimedOut { get; private set; }
/// <summary> /// <summary>
/// If all clients are done loading the scene, at the moment of completed. /// This will make sure that we only have timed out if we never completed
/// </summary> /// </summary>
internal bool AreAllClientsDoneLoading { get; private set; } internal bool HasTimedOut()
{
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
}
/// <summary> /// <summary>
/// The hash value generated from the full scene path /// The hash value generated from the full scene path
@@ -93,9 +92,10 @@ namespace Unity.Netcode
internal uint SceneHash { get; set; } internal uint SceneHash { get; set; }
internal Guid Guid { get; } = Guid.NewGuid(); internal Guid Guid { get; } = Guid.NewGuid();
internal uint SceneEventId;
private Coroutine m_TimeOutCoroutine; private Coroutine m_TimeOutCoroutine;
private AsyncOperation m_SceneLoadOperation; private AsyncOperation m_AsyncOperation;
private NetworkManager m_NetworkManager { get; } private NetworkManager m_NetworkManager { get; }
@@ -105,21 +105,85 @@ namespace Unity.Netcode
internal LoadSceneMode LoadSceneMode; internal LoadSceneMode LoadSceneMode;
internal List<ulong> ClientsThatStartedSceneEvent; internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
{
var clients = new List<ulong>();
if (completedSceneEvent)
{
// If we are the host, then add the host-client to the list
// of clients that completed if the AsyncOperation is done.
if (m_NetworkManager.IsHost && m_AsyncOperation.isDone)
{
clients.Add(m_NetworkManager.LocalClientId);
}
// Add all clients that completed the scene event
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (clientStatus.Value == completedSceneEvent)
{
clients.Add(clientStatus.Key);
}
}
}
else
{
// If we are the host, then add the host-client to the list
// of clients that did not complete if the AsyncOperation is
// not done.
if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone)
{
clients.Add(m_NetworkManager.LocalClientId);
}
// If we are getting the list of clients that have not completed the
// scene event, then add any clients that disconnected during this
// scene event.
clients.AddRange(ClientsThatDisconnected);
}
return clients;
}
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started) internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
{ {
if (status == SceneEventProgressStatus.Started) if (status == SceneEventProgressStatus.Started)
{ {
// Track the clients that were connected when we started this event
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
m_NetworkManager = networkManager; m_NetworkManager = networkManager;
if (networkManager.IsServer)
{
m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
// Track the clients that were connected when we started this event
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
{
// Ignore the host client
if (NetworkManager.ServerClientId == connectedClientId)
{
continue;
}
ClientsProcessingSceneEvent.Add(connectedClientId, false);
}
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress()); m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
TimeAtInitiation = Time.realtimeSinceStartup; }
} }
Status = status; Status = status;
} }
/// <summary>
/// Remove the client from the clients processing the current scene event
/// Add this client to the clients that disconnected list
/// </summary>
private void OnClientDisconnectCallback(ulong clientId)
{
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsThatDisconnected.Add(clientId);
ClientsProcessingSceneEvent.Remove(clientId);
}
}
/// <summary> /// <summary>
/// Coroutine that checks to see if the scene event is complete every network tick period. /// Coroutine that checks to see if the scene event is complete every network tick period.
/// This will handle completing the scene event when one or more client(s) disconnect(s) /// This will handle completing the scene event when one or more client(s) disconnect(s)
@@ -129,79 +193,107 @@ namespace Unity.Netcode
internal IEnumerator TimeOutSceneEventProgress() internal IEnumerator TimeOutSceneEventProgress()
{ {
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate); var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
while (!TimedOut && !IsCompleted) while (!HasTimedOut())
{ {
yield return waitForNetworkTick; yield return waitForNetworkTick;
CheckCompletion(); TryFinishingSceneEventProgress();
if (!IsCompleted)
{
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
} }
} }
}
internal void AddClientAsDone(ulong clientId)
{
DoneClients.Add(clientId);
CheckCompletion();
}
internal void RemoveClientAsDone(ulong clientId)
{
DoneClients.Remove(clientId);
CheckCompletion();
}
internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
{
m_SceneLoadOperation = sceneLoadOperation;
m_SceneLoadOperation.completed += operation => CheckCompletion();
}
/// <summary> /// <summary>
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific) /// Sets the client's scene event progress to finished/true
/// scene loading and unloading.
///
/// Note: During integration testing we must queue all scene loading and unloading requests for
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
/// conflicts when the <see cref="SceneManager.sceneLoaded"/> and <see cref="SceneManager.sceneUnloaded"/>
/// events are triggered. The Completed action simulates the <see cref="AsyncOperation.completed"/> event.
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
/// </summary> /// </summary>
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction) internal void ClientFinishedSceneEvent(ulong clientId)
{ {
sceneEventAction.Completed = SetComplete; if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsProcessingSceneEvent[clientId] = true;
TryFinishingSceneEventProgress();
}
} }
/// <summary> /// <summary>
/// Finalizes the SceneEventProgress /// Determines if the scene event has finished for both
/// client(s) and server.
/// </summary> /// </summary>
internal void SetComplete() /// <remarks>
/// The server checks if all known clients processing this scene event
/// have finished and then it returns its local AsyncOperation status.
/// Clients finish when their AsyncOperation finishes.
/// </remarks>
private bool HasFinished()
{ {
IsCompleted = true; // If the network session is terminated/terminating then finish tracking
AreAllClientsDoneLoading = true; // this scene event
if (!IsNetworkSessionActive())
{
return true;
}
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking // Clients skip over this
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this))) foreach (var clientStatus in ClientsProcessingSceneEvent)
{ {
if (!clientStatus.Value)
{
return false;
}
}
// Return the local scene event's AsyncOperation status
// Note: Integration tests process scene loading through a queue
// and the AsyncOperation could not be assigned for several
// network tick periods. Return false if that is the case.
return m_AsyncOperation == null ? false : m_AsyncOperation.isDone;
}
/// <summary>
/// Sets the AsyncOperation for the scene load/unload event
/// </summary>
internal void SetAsyncOperation(AsyncOperation asyncOperation)
{
m_AsyncOperation = asyncOperation;
m_AsyncOperation.completed += new Action<AsyncOperation>(asyncOp2 =>
{
// Don't invoke the callback if the network session is disconnected
// during a SceneEventProgress
if (IsNetworkSessionActive())
{
OnSceneEventCompleted?.Invoke(SceneEventId);
}
// Go ahead and try finishing even if the network session is terminated/terminating
// as we might need to stop the coroutine
TryFinishingSceneEventProgress();
});
}
internal bool IsNetworkSessionActive()
{
return m_NetworkManager != null && m_NetworkManager.IsListening && !m_NetworkManager.ShutdownInProgress;
}
/// <summary>
/// Will try to finish the current scene event in progress as long as
/// all conditions are met.
/// </summary>
internal void TryFinishingSceneEventProgress()
{
if (HasFinished() || HasTimedOut())
{
// Don't attempt to finalize this scene event if we are no longer listening or a shutdown is in progress
if (IsNetworkSessionActive())
{
OnComplete?.Invoke(this);
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid); m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
} }
if (m_TimeOutCoroutine != null)
{
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine); m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
} }
internal void CheckCompletion()
{
try
{
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
{
SetComplete();
}
}
catch (Exception ex)
{
Debug.LogException(ex);
} }
} }
} }

View File

@@ -2,6 +2,9 @@ using System.Runtime.CompilerServices;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Utility class to count the number of bytes or bits needed to serialize a value.
/// </summary>
public static class BitCounter public static class BitCounter
{ {
// Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest) // Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest)

View File

@@ -21,6 +21,8 @@ namespace Unity.Netcode
private const int k_BitsPerByte = 8; private const int k_BitsPerByte = 8;
private int BytePosition => m_BitPosition >> 3;
/// <summary> /// <summary>
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary. /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
/// </summary> /// </summary>
@@ -98,11 +100,6 @@ namespace Unity.Netcode
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!"); throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
} }
if (bitCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
}
int checkPos = (int)(m_BitPosition + bitCount); int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseReadMark) if (checkPos > m_AllowedBitwiseReadMark)
{ {
@@ -165,7 +162,7 @@ namespace Unity.Netcode
#endif #endif
int offset = m_BitPosition & 7; int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3; int pos = BytePosition;
bit = (m_BufferPointer[pos] & (1 << offset)) != 0; bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
++m_BitPosition; ++m_BitPosition;
} }
@@ -175,7 +172,7 @@ namespace Unity.Netcode
{ {
var val = new T(); var val = new T();
byte* ptr = ((byte*)&val) + offsetBytes; byte* ptr = ((byte*)&val) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position; byte* bufferPointer = m_BufferPointer + BytePosition;
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
m_BitPosition += bytesToRead * k_BitsPerByte; m_BitPosition += bytesToRead * k_BitsPerByte;

View File

@@ -29,6 +29,8 @@ namespace Unity.Netcode
get => (m_BitPosition & 7) == 0; get => (m_BitPosition & 7) == 0;
} }
private int BytePosition => m_BitPosition >> 3;
internal unsafe BitWriter(FastBufferWriter writer) internal unsafe BitWriter(FastBufferWriter writer)
{ {
m_Writer = writer; m_Writer = writer;
@@ -181,7 +183,7 @@ namespace Unity.Netcode
#endif #endif
int offset = m_BitPosition & 7; int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3; int pos = BytePosition;
++m_BitPosition; ++m_BitPosition;
m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset))); m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
} }
@@ -190,7 +192,7 @@ namespace Unity.Netcode
private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
{ {
byte* ptr = ((byte*)&value) + offsetBytes; byte* ptr = ((byte*)&value) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position; byte* bufferPointer = m_BufferPointer + BytePosition;
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite); UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
m_BitPosition += bytesToWrite * k_BitsPerByte; m_BitPosition += bytesToWrite * k_BitsPerByte;

View File

@@ -62,80 +62,507 @@ namespace Unity.Netcode
return m_Implementation.GetFastBufferWriter(); return m_Implementation.GetFastBufferWriter();
} }
/// <summary>
/// Read or write a string
/// </summary>
/// <param name="s">The value to read/write</param>
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValue(ref s, oneByteChars); public void SerializeValue(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValue(ref s, oneByteChars);
/// <summary>
/// Read or write a single byte
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref byte value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref byte value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of primitive values (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an enum value
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of enum values
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a struct value implementing ISerializeByMemcpy
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of struct values implementing ISerializeByMemcpy
/// </summary>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a struct or class value implementing INetworkSerializable
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of struct or class values implementing INetworkSerializable
/// </summary>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Vector2 value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Vector2 value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector2 value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Vector2 values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Vector3 value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Vector3 values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Vector2Int value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Vector2Int value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector2Int value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Vector2Int values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Vector2Int[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector2Int[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Vector3Int value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Vector3Int value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector3Int value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Vector3Int values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Vector3Int[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector3Int[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Vector4 value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Vector4 values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Quaternion value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Quaternion values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Quaternion[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Quaternion[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Color value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Color value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Color value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Color values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Color[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Color[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Color32 value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Color32 value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Color32 value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Color32 values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Color32[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Color32[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Ray value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Ray value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Ray value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Ray values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Ray[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Ray[] value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a Ray2D value
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write an array of Ray2D values
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(ref value); public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(ref value);
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes. // There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
// INativeList<bool> provides the Length property // INativeList<bool> provides the Length property
// IUTF8Bytes provides GetUnsafePtr() // IUTF8Bytes provides GetUnsafePtr()
// Those two are necessary to serialize FixedStrings efficiently // Those two are necessary to serialize FixedStrings efficiently
// - otherwise we'd just be memcpying the whole thing even if // - otherwise we'd just be memcpy'ing the whole thing even if
// most of it isn't used. // most of it isn't used.
/// <summary>
/// Read or write a FixedString value
/// </summary>
/// <typeparam name="T">The network serializable type</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution of FixedStrings</param>
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default) public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValue(ref value); where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValue(ref value);
/// <summary>
/// Read or write a NetworkSerializable value.
/// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only.
/// </summary>
/// <typeparam name="T">The network serializable type</typeparam>
/// <param name="value">The values to read/write</param>
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value); public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
/// <summary>
/// Performs an advance check to ensure space is available to read/write one or more values.
/// This provides a performance benefit for serializing multiple values using the
/// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be
/// noticeable if serializing a very large number of items.
/// </summary>
/// <param name="amount"></param>
/// <returns></returns>
public bool PreCheck(int amount) public bool PreCheck(int amount)
{ {
return m_Implementation.PreCheck(amount); return m_Implementation.PreCheck(amount);
} }
/// <summary>
/// Serialize a string, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="s">The value to read/write</param>
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValuePreChecked(ref s, oneByteChars); public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
/// <summary>
/// Serialize a byte, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref byte value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref byte value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a primitive, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable type</typeparam>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize an array of primitives, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable types in an array</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution of primitives</param>
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize an enum, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable type</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution of enums</param>
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize an array of enums, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable types in an array</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution of enums</param>
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a struct, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable type</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution of structs</param>
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize an array of structs, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable types in an array</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution of structs</param>
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector2, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector2 value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector2 value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector2 array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector3, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector3 array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector2Int, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector2Int value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector2Int value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector2Int array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The values to read/write</param>
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector3Int, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector3Int value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector3Int value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector3Int array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector4, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Vector4Array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Quaternion, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Quaternion array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Color, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Color value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Color value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Color array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Color[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Color[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Color32, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Color32 value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Color32 value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Color32 array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Ray, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Ray array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Ray2D, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
/// <summary>
/// Serialize a Ray2D array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value); public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes. // There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
@@ -144,6 +571,16 @@ namespace Unity.Netcode
// Those two are necessary to serialize FixedStrings efficiently // Those two are necessary to serialize FixedStrings efficiently
// - otherwise we'd just be memcpying the whole thing even if // - otherwise we'd just be memcpying the whole thing even if
// most of it isn't used. // most of it isn't used.
/// <summary>
/// Serialize a FixedString, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The network serializable type</typeparam>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution for fixed strings</param>
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default) public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValuePreChecked(ref value); where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValuePreChecked(ref value);
} }

View File

@@ -34,7 +34,7 @@ namespace Unity.Netcode
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value); public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value); public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializableInPlace(ref value);
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value); public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default) public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)

View File

@@ -6,6 +6,7 @@ namespace Unity.Netcode
{ {
/// <summary> /// <summary>
/// Utility class for packing values in serialization. /// Utility class for packing values in serialization.
/// <seealso cref="ByteUnpacker"/> to unpack packed values.
/// </summary> /// </summary>
public static class BytePacker public static class BytePacker
{ {
@@ -49,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>
@@ -60,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>
@@ -97,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.
@@ -108,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.
@@ -119,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.
@@ -127,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.
@@ -135,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.
@@ -143,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.
@@ -151,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
@@ -281,196 +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>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const ushort BitPackedUshortMax = (1 << 15) - 1; public const ushort BitPackedUshortMax = (1 << 15) - 1;
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const short BitPackedShortMax = (1 << 14) - 1; public const short BitPackedShortMax = (1 << 14) - 1;
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const short BitPackedShortMin = -(1 << 14); public const short BitPackedShortMin = -(1 << 14);
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const uint BitPackedUintMax = (1 << 30) - 1; public const uint BitPackedUintMax = (1 << 30) - 1;
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const int BitPackedIntMax = (1 << 29) - 1; public const int BitPackedIntMax = (1 << 29) - 1;
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const int BitPackedIntMin = -(1 << 29); public const int BitPackedIntMin = -(1 << 29);
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const ulong BitPackedULongMax = (1L << 61) - 1; public const ulong BitPackedULongMax = (1L << 61) - 1;
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const long BitPackedLongMax = (1L << 60) - 1; public const long BitPackedLongMax = (1L << 60) - 1;
/// <summary>
/// Obsolete value that no longer carries meaning. Do not use.
/// </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

@@ -4,14 +4,25 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Byte Unpacking Helper Class
/// Use this class to unpack values during deserialization for values that were packed.
/// <seealso cref="BytePacker"/> to pack unpacked values
/// </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)]
public void ReadValuePacked<T>(FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value); public void ReadValuePacked<T>(FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value);
#else #else
/// <summary>
/// Read a packed enum value
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value that's read</param>
/// <typeparam name="TEnum">Type of enum to read</typeparam>
/// <exception cref="InvalidOperationException">Throws InvalidOperationException if an enum somehow ends up not being the size of a byte, short, int, or long (which should be impossible)</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void ReadValuePacked<TEnum>(FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum public static unsafe void ReadValuePacked<TEnum>(FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum
{ {
@@ -46,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);
} }
@@ -58,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);
} }
@@ -97,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.
@@ -109,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.
@@ -123,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;
} }
@@ -133,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.
@@ -145,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.
@@ -153,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.
@@ -163,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>
@@ -329,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");
@@ -338,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>
@@ -374,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");
@@ -383,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>
@@ -428,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");
@@ -437,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

@@ -6,6 +6,12 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Optimized class used for reading values from a byte stream
/// <seealso cref="FastBufferWriter"/>
/// <seealso cref="BytePacker"/>
/// <seealso cref="ByteUnpacker"/>
/// </summary>
public struct FastBufferReader : IDisposable public struct FastBufferReader : IDisposable
{ {
internal struct ReaderHandle internal struct ReaderHandle
@@ -59,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;
} }
@@ -87,15 +93,15 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Create a FastBufferReader from a NativeArray. /// Create a FastBufferReader from a NativeArray.
/// ///
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in. /// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
/// FastBufferReader will then own the data. /// FastBufferReader will then own the data.
/// ///
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario, /// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly. /// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using /// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive /// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive
/// the context in which it was created (it should neither be returned from that function nor /// the context in which it was created (it should neither be returned from that function nor
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set /// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"></param> param is explicitly set
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
/// should manually call Dispose() when it is no longer needed. /// should manually call Dispose() when it is no longer needed.
/// </summary> /// </summary>
@@ -162,15 +168,15 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Create a FastBufferReader from an existing byte buffer. /// Create a FastBufferReader from an existing byte buffer.
/// ///
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in. /// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
/// FastBufferReader will then own the data. /// FastBufferReader will then own the data.
/// ///
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario, /// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly. /// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using /// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
/// the context in which it was created (it should neither be returned from that function nor /// the context in which it was created (it should neither be returned from that function nor
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set /// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"></param> param is explicitly set
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
/// should manually call Dispose() when it is no longer needed. /// should manually call Dispose() when it is no longer needed.
/// </summary> /// </summary>
@@ -187,15 +193,15 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Create a FastBufferReader from a FastBufferWriter. /// Create a FastBufferReader from a FastBufferWriter.
/// ///
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in. /// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
/// FastBufferReader will then own the data. /// FastBufferReader will then own the data.
/// ///
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario, /// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly. /// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using /// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
/// the context in which it was created (it should neither be returned from that function nor /// the context in which it was created (it should neither be returned from that function nor
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"> param is explicitly set /// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"></param> param is explicitly set
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller /// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
/// should manually call Dispose() when it is no longer needed. /// should manually call Dispose() when it is no longer needed.
/// </summary> /// </summary>
@@ -214,10 +220,10 @@ namespace Unity.Netcode
/// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to /// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to
/// a Persistent one to be processed later. /// a Persistent one to be processed later.
/// ///
/// A new buffer will be created using the given <param name="copyAllocator"> and the value will be copied in. /// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
/// FastBufferReader will then own the data. /// FastBufferReader will then own the data.
/// ///
/// The exception to this is when the <param name="copyAllocator"> passed in is Allocator.None. In this scenario, /// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly. /// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using /// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive /// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
@@ -235,7 +241,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Frees the allocated buffer /// <see cref="IDisposable"/> implementation that frees the allocated buffer
/// </summary> /// </summary>
public unsafe void Dispose() public unsafe void Dispose()
{ {
@@ -335,6 +341,7 @@ namespace Unity.Netcode
/// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
/// operations in release builds. /// operations in release builds.
/// </summary> /// </summary>
/// <typeparam name="T">the type `T` of the value you are trying to read</typeparam>
/// <param name="value">The value you want to read</param> /// <param name="value">The value you want to read</param>
/// <returns>True if the read is allowed, false otherwise</returns> /// <returns>True if the read is allowed, false otherwise</returns>
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception> /// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
@@ -364,7 +371,7 @@ namespace Unity.Netcode
/// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward. /// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward.
/// </summary> /// </summary>
/// <param name="bytes"></param> /// <param name="bytes"></param>
/// <returns></returns> /// <returns>true upon success</returns>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe bool TryBeginReadInternal(int bytes) internal unsafe bool TryBeginReadInternal(int bytes)
@@ -393,7 +400,7 @@ namespace Unity.Netcode
/// Returns an array representation of the underlying byte buffer. /// Returns an array representation of the underlying byte buffer.
/// !!Allocates a new array!! /// !!Allocates a new array!!
/// </summary> /// </summary>
/// <returns></returns> /// <returns>byte array</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte[] ToArray() public unsafe byte[] ToArray()
{ {
@@ -408,7 +415,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets a direct pointer to the underlying buffer /// Gets a direct pointer to the underlying buffer
/// </summary> /// </summary>
/// <returns></returns> /// <returns><see cref="byte"/> pointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtr() public unsafe byte* GetUnsafePtr()
{ {
@@ -418,7 +425,7 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Gets a direct pointer to the underlying buffer at the current read position /// Gets a direct pointer to the underlying buffer at the current read position
/// </summary> /// </summary>
/// <returns></returns> /// <returns><see cref="byte"/> pointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtrAtCurrentPosition() public unsafe byte* GetUnsafePtrAtCurrentPosition()
{ {
@@ -428,8 +435,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Read an INetworkSerializable /// Read an INetworkSerializable
/// </summary> /// </summary>
/// <param name="value">INetworkSerializable instance</param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="value">INetworkSerializable instance</param>
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
public void ReadNetworkSerializable<T>(out T value) where T : INetworkSerializable, new() public void ReadNetworkSerializable<T>(out T value) where T : INetworkSerializable, new()
{ {
@@ -442,7 +449,7 @@ namespace Unity.Netcode
/// Read an array of INetworkSerializables /// Read an array of INetworkSerializables
/// </summary> /// </summary>
/// <param name="value">INetworkSerializable instance</param> /// <param name="value">INetworkSerializable instance</param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T">the array to read the values of type `T` into</typeparam>
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
public void ReadNetworkSerializable<T>(out T[] value) where T : INetworkSerializable, new() public void ReadNetworkSerializable<T>(out T[] value) where T : INetworkSerializable, new()
{ {
@@ -454,6 +461,19 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Read an INetworkSerializable in-place, without constructing a new one
/// Note that this will NOT check for null before calling NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value">INetworkSerializable instance</param>
/// <exception cref="NotImplementedException"></exception>
public void ReadNetworkSerializableInPlace<T>(ref T value) where T : INetworkSerializable
{
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(this));
value.NetworkSerialize(bufferSerializer);
}
/// <summary> /// <summary>
/// Reads a string /// Reads a string
/// NOTE: ALLOCATES /// NOTE: ALLOCATES
@@ -537,7 +557,7 @@ namespace Unity.Netcode
/// <param name="value">Value to read</param> /// <param name="value">Value to read</param>
/// <param name="bytesToRead">Number of bytes</param> /// <param name="bytesToRead">Number of bytes</param>
/// <param name="offsetBytes">Offset into the value to write the bytes</param> /// <param name="offsetBytes">Offset into the value to write the bytes</param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T">the type value to read the value into</typeparam>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
/// <exception cref="OverflowException"></exception> /// <exception cref="OverflowException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -738,127 +758,522 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Read a NetworkSerializable value
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
/// <summary>
/// Read a NetworkSerializable array
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
/// <summary>
/// Read a NetworkSerializable value
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
/// <summary>
/// Read a NetworkSerializable array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value); public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
/// <summary>
/// Read a struct
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value); public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
/// <summary>
/// Read a struct array
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value); public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
/// <summary>
/// Read a struct
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value); public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a struct array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value); public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value); public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
/// <summary>
/// Read a primitive value array (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value); public void ReadValue<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
/// <summary>
/// Read a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value); public void ReadValueSafe<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value); public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
/// <summary>
/// Read an enum value
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value); public void ReadValue<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
/// <summary>
/// Read an enum array
/// </summary>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value); public void ReadValue<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <summary>
/// Read an enum value
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value); public void ReadValueSafe<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <summary>
/// Read an enum array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value); public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector2
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector2 value) => ReadUnmanaged(out value); public void ReadValue(out Vector2 value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector2 array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector2[] value) => ReadUnmanaged(out value); public void ReadValue(out Vector2[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector3
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector3 value) => ReadUnmanaged(out value); public void ReadValue(out Vector3 value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector3 array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value); public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector2Int
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector2Int value) => ReadUnmanaged(out value); public void ReadValue(out Vector2Int value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector2Int array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector2Int[] value) => ReadUnmanaged(out value); public void ReadValue(out Vector2Int[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector3Int
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector3Int value) => ReadUnmanaged(out value); public void ReadValue(out Vector3Int value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector3Int array
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector3Int[] value) => ReadUnmanaged(out value); public void ReadValue(out Vector3Int[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector4
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector4 value) => ReadUnmanaged(out value); public void ReadValue(out Vector4 value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector4
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value); public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Quaternion
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Quaternion value) => ReadUnmanaged(out value); public void ReadValue(out Quaternion value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Quaternion array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Quaternion[] value) => ReadUnmanaged(out value); public void ReadValue(out Quaternion[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Color
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Color value) => ReadUnmanaged(out value); public void ReadValue(out Color value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Color array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Color[] value) => ReadUnmanaged(out value); public void ReadValue(out Color[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Color32
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Color32 value) => ReadUnmanaged(out value); public void ReadValue(out Color32 value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Color32 array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Color32[] value) => ReadUnmanaged(out value); public void ReadValue(out Color32[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Ray
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Ray value) => ReadUnmanaged(out value); public void ReadValue(out Ray value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Ray array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Ray[] value) => ReadUnmanaged(out value); public void ReadValue(out Ray[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Ray2D
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Ray2D value) => ReadUnmanaged(out value); public void ReadValue(out Ray2D value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Ray2D array
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValue(out Ray2D[] value) => ReadUnmanaged(out value); public void ReadValue(out Ray2D[] value) => ReadUnmanaged(out value);
/// <summary>
/// Read a Vector2
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector2 value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector2 value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector2 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector2[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector2[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector3
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector3 value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector3 value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector3 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector2Int
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector2Int value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector2Int value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector2Int array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector2Int[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector2Int[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector3Int
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector3Int value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector3Int value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector3Int array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector3Int[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector3Int[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector4
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Vector4 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Quaternion
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Quaternion value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Quaternion value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Quaternion array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Quaternion[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Quaternion[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Color
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Color value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Color value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Collor array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Color[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Color[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Color32
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Color32 value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Color32 value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Color32 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Color32[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Color32[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Ray
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Ray value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Ray value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Ray array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Ray[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Ray[] value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Ray2D
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value);
/// <summary>
/// Read a Ray2D array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the values to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value); public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value);
@@ -868,6 +1283,17 @@ namespace Unity.Netcode
// Those two are necessary to serialize FixedStrings efficiently // Those two are necessary to serialize FixedStrings efficiently
// - otherwise we'd just be memcpying the whole thing even if // - otherwise we'd just be memcpying the whole thing even if
// most of it isn't used. // most of it isn't used.
/// <summary>
/// Read a FixedString value.
/// This method is a little difficult to use, since you have to know the size of the string before
/// reading it, but is useful when the string is a known, fixed size. Note that the size of the
/// string is also encoded, so the size to call TryBeginRead on is actually the fixed size (in bytes)
/// plus sizeof(int)
/// </summary>
/// <param name="value">the value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValue<T>(out T value, FastBufferWriter.ForFixedStrings unused = default) public unsafe void ReadValue<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes where T : unmanaged, INativeList<byte>, IUTF8Bytes
@@ -878,6 +1304,16 @@ namespace Unity.Netcode
ReadBytes(value.GetUnsafePtr(), length); ReadBytes(value.GetUnsafePtr(), length);
} }
/// <summary>
/// Read a FixedString value.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValueSafe<T>(out T value, FastBufferWriter.ForFixedStrings unused = default) public unsafe void ReadValueSafe<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes where T : unmanaged, INativeList<byte>, IUTF8Bytes
@@ -887,5 +1323,24 @@ namespace Unity.Netcode
value.Length = length; value.Length = length;
ReadBytesSafe(value.GetUnsafePtr(), length); ReadBytesSafe(value.GetUnsafePtr(), length);
} }
/// <summary>
/// Read a FixedString value.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">the value to read</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValueSafeInPlace<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
ReadUnmanagedSafe(out int length);
value.Length = length;
ReadBytesSafe(value.GetUnsafePtr(), length);
}
} }
} }

View File

@@ -6,6 +6,12 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Optimized class used for writing values into a byte stream
/// <seealso cref="FastBufferReader"/>
/// <seealso cref="BytePacker"/>
/// <seealso cref="ByteUnpacker"/>
/// </summary>
public struct FastBufferWriter : IDisposable public struct FastBufferWriter : IDisposable
{ {
internal struct WriterHandle internal struct WriterHandle
@@ -108,7 +114,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Frees the allocated buffer /// <see cref="IDisposable"/> implementation that frees the allocated buffer
/// </summary> /// </summary>
public unsafe void Dispose() public unsafe void Dispose()
{ {
@@ -267,7 +273,8 @@ namespace Unity.Netcode
/// operations in release builds. Instead, attempting to write past the marked position in release builds /// operations in release builds. Instead, attempting to write past the marked position in release builds
/// will write to random memory and cause undefined behavior, likely including instability and crashes. /// will write to random memory and cause undefined behavior, likely including instability and crashes.
/// </summary> /// </summary>
/// <param name="value">The value you want to write</param> /// <typeparam name="T">The value type to write</typeparam>
/// <param name="value">The value of the type `T` you want to write</param>
/// <returns>True if the write is allowed, false otherwise</returns> /// <returns>True if the write is allowed, false otherwise</returns>
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception> /// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -792,151 +799,590 @@ namespace Unity.Netcode
} }
} }
// These structs enable overloading of WriteValue with different generic constraints. /// <summary>
// The compiler's actually able to distinguish between overloads based on generic constraints. /// This empty struct exists to allow overloading WriteValue based on generic constraints.
// But at the bytecode level, the constraints aren't included in the method signature. /// At the bytecode level, constraints aren't included in the method signature, so if multiple
// By adding a second parameter with a defaulted value, the signatures of each generic are different, /// methods exist with the same signature, it causes a compile error because they would end up
// thus allowing overloads of methods based on the first parameter meeting constraints. /// being emitted as the same method, even if the constraints are different.
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
/// which then allows the compiler to do overload resolution based on the generic constraints
/// without the user having to pass the struct in themselves.
/// </summary>
public struct ForPrimitives public struct ForPrimitives
{ {
} }
/// <summary>
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
/// methods exist with the same signature, it causes a compile error because they would end up
/// being emitted as the same method, even if the constraints are different.
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
/// which then allows the compiler to do overload resolution based on the generic constraints
/// without the user having to pass the struct in themselves.
/// </summary>
public struct ForEnums public struct ForEnums
{ {
} }
/// <summary>
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
/// methods exist with the same signature, it causes a compile error because they would end up
/// being emitted as the same method, even if the constraints are different.
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
/// which then allows the compiler to do overload resolution based on the generic constraints
/// without the user having to pass the struct in themselves.
/// </summary>
public struct ForStructs public struct ForStructs
{ {
} }
/// <summary>
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
/// methods exist with the same signature, it causes a compile error because they would end up
/// being emitted as the same method, even if the constraints are different.
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
/// which then allows the compiler to do overload resolution based on the generic constraints
/// without the user having to pass the struct in themselves.
/// </summary>
public struct ForNetworkSerializable public struct ForNetworkSerializable
{ {
} }
/// <summary>
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
/// methods exist with the same signature, it causes a compile error because they would end up
/// being emitted as the same method, even if the constraints are different.
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
/// which then allows the compiler to do overload resolution based on the generic constraints
/// without the user having to pass the struct in themselves.
/// </summary>
public struct ForFixedStrings public struct ForFixedStrings
{ {
} }
/// <summary>
/// Write a NetworkSerializable value
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
/// <summary>
/// Write a NetworkSerializable array
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
/// <summary>
/// Write a NetworkSerializable value
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
/// <summary>
/// Write a NetworkSerializable array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value); public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
/// <summary>
/// Write a struct
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value); public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
/// <summary>
/// Write a struct array
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value); public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
/// <summary>
/// Write a struct
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value); public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
/// <summary>
/// Write a struct array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value); public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
/// <summary>
/// Write a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value); public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
/// <summary>
/// Write a primitive value array (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value); public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
/// <summary>
/// Write a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value); public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
/// <summary>
/// Write a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value); public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
/// <summary>
/// Write an enum value
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value); public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
/// <summary>
/// Write an enum array
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value); public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
/// <summary>
/// Write an enum value
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value); public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
/// <summary>
/// Write an enum array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The values to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value); public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector2
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Vector2 value) => WriteUnmanaged(value); public void WriteValue(in Vector2 value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector2 array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Vector2[] value) => WriteUnmanaged(value); public void WriteValue(Vector2[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector3
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Vector3 value) => WriteUnmanaged(value); public void WriteValue(in Vector3 value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector3 array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Vector3[] value) => WriteUnmanaged(value); public void WriteValue(Vector3[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector2Int
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Vector2Int value) => WriteUnmanaged(value); public void WriteValue(in Vector2Int value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector2Int array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value); public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector3Int
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Vector3Int value) => WriteUnmanaged(value); public void WriteValue(in Vector3Int value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector3Int array
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value); public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector4
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Vector4 value) => WriteUnmanaged(value); public void WriteValue(in Vector4 value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector4
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Vector4[] value) => WriteUnmanaged(value); public void WriteValue(Vector4[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Quaternion
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Quaternion value) => WriteUnmanaged(value); public void WriteValue(in Quaternion value) => WriteUnmanaged(value);
/// <summary>
/// Write a Quaternion array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Quaternion[] value) => WriteUnmanaged(value); public void WriteValue(Quaternion[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Color
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Color value) => WriteUnmanaged(value); public void WriteValue(in Color value) => WriteUnmanaged(value);
/// <summary>
/// Write a Color array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Color[] value) => WriteUnmanaged(value); public void WriteValue(Color[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Color32
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Color32 value) => WriteUnmanaged(value); public void WriteValue(in Color32 value) => WriteUnmanaged(value);
/// <summary>
/// Write a Color32 array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Color32[] value) => WriteUnmanaged(value); public void WriteValue(Color32[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Ray
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Ray value) => WriteUnmanaged(value); public void WriteValue(in Ray value) => WriteUnmanaged(value);
/// <summary>
/// Write a Ray array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Ray[] value) => WriteUnmanaged(value); public void WriteValue(Ray[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Ray2D
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(in Ray2D value) => WriteUnmanaged(value); public void WriteValue(in Ray2D value) => WriteUnmanaged(value);
/// <summary>
/// Write a Ray2D array
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValue(Ray2D[] value) => WriteUnmanaged(value); public void WriteValue(Ray2D[] value) => WriteUnmanaged(value);
/// <summary>
/// Write a Vector2
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector2 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector3
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector3 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector2Int
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector2Int array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector3Int
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector3Int array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector4
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Vector4 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Quaternion
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Quaternion array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Color
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Collor array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Color32
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Color32 array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Ray
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Ray array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Ray2D
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value); public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value);
/// <summary>
/// Write a Ray2D array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the values to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value); public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value);
@@ -946,6 +1392,15 @@ namespace Unity.Netcode
// Those two are necessary to serialize FixedStrings efficiently // Those two are necessary to serialize FixedStrings efficiently
// - otherwise we'd just be memcpying the whole thing even if // - otherwise we'd just be memcpying the whole thing even if
// most of it isn't used. // most of it isn't used.
/// <summary>
/// Write a FixedString value. Writes only the part of the string that's actually used.
/// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling
/// FastBufferWriter.GetWriteSize())
/// </summary>
/// <param name="value">the value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteValue<T>(in T value, ForFixedStrings unused = default) public unsafe void WriteValue<T>(in T value, ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes where T : unmanaged, INativeList<byte>, IUTF8Bytes
@@ -960,6 +1415,16 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Write a FixedString value. Writes only the part of the string that's actually used.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">the value to write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueSafe<T>(in T value, ForFixedStrings unused = default) public void WriteValueSafe<T>(in T value, ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes where T : unmanaged, INativeList<byte>, IUTF8Bytes

View File

@@ -9,26 +9,58 @@ namespace Unity.Netcode
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public struct ForceNetworkSerializeByMemcpy<T> : INetworkSerializeByMemcpy, IEquatable<ForceNetworkSerializeByMemcpy<T>> where T : unmanaged, IEquatable<T> public struct ForceNetworkSerializeByMemcpy<T> : INetworkSerializeByMemcpy, IEquatable<ForceNetworkSerializeByMemcpy<T>> where T : unmanaged, IEquatable<T>
{ {
/// <summary>
/// The wrapped value
/// </summary>
public T Value; public T Value;
/// <summary>
/// The default constructor for <see cref="ForceNetworkSerializeByMemcpy{T}"/>
/// </summary>
/// <param name="value">sets the initial value of type `T`</param>
public ForceNetworkSerializeByMemcpy(T value) public ForceNetworkSerializeByMemcpy(T value)
{ {
Value = value; Value = value;
} }
/// <summary>
/// Convert implicitly from the ForceNetworkSerializeByMemcpy wrapper to the underlying value
/// </summary>
/// <param name="container">The wrapper</param>
/// <returns>The underlying value</returns>
public static implicit operator T(ForceNetworkSerializeByMemcpy<T> container) => container.Value; public static implicit operator T(ForceNetworkSerializeByMemcpy<T> container) => container.Value;
/// <summary>
/// Convert implicitly from a T value to a ForceNetworkSerializeByMemcpy wrapper
/// </summary>
/// <param name="underlyingValue">the value</param>
/// <returns>a new wrapper</returns>
public static implicit operator ForceNetworkSerializeByMemcpy<T>(T underlyingValue) => new ForceNetworkSerializeByMemcpy<T> { Value = underlyingValue }; public static implicit operator ForceNetworkSerializeByMemcpy<T>(T underlyingValue) => new ForceNetworkSerializeByMemcpy<T> { Value = underlyingValue };
/// <summary>
/// Check if wrapped values are equal
/// </summary>
/// <param name="other">Other wrapper</param>
/// <returns>true if equal</returns>
public bool Equals(ForceNetworkSerializeByMemcpy<T> other) public bool Equals(ForceNetworkSerializeByMemcpy<T> other)
{ {
return Value.Equals(other.Value); return Value.Equals(other.Value);
} }
/// <summary>
/// Check if this value is equal to a boxed object value
/// </summary>
/// <param name="obj">The boxed value to check against</param>
/// <returns>true if equal</returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is ForceNetworkSerializeByMemcpy<T> other && Equals(other); return obj is ForceNetworkSerializeByMemcpy<T> other && Equals(other);
} }
/// <summary>
/// Obtains the wrapped value's hash code
/// </summary>
/// <returns>Wrapped value's hash code</returns>
public override int GetHashCode() public override int GetHashCode()
{ {
return Value.GetHashCode(); return Value.GetHashCode();

View File

@@ -6,7 +6,7 @@ namespace Unity.Netcode
/// by memcpy. It's up to the developer of the struct to analyze the struct's contents and ensure it /// by memcpy. It's up to the developer of the struct to analyze the struct's contents and ensure it
/// is actually serializable by memcpy. This requires all of the members of the struct to be /// is actually serializable by memcpy. This requires all of the members of the struct to be
/// `unmanaged` Plain-Old-Data values - if your struct contains a pointer (or a type that contains a pointer, /// `unmanaged` Plain-Old-Data values - if your struct contains a pointer (or a type that contains a pointer,
/// like `NativeList<T>`), it should be serialized via `INetworkSerializable` or via /// like `NativeList&lt;T&gt;`), it should be serialized via `INetworkSerializable` or via
/// `FastBufferReader`/`FastBufferWriter` extension methods. /// `FastBufferReader`/`FastBufferWriter` extension methods.
/// </summary> /// </summary>
public interface INetworkSerializeByMemcpy public interface INetworkSerializeByMemcpy

View File

@@ -4,81 +4,536 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Interface for an implementation of one side of a two-way serializer
/// </summary>
public interface IReaderWriter public interface IReaderWriter
{ {
/// <summary>
/// Check whether this implementation is a "reader" - if it's been constructed to deserialize data
/// </summary>
bool IsReader { get; } bool IsReader { get; }
/// <summary>
/// Check whether this implementation is a "writer" - if it's been constructed to serialize data
/// </summary>
bool IsWriter { get; } bool IsWriter { get; }
/// <summary>
/// Get the underlying FastBufferReader struct.
/// Only valid when IsReader == true
/// </summary>
/// <returns>underlying FastBufferReader</returns>
FastBufferReader GetFastBufferReader(); FastBufferReader GetFastBufferReader();
/// <summary>
/// Get the underlying FastBufferWriter struct.
/// Only valid when IsWriter == true
/// </summary>
/// <returns>underlying FastBufferWriter</returns>
FastBufferWriter GetFastBufferWriter(); FastBufferWriter GetFastBufferWriter();
/// <summary>
/// Read or write a string
/// </summary>
/// <param name="s">The value to read/write</param>
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
void SerializeValue(ref string s, bool oneByteChars = false); void SerializeValue(ref string s, bool oneByteChars = false);
/// <summary>
/// Read or write a single byte
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref byte value); void SerializeValue(ref byte value);
/// <summary>
/// Read or write a primitive value (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>; void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
/// <summary>
/// Read or write an array of primitive values (int, bool, etc)
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
/// on values that are not primitives.
/// </summary>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>; void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
/// <summary>
/// Read or write an enum value
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum; void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
/// <summary>
/// Read or write an array of enum values
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum; void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
/// <summary>
/// Read or write a struct value implementing ISerializeByMemcpy
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy; void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
/// <summary>
/// Read or write an array of struct values implementing ISerializeByMemcpy
/// </summary>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy; void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
/// <summary>
/// Read or write a struct or class value implementing INetworkSerializable
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new(); void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
/// <summary>
/// Read or write an array of struct or class values implementing INetworkSerializable
/// </summary>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new(); void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
/// <summary>
/// Read or write a FixedString value
/// </summary>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
/// <typeparam name="T">The type being serialized</typeparam>
void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default) void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes; where T : unmanaged, INativeList<byte>, IUTF8Bytes;
/// <summary>
/// Read or write a Vector2 value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Vector2 value); void SerializeValue(ref Vector2 value);
/// <summary>
/// Read or write an array of Vector2 values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Vector2[] value); void SerializeValue(ref Vector2[] value);
/// <summary>
/// Read or write a Vector3 value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Vector3 value); void SerializeValue(ref Vector3 value);
/// <summary>
/// Read or write an array of Vector3 values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Vector3[] value); void SerializeValue(ref Vector3[] value);
/// <summary>
/// Read or write a Vector2Int value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Vector2Int value); void SerializeValue(ref Vector2Int value);
/// <summary>
/// Read or write an array of Vector2Int values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Vector2Int[] value); void SerializeValue(ref Vector2Int[] value);
/// <summary>
/// Read or write a Vector3Int value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Vector3Int value); void SerializeValue(ref Vector3Int value);
/// <summary>
/// Read or write an array of Vector3Int values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Vector3Int[] value); void SerializeValue(ref Vector3Int[] value);
/// <summary>
/// Read or write a Vector4 value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Vector4 value); void SerializeValue(ref Vector4 value);
/// <summary>
/// Read or write an array of Vector4 values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Vector4[] value); void SerializeValue(ref Vector4[] value);
/// <summary>
/// Read or write a Quaternion value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Quaternion value); void SerializeValue(ref Quaternion value);
/// <summary>
/// Read or write an array of Quaternion values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Quaternion[] value); void SerializeValue(ref Quaternion[] value);
/// <summary>
/// Read or write a Color value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Color value); void SerializeValue(ref Color value);
/// <summary>
/// Read or write an array of Color values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Color[] value); void SerializeValue(ref Color[] value);
/// <summary>
/// Read or write a Color32 value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Color32 value); void SerializeValue(ref Color32 value);
/// <summary>
/// Read or write an array of Color32 values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Color32[] value); void SerializeValue(ref Color32[] value);
/// <summary>
/// Read or write a Ray value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Ray value); void SerializeValue(ref Ray value);
/// <summary>
/// Read or write an array of Ray values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Ray[] value); void SerializeValue(ref Ray[] value);
/// <summary>
/// Read or write a Ray2D value
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValue(ref Ray2D value); void SerializeValue(ref Ray2D value);
/// <summary>
/// Read or write an array of Ray2D values
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValue(ref Ray2D[] value); void SerializeValue(ref Ray2D[] value);
// Has to have a different name to avoid conflicting with "where T: unmananged" /// <summary>
/// Read or write a NetworkSerializable value.
/// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only.
/// </summary>
/// <param name="value">The value to read/write</param>
/// <typeparam name="T">The network serializable type</typeparam>
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new(); void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
/// <summary>
/// Performs an advance check to ensure space is available to read/write one or more values.
/// This provides a performance benefit for serializing multiple values using the
/// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be
/// noticeable if serializing a very large number of items.
/// </summary>
/// <param name="amount"></param>
/// <returns></returns>
bool PreCheck(int amount); bool PreCheck(int amount);
/// <summary>
/// Serialize a string, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="s">The value to read/write</param>
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
void SerializeValuePreChecked(ref string s, bool oneByteChars = false); void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
/// <summary>
/// Serialize a byte, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref byte value); void SerializeValuePreChecked(ref byte value);
/// <summary>
/// Serialize a primitive, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>; void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
/// <summary>
/// Serialize an array of primitives, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>; void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
/// <summary>
/// Serialize an enum, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum; void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
/// <summary>
/// Serialize an array of enums, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum; void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
/// <summary>
/// Serialize a struct, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy; void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
/// <summary>
/// Serialize an array of structs, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The values to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy; void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
/// <summary>
/// Serialize a FixedString, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <typeparam name="T">The type being serialized</typeparam>
/// <param name="value">The value to read/write</param>
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default) void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
where T : unmanaged, INativeList<byte>, IUTF8Bytes; where T : unmanaged, INativeList<byte>, IUTF8Bytes;
/// <summary>
/// Serialize a Vector2, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector2 value); void SerializeValuePreChecked(ref Vector2 value);
/// <summary>
/// Serialize a Vector2 array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValuePreChecked(ref Vector2[] value); void SerializeValuePreChecked(ref Vector2[] value);
/// <summary>
/// Serialize a Vector3, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector3 value); void SerializeValuePreChecked(ref Vector3 value);
/// <summary>
/// Serialize a Vector3 array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValuePreChecked(ref Vector3[] value); void SerializeValuePreChecked(ref Vector3[] value);
/// <summary>
/// Serialize a Vector2Int, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector2Int value); void SerializeValuePreChecked(ref Vector2Int value);
/// <summary>
/// Serialize a Vector2Int array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The values to read/write</param>
void SerializeValuePreChecked(ref Vector2Int[] value); void SerializeValuePreChecked(ref Vector2Int[] value);
/// <summary>
/// Serialize a Vector3Int, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector3Int value); void SerializeValuePreChecked(ref Vector3Int value);
/// <summary>
/// Serialize a Vector3Int array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector3Int[] value); void SerializeValuePreChecked(ref Vector3Int[] value);
/// <summary>
/// Serialize a Vector4, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector4 value); void SerializeValuePreChecked(ref Vector4 value);
/// <summary>
/// Serialize a Vector4Array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Vector4[] value); void SerializeValuePreChecked(ref Vector4[] value);
/// <summary>
/// Serialize a Quaternion, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Quaternion value); void SerializeValuePreChecked(ref Quaternion value);
/// <summary>
/// Serialize a Quaternion array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Quaternion[] value); void SerializeValuePreChecked(ref Quaternion[] value);
/// <summary>
/// Serialize a Color, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Color value); void SerializeValuePreChecked(ref Color value);
/// <summary>
/// Serialize a Color array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Color[] value); void SerializeValuePreChecked(ref Color[] value);
/// <summary>
/// Serialize a Color32, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Color32 value); void SerializeValuePreChecked(ref Color32 value);
/// <summary>
/// Serialize a Color32 array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Color32[] value); void SerializeValuePreChecked(ref Color32[] value);
/// <summary>
/// Serialize a Ray, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Ray value); void SerializeValuePreChecked(ref Ray value);
/// <summary>
/// Serialize a Ray array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Ray[] value); void SerializeValuePreChecked(ref Ray[] value);
/// <summary>
/// Serialize a Ray2D, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Ray2D value); void SerializeValuePreChecked(ref Ray2D value);
/// <summary>
/// Serialize a Ray2D array, "pre-checked", which skips buffer checks.
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
/// </summary>
/// <param name="value">The value to read/write</param>
void SerializeValuePreChecked(ref Ray2D[] value); void SerializeValuePreChecked(ref Ray2D[] value);
} }
} }

View File

@@ -96,8 +96,18 @@ namespace Unity.Netcode
serializer.SerializeValue(ref m_NetworkBehaviourId); serializer.SerializeValue(ref m_NetworkBehaviourId);
} }
/// <summary>
/// Implicitly convert <see cref="NetworkBehaviourReference"/> to <see cref="NetworkBehaviour"/>.
/// </summary>
/// <param name="networkBehaviourRef">The <see cref="NetworkBehaviourReference"/> to convert from.</param>
/// <returns>The <see cref="NetworkBehaviour"/> this class is holding a reference to</returns>
public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef); public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef);
/// <summary>
/// Implicitly convert <see cref="NetworkBehaviour"/> to <see cref="NetworkBehaviourReference"/>.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> to convert from.</param>
/// <returns>The <see cref="NetworkBehaviourReference"/> created from the <see cref="NetworkBehaviour"/> passed in as a parameter</returns>
public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour); public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour);
} }
} }

View File

@@ -120,12 +120,41 @@ namespace Unity.Netcode
serializer.SerializeValue(ref m_NetworkObjectId); serializer.SerializeValue(ref m_NetworkObjectId);
} }
/// <summary>
/// Implicitly convert <see cref="NetworkObjectReference"/> to <see cref="NetworkObject"/>.
/// </summary>
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
/// <returns>The <see cref="NetworkObject"/> the <see cref="NetworkObjectReference"/> is referencing</returns>
public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef); public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef);
/// <summary>
/// Implicitly convert <see cref="NetworkObject"/> to <see cref="NetworkObjectReference"/>.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> to convert from.</param>
/// <returns>The <see cref="NetworkObjectReference"/> created from the <see cref="NetworkObject"/> parameter</returns>
public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject); public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject);
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject; /// <summary>
/// Implicitly convert <see cref="NetworkObjectReference"/> to <see cref="GameObject"/>.
/// </summary>
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
/// <returns>This returns the <see cref="GameObject"/> that the <see cref="NetworkObject"/> is attached to and is referenced by the <see cref="NetworkObjectReference"/> passed in as a parameter</returns>
public static implicit operator GameObject(NetworkObjectReference networkObjectRef)
{
var networkObject = Resolve(networkObjectRef);
if (networkObject != null)
{
return networkObject.gameObject;
}
return null;
}
/// <summary>
/// Implicitly convert <see cref="GameObject"/> to <see cref="NetworkObject"/>.
/// </summary>
/// <param name="gameObject">The <see cref="GameObject"/> to convert from.</param>
/// <returns>The <see cref="NetworkObjectReference"/> created from the <see cref="GameObject"/> parameter that has a <see cref="NetworkObject"/> component attached to it</returns>
public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject); public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject);
} }
} }

View File

@@ -119,8 +119,7 @@ namespace Unity.Netcode
// Now we register all // Now we register all
foreach (var gameObject in networkPrefabOverrides) foreach (var gameObject in networkPrefabOverrides)
{ {
var targetNetworkObject = gameObject.GetComponent<NetworkObject>(); if (gameObject.TryGetComponent<NetworkObject>(out var targetNetworkObject))
if (targetNetworkObject != null)
{ {
if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash)) if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash))
{ {

View File

@@ -126,6 +126,7 @@ namespace Unity.Netcode
/// Returns a list of all NetworkObjects that belong to a client. /// Returns a list of all NetworkObjects that belong to a client.
/// </summary> /// </summary>
/// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param> /// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param>
/// <returns>returns the list of <see cref="NetworkObject"/>s owned by the client</returns>
public List<NetworkObject> GetClientOwnedObjects(ulong clientId) public List<NetworkObject> GetClientOwnedObjects(ulong clientId)
{ {
if (!OwnershipToObjectsTable.ContainsKey(clientId)) if (!OwnershipToObjectsTable.ContainsKey(clientId))
@@ -172,9 +173,11 @@ namespace Unity.Netcode
return GetPlayerNetworkObject(NetworkManager.LocalClientId); return GetPlayerNetworkObject(NetworkManager.LocalClientId);
} }
/// <summary> /// <summary>
/// Returns the player object with a given clientId or null if one does not exist. This is only valid server side. /// Returns the player object with a given clientId or null if one does not exist. This is only valid server side.
/// </summary> /// </summary>
/// <param name="clientId">the client identifier of the player</param>
/// <returns>The player object with a given clientId or null if one does not exist</returns> /// <returns>The player object with a given clientId or null if one does not exist</returns>
public NetworkObject GetPlayerNetworkObject(ulong clientId) public NetworkObject GetPlayerNetworkObject(ulong clientId)
{ {
@@ -210,11 +213,11 @@ namespace Unity.Netcode
return; return;
} }
networkObject.OwnerClientId = NetworkManager.ServerClientId;
// Server removes the entry and takes over ownership before notifying // Server removes the entry and takes over ownership before notifying
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true); UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
networkObject.OwnerClientId = NetworkManager.ServerClientId;
var message = new ChangeOwnershipMessage var message = new ChangeOwnershipMessage
{ {
NetworkObjectId = networkObject.NetworkObjectId, NetworkObjectId = networkObject.NetworkObjectId,
@@ -267,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);
@@ -275,23 +281,26 @@ namespace Unity.Netcode
NetworkObjectId = networkObject.NetworkObjectId, NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId OwnerClientId = networkObject.OwnerClientId
}; };
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients) foreach (var client in NetworkManager.ConnectedClients)
{ {
if (networkObject.IsNetworkVisibleTo(client.Value.ClientId))
{
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId);
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
} }
} }
}
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.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Hash, out var networkPrefab))
{ {
switch (networkPrefab.Override) switch (networkPrefab.Override)
{ {
@@ -306,53 +315,38 @@ 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;
} }
/// <summary> /// <summary>
/// Should only run on the client /// Creates a local NetowrkObject to be spawned.
/// </summary> /// </summary>
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false) /// <remarks>
/// For most cases this is client-side only, with the exception of when the server
/// is spawning a player.
/// </remarks>
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
{ {
NetworkObject parentNetworkObject = null; NetworkObject networkObject = null;
var globalObjectIdHash = sceneObject.Hash;
var position = sceneObject.HasTransform ? sceneObject.Transform.Position : default;
var rotation = sceneObject.HasTransform ? sceneObject.Transform.Rotation : default;
var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default;
var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default;
var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays;
var isSpawnedByPrefabHandler = false;
if (parentNetworkId != null && !isReparented) // If scene management is disabled or the NetworkObject was dynamically spawned
{ if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject))
{
parentNetworkObject = networkObject;
}
else
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child");
}
}
}
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
{ {
// If 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
var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity)); networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation);
networkObject.NetworkManagerOwner = NetworkManager; networkObject.NetworkManagerOwner = NetworkManager;
isSpawnedByPrefabHandler = true;
if (parentNetworkObject != null)
{
networkObject.transform.SetParent(parentNetworkObject.transform, true);
}
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
return networkObject;
} }
else else
{ {
@@ -380,31 +374,18 @@ namespace Unity.Netcode
{ {
NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?"); NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?");
} }
return null;
}
// Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash
var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent<NetworkObject>();
networkObject.NetworkManagerOwner = NetworkManager;
if (parentNetworkObject != null)
{
networkObject.transform.SetParent(parentNetworkObject.transform, true);
}
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
return networkObject;
}
} }
else else
{ {
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle); // Create prefab instance
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
networkObject.NetworkManagerOwner = NetworkManager;
}
}
}
else // Get the in-scene placed NetworkObject
{
networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, sceneObject.NetworkSceneHandle);
if (networkObject == null) if (networkObject == null)
{ {
@@ -412,19 +393,91 @@ namespace Unity.Netcode
{ {
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!"); NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
} }
return null;
} }
if (parentNetworkObject != null) // Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
// NetworkBehaviours will have their OnNetworkSpawn method invoked
if (networkObject != null && !networkObject.gameObject.activeInHierarchy)
{ {
networkObject.transform.SetParent(parentNetworkObject.transform, true); networkObject.gameObject.SetActive(true);
}
} }
return networkObject; if (networkObject != null)
{
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
// This is a special case scenario where a late joining client has joined and loaded one or
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
// synchronization information does not indicate the NetworkObject in question has a parent.
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null)
{
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
// include parenting, then we need to force the removal of that parent
if (networkObject.transform.parent.GetComponent<NetworkObject>() != null)
{
// remove the parent
networkObject.ApplyNetworkParenting(true, true);
} }
} }
// Set the transform unless we were spawned by a prefab handler
// Note: prefab handlers are provided the position and rotation
// but it is up to the user to set those values
if (sceneObject.HasTransform && !isSpawnedByPrefabHandler)
{
// If world position stays is true or we have auto object parent synchronization disabled
// then we want to apply the position and rotation values world space relative
if (worldPositionStays || !networkObject.AutoObjectParentSync)
{
networkObject.transform.position = position;
networkObject.transform.rotation = rotation;
}
else
{
networkObject.transform.localPosition = position;
networkObject.transform.localRotation = rotation;
}
// SPECIAL CASE:
// Since players are created uniquely we don't apply scale because
// the ConnectionApprovalResponse does not currently provide the
// ability to specify scale. So, we just use the default scale of
// the network prefab used to represent the player.
// Note: not doing this would set the player's scale to zero since
// that is the default value of Vector3.
if (!sceneObject.IsPlayerObject)
{
// Since scale is always applied to local space scale, we do the transform
// space logic during serialization such that it works out whether AutoObjectParentSync
// is enabled or not (see NetworkObject.SceneObject)
networkObject.transform.localScale = scale;
}
}
if (sceneObject.HasParent)
{
// Go ahead and set network parenting properties, if the latest parent is not set then pass in null
// (we always want to set worldPositionStays)
ulong? parentId = null;
if (sceneObject.IsLatestParentSet)
{
parentId = parentNetworkId;
}
networkObject.SetNetworkParenting(parentId, worldPositionStays);
}
// Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL
// until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
return networkObject;
}
// Ran on both server and client // Ran on both server and client
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
{ {
@@ -451,8 +504,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)
{ {
@@ -464,9 +516,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)
@@ -542,7 +592,6 @@ namespace Unity.Netcode
} }
} }
networkObject.SetCachedParent(networkObject.transform.parent);
networkObject.ApplyNetworkParenting(); networkObject.ApplyNetworkParenting();
NetworkObject.CheckOrphanChildren(); NetworkObject.CheckOrphanChildren();
@@ -572,8 +621,6 @@ namespace Unity.Netcode
}; };
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
} }
internal ulong? GetSpawnParentId(NetworkObject networkObject) internal ulong? GetSpawnParentId(NetworkObject networkObject)
@@ -611,7 +658,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;
@@ -642,7 +693,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++)
{ {
@@ -672,7 +727,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++)
{ {
@@ -699,7 +758,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++)
@@ -742,15 +805,26 @@ namespace Unity.Netcode
} }
// If we are shutting down the NetworkManager, then ignore resetting the parent // If we are shutting down the NetworkManager, then ignore resetting the parent
if (!NetworkManager.ShutdownInProgress) // and only attempt to remove the child's parent on the server-side
if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer)
{ {
// Move child NetworkObjects to the root when parent NetworkObject is destroyed // Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList) foreach (var spawnedNetObj in SpawnedObjectsList)
{ {
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting(); var latestParent = spawnedNetObj.GetNetworkParenting();
if (isReparented && latestParent == networkObject.NetworkObjectId) if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId)
{ {
spawnedNetObj.gameObject.transform.parent = null; // Try to remove the parent using the cached WorldPositioNStays value
// Note: WorldPositionStays will still default to true if this was an
// in-scene placed NetworkObject and parenting was predefined in the
// scene via the editor.
if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays())
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed");
}
}
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {

View File

@@ -3,6 +3,10 @@ using Unity.Profiling;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// Provides discretized time.
/// This is useful for games that require ticks happening at regular interval on the server and clients.
/// </summary>
public class NetworkTickSystem public class NetworkTickSystem
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -69,6 +73,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Called after advancing the time system to run ticks based on the difference in time. /// Called after advancing the time system to run ticks based on the difference in time.
/// </summary> /// </summary>
/// <param name="localTimeSec">The local time in seconds</param>
/// <param name="serverTimeSec">The server time in seconds</param>
public void UpdateTick(double localTimeSec, double serverTimeSec) public void UpdateTick(double localTimeSec, double serverTimeSec)
{ {
// store old local tick to know how many fixed ticks passed // store old local tick to know how many fixed ticks passed

View File

@@ -25,7 +25,7 @@ namespace Unity.Netcode
public double TickOffset => m_CachedTickOffset; public double TickOffset => m_CachedTickOffset;
/// <summary> /// <summary>
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/> /// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>.
/// </summary> /// </summary>
public double Time => m_TimeSec; public double Time => m_TimeSec;
@@ -35,13 +35,13 @@ namespace Unity.Netcode
public float TimeAsFloat => (float)m_TimeSec; public float TimeAsFloat => (float)m_TimeSec;
/// <summary> /// <summary>
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedTime"/> /// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedUnscaledTime"/>.
/// </summary> /// </summary>
public double FixedTime => m_CachedTick * m_TickInterval; public double FixedTime => m_CachedTick * m_TickInterval;
/// <summary> /// <summary>
/// Gets the fixed delta time. This value is based on the <see cref="TickRate"/> and stays constant. /// Gets the fixed delta time. This value is based on the <see cref="TickRate"/> and stays constant.
/// Similar to <see cref="Time.fixedDeltaTime"/> There is no equivalent to <see cref="Time.deltaTime"/> /// Similar to <see cref="Time.fixedUnscaledTime"/> There is no equivalent to <see cref="Time.deltaTime"/>.
/// </summary> /// </summary>
public float FixedDeltaTime => (float)m_TickInterval; public float FixedDeltaTime => (float)m_TickInterval;
@@ -108,6 +108,11 @@ namespace Unity.Netcode
return new NetworkTime(m_TickRate, m_CachedTick); return new NetworkTime(m_TickRate, m_CachedTick);
} }
/// <summary>
/// Returns the time a number of ticks in the past.
/// </summary>
/// <param name="ticks">The number of ticks ago we're querying the time</param>
/// <returns></returns>
public NetworkTime TimeTicksAgo(int ticks) public NetworkTime TimeTicksAgo(int ticks)
{ {
return this - new NetworkTime(TickRate, ticks); return this - new NetworkTime(TickRate, ticks);
@@ -132,16 +137,34 @@ namespace Unity.Netcode
} }
} }
/// <summary>
/// Computes the time difference between two ticks
/// </summary>
/// <param name="a">End time</param>
/// <param name="b">Start time</param>
/// <returns>The time difference between start and end</returns>
public static NetworkTime operator -(NetworkTime a, NetworkTime b) public static NetworkTime operator -(NetworkTime a, NetworkTime b)
{ {
return new NetworkTime(a.TickRate, a.Time - b.Time); return new NetworkTime(a.TickRate, a.Time - b.Time);
} }
/// <summary>
/// Computes the sum of two times
/// </summary>
/// <param name="a">First time</param>
/// <param name="b">Second time</param>
/// <returns>The sum of the two times passed in</returns>
public static NetworkTime operator +(NetworkTime a, NetworkTime b) public static NetworkTime operator +(NetworkTime a, NetworkTime b)
{ {
return new NetworkTime(a.TickRate, a.Time + b.Time); return new NetworkTime(a.TickRate, a.Time + b.Time);
} }
/// <summary>
/// Computes the time a number of seconds later
/// </summary>
/// <param name="a">The start time</param>
/// <param name="b">The number of seconds to add</param>
/// <returns>The resulting time</returns>
public static NetworkTime operator +(NetworkTime a, double b) public static NetworkTime operator +(NetworkTime a, double b)
{ {
a.m_TimeSec += b; a.m_TimeSec += b;
@@ -149,6 +172,12 @@ namespace Unity.Netcode
return a; return a;
} }
/// <summary>
/// Computes the time a number of seconds before
/// </summary>
/// <param name="a">The start time</param>
/// <param name="b">The number of seconds to remove</param>
/// <returns>The resulting time</returns>
public static NetworkTime operator -(NetworkTime a, double b) public static NetworkTime operator -(NetworkTime a, double b)
{ {
return a + -b; return a + -b;

View File

@@ -36,12 +36,27 @@ namespace Unity.Netcode
/// Gets or sets the ratio at which the NetworkTimeSystem speeds up or slows down time. /// Gets or sets the ratio at which the NetworkTimeSystem speeds up or slows down time.
/// </summary> /// </summary>
public double AdjustmentRatio { get; set; } public double AdjustmentRatio { get; set; }
/// <summary>
/// The current local time with the local time offset applied
/// </summary>
public double LocalTime => m_TimeSec + m_CurrentLocalTimeOffset; public double LocalTime => m_TimeSec + m_CurrentLocalTimeOffset;
/// <summary>
/// The current server time with the server time offset applied
/// </summary>
public double ServerTime => m_TimeSec + m_CurrentServerTimeOffset; public double ServerTime => m_TimeSec + m_CurrentServerTimeOffset;
internal double LastSyncedServerTimeSec { get; private set; } internal double LastSyncedServerTimeSec { get; private set; }
internal double LastSyncedRttSec { get; private set; } internal double LastSyncedRttSec { get; private set; }
/// <summary>
/// The constructor class for <see cref="NetworkTickSystem"/>
/// </summary>
/// <param name="localBufferSec">The amount of time, in seconds, the server should buffer incoming client messages.</param>
/// <param name="serverBufferSec">The amount of the time in seconds the client should buffer incoming messages from the server.</param>
/// <param name="hardResetThresholdSec">The threshold, in seconds, used to force a hard catchup of network time.</param>
/// <param name="adjustmentRatio">The ratio at which the NetworkTimeSystem speeds up or slows down time.</param>
public NetworkTimeSystem(double localBufferSec, double serverBufferSec, double hardResetThresholdSec, double adjustmentRatio = 0.01d) public NetworkTimeSystem(double localBufferSec, double serverBufferSec, double hardResetThresholdSec, double adjustmentRatio = 0.01d)
{ {
LocalBufferSec = localBufferSec; LocalBufferSec = localBufferSec;
@@ -61,7 +76,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.deltaTime or similar. /// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar.
/// </summary> /// </summary>
/// <param name="deltaTimeSec">The amount of time to advance. The delta time which passed since Advance was last called.</param> /// <param name="deltaTimeSec">The amount of time to advance. The delta time which passed since Advance was last called.</param>
/// <returns></returns> /// <returns></returns>

View File

@@ -3,6 +3,11 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
/// <summary>
/// The generic transport class all Netcode for GameObjects network transport implementations
/// derive from. Use this class to add a custom transport.
/// <seealso cref="Transports.UTP.UnityTransport"> for an example of how a transport is integrated</seealso>
/// </summary>
public abstract class NetworkTransport : MonoBehaviour public abstract class NetworkTransport : MonoBehaviour
{ {
/// <summary> /// <summary>
@@ -45,7 +50,7 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Send a payload to the specified clientId, data and channelName. /// Send a payload to the specified clientId, data and networkDelivery.
/// </summary> /// </summary>
/// <param name="clientId">The clientId to send to</param> /// <param name="clientId">The clientId to send to</param>
/// <param name="payload">The data to send</param> /// <param name="payload">The data to send</param>
@@ -64,11 +69,13 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Connects client to the server /// Connects client to the server
/// </summary> /// </summary>
/// <returns>Returns success or failure</returns>
public abstract bool StartClient(); public abstract bool StartClient();
/// <summary> /// <summary>
/// Starts to listening for incoming clients /// Starts to listening for incoming clients
/// </summary> /// </summary>
/// <returns>Returns success or failure</returns>
public abstract bool StartServer(); public abstract bool StartServer();
/// <summary> /// <summary>

View File

@@ -7,6 +7,7 @@ using UnityEngine.Networking;
namespace Unity.Netcode.Transports.UNET namespace Unity.Netcode.Transports.UNET
{ {
[AddComponentMenu("Netcode/UNet Transport")]
public class UNetTransport : NetworkTransport public class UNetTransport : NetworkTransport
{ {
public enum SendMode public enum SendMode

View File

@@ -1,5 +1,9 @@
using System; using System;
using Unity.Networking.Transport; using Unity.Networking.Transport;
#if UTP_TRANSPORT_2_0_ABOVE
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
#endif
namespace Unity.Netcode.Transports.UTP namespace Unity.Netcode.Transports.UTP
{ {
@@ -25,7 +29,11 @@ namespace Unity.Netcode.Transports.UTP
{ {
fixed (byte* dataPtr = m_Data) fixed (byte* dataPtr = m_Data)
{ {
#if UTP_TRANSPORT_2_0_ABOVE
reader.ReadBytesUnsafe(dataPtr, reader.Length);
#else
reader.ReadBytes(dataPtr, reader.Length); reader.ReadBytes(dataPtr, reader.Length);
#endif
} }
} }
@@ -62,7 +70,11 @@ namespace Unity.Netcode.Transports.UTP
{ {
fixed (byte* dataPtr = m_Data) fixed (byte* dataPtr = m_Data)
{ {
#if UTP_TRANSPORT_2_0_ABOVE
reader.ReadBytesUnsafe(dataPtr + m_Offset + m_Length, reader.Length);
#else
reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length); reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length);
#endif
} }
} }

View File

@@ -8,22 +8,30 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>Queue for batched messages meant to be sent through UTP.</summary> /// <summary>Queue for batched messages meant to be sent through UTP.</summary>
/// <remarks> /// <remarks>
/// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched /// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched
/// messages, call <see cref="FillWriter"> with the <see cref="DataStreamWriter"/> obtained from /// messages, call <see cref="FillWriterWithMessages"/> or <see cref="FillWriterWithBytes"/>
/// <see cref="NetworkDriver.BeginSend"/>. This will fill the writer with as many messages as /// with the <see cref="DataStreamWriter"/> obtained from <see cref="NetworkDriver.BeginSend"/>.
/// possible. If the send is successful, call <see cref="Consume"/> to remove the data from the /// This will fill the writer with as many messages/bytes as possible. If the send is
/// queue. /// successful, call <see cref="Consume"/> to remove the data from the queue.
/// ///
/// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to /// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to
/// read messages sent with this queue. /// read messages sent with this queue.
/// </remarks> /// </remarks>
internal struct BatchedSendQueue : IDisposable internal struct BatchedSendQueue : IDisposable
{ {
private NativeArray<byte> m_Data; // Note that we're using NativeList basically like a growable NativeArray, where the length
// of the list is the capacity of our array. (We can't use the capacity of the list as our
// queue capacity because NativeList may elect to set it higher than what we'd set it to
// with SetCapacity, which breaks the logic of our code.)
private NativeList<byte> m_Data;
private NativeArray<int> m_HeadTailIndices; private NativeArray<int> m_HeadTailIndices;
private int m_MaximumCapacity;
private int m_MinimumCapacity;
/// <summary>Overhead that is added to each message in the queue.</summary> /// <summary>Overhead that is added to each message in the queue.</summary>
public const int PerMessageOverhead = sizeof(int); public const int PerMessageOverhead = sizeof(int);
internal const int MinimumMinimumCapacity = 4096;
// Indices into m_HeadTailIndicies. // Indices into m_HeadTailIndicies.
private const int k_HeadInternalIndex = 0; private const int k_HeadInternalIndex = 0;
private const int k_TailInternalIndex = 1; private const int k_TailInternalIndex = 1;
@@ -43,18 +51,33 @@ namespace Unity.Netcode.Transports.UTP
} }
public int Length => TailIndex - HeadIndex; public int Length => TailIndex - HeadIndex;
public int Capacity => m_Data.Length;
public bool IsEmpty => HeadIndex == TailIndex; public bool IsEmpty => HeadIndex == TailIndex;
public bool IsCreated => m_Data.IsCreated; public bool IsCreated => m_Data.IsCreated;
/// <summary>Construct a new empty send queue.</summary> /// <summary>Construct a new empty send queue.</summary>
/// <param name="capacity">Maximum capacity of the send queue.</param> /// <param name="capacity">Maximum capacity of the send queue.</param>
public BatchedSendQueue(int capacity) public BatchedSendQueue(int capacity)
{ {
m_Data = new NativeArray<byte>(capacity, Allocator.Persistent); // Make sure the maximum capacity will be even.
m_MaximumCapacity = capacity + (capacity & 1);
// We pick the minimum capacity such that if we keep doubling it, we'll eventually hit
// the maximum capacity exactly. The alternative would be to use capacities that are
// powers of 2, but this can lead to over-allocating quite a bit of memory (especially
// since we expect maximum capacities to be in the megabytes range). The approach taken
// here avoids this issue, at the cost of not having allocations of nice round sizes.
m_MinimumCapacity = m_MaximumCapacity;
while (m_MinimumCapacity / 2 >= MinimumMinimumCapacity)
{
m_MinimumCapacity /= 2;
}
m_Data = new NativeList<byte>(m_MinimumCapacity, Allocator.Persistent);
m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent); m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent);
m_Data.ResizeUninitialized(m_MinimumCapacity);
HeadIndex = 0; HeadIndex = 0;
TailIndex = 0; TailIndex = 0;
} }
@@ -68,18 +91,28 @@ namespace Unity.Netcode.Transports.UTP
} }
} }
/// <summary>Write a raw buffer to a DataStreamWriter.</summary>
private unsafe void WriteBytes(ref DataStreamWriter writer, byte* data, int length)
{
#if UTP_TRANSPORT_2_0_ABOVE
writer.WriteBytesUnsafe(data, length);
#else
writer.WriteBytes(data, length);
#endif
}
/// <summary>Append data at the tail of the queue. No safety checks.</summary> /// <summary>Append data at the tail of the queue. No safety checks.</summary>
private void AppendDataAtTail(ArraySegment<byte> data) private void AppendDataAtTail(ArraySegment<byte> data)
{ {
unsafe unsafe
{ {
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex); var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex);
writer.WriteInt(data.Count); writer.WriteInt(data.Count);
fixed (byte* dataPtr = data.Array) fixed (byte* dataPtr = data.Array)
{ {
writer.WriteBytes(dataPtr + data.Offset, data.Count); WriteBytes(ref writer, dataPtr + data.Offset, data.Count);
} }
} }
@@ -100,16 +133,16 @@ namespace Unity.Netcode.Transports.UTP
} }
// Check if there's enough room after the current tail index. // Check if there's enough room after the current tail index.
if (m_Data.Length - TailIndex >= sizeof(int) + message.Count) if (Capacity - TailIndex >= sizeof(int) + message.Count)
{ {
AppendDataAtTail(message); AppendDataAtTail(message);
return true; return true;
} }
// Check if there would be enough room if we moved data at the beginning of m_Data. // Move the data at the beginning of of m_Data. Either it will leave enough space for
if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count) // the message, or we'll grow m_Data and will want the data at the beginning anyway.
if (HeadIndex > 0 && Length > 0)
{ {
// Move the data back at the beginning of m_Data.
unsafe unsafe
{ {
UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
@@ -117,14 +150,40 @@ namespace Unity.Netcode.Transports.UTP
TailIndex = Length; TailIndex = Length;
HeadIndex = 0; HeadIndex = 0;
}
// If there's enough space left at the end for the message, now is a good time to trim
// the capacity of m_Data if it got very large. We define "very large" here as having
// more than 75% of m_Data unused after adding the new message.
if (Capacity - TailIndex >= sizeof(int) + message.Count)
{
AppendDataAtTail(message); AppendDataAtTail(message);
while (TailIndex < Capacity / 4 && Capacity > m_MinimumCapacity)
{
m_Data.ResizeUninitialized(Capacity / 2);
}
return true; return true;
} }
// If we get here we need to grow m_Data until the data fits (or it's too large).
while (Capacity - TailIndex < sizeof(int) + message.Count)
{
// Can't grow m_Data anymore. Message simply won't fit.
if (Capacity * 2 > m_MaximumCapacity)
{
return false; return false;
} }
m_Data.ResizeUninitialized(Capacity * 2);
}
// If we get here we know there's now enough room for the message.
AppendDataAtTail(message);
return true;
}
/// <summary> /// <summary>
/// Fill as much of a <see cref="DataStreamWriter"/> as possible with data from the head of /// Fill as much of a <see cref="DataStreamWriter"/> as possible with data from the head of
/// the queue. Only full messages (and their length) are written to the writer. /// the queue. Only full messages (and their length) are written to the writer.
@@ -149,12 +208,12 @@ namespace Unity.Netcode.Transports.UTP
unsafe unsafe
{ {
var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length); var reader = new DataStreamReader(m_Data.AsArray());
var writerAvailable = writer.Capacity; var writerAvailable = writer.Capacity;
var readerOffset = 0; var readerOffset = HeadIndex;
while (readerOffset < Length) while (readerOffset < TailIndex)
{ {
reader.SeekSet(readerOffset); reader.SeekSet(readerOffset);
var messageLength = reader.ReadInt(); var messageLength = reader.ReadInt();
@@ -168,7 +227,7 @@ namespace Unity.Netcode.Transports.UTP
writer.WriteInt(messageLength); writer.WriteInt(messageLength);
var messageOffset = HeadIndex + reader.GetBytesRead(); var messageOffset = HeadIndex + reader.GetBytesRead();
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength); WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
writerAvailable -= sizeof(int) + messageLength; writerAvailable -= sizeof(int) + messageLength;
readerOffset += sizeof(int) + messageLength; readerOffset += sizeof(int) + messageLength;
@@ -205,7 +264,7 @@ namespace Unity.Netcode.Transports.UTP
unsafe unsafe
{ {
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength); WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
} }
return copyLength; return copyLength;
@@ -219,10 +278,14 @@ namespace Unity.Netcode.Transports.UTP
/// <param name="size">Number of bytes to consume from the queue.</param> /// <param name="size">Number of bytes to consume from the queue.</param>
public void Consume(int size) public void Consume(int size)
{ {
// Adjust the head/tail indices such that we consume the given size.
if (size >= Length) if (size >= Length)
{ {
HeadIndex = 0; HeadIndex = 0;
TailIndex = 0; TailIndex = 0;
// This is a no-op if m_Data is already at minimum capacity.
m_Data.ResizeUninitialized(m_MinimumCapacity);
} }
else else
{ {

View File

@@ -1,8 +1,17 @@
namespace Unity.Netcode.Transports.UTP namespace Unity.Netcode.Transports.UTP
{ {
/// <summary>
/// Caching structure to track network metrics related information.
/// </summary>
public struct NetworkMetricsContext public struct NetworkMetricsContext
{ {
/// <summary>
/// The number of packet sent.
/// </summary>
public uint PacketSentCount; public uint PacketSentCount;
/// <summary>
/// The number of packet received.
/// </summary>
public uint PacketReceivedCount; public uint PacketReceivedCount;
} }
} }

View File

@@ -4,25 +4,24 @@ using AOT;
using Unity.Burst; using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using Unity.Networking.Transport; using Unity.Networking.Transport;
using UnityEngine;
namespace Unity.Netcode.Transports.UTP namespace Unity.Netcode.Transports.UTP
{ {
[BurstCompile] [BurstCompile]
internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage
{ {
static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive); private static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> s_ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send); private static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> s_SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection); private static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> s_InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer, public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer,
int staticInstanceBufferLength, int staticInstanceBufferLength,
NetworkSettings settings) NetworkSettings settings)
{ {
return new NetworkPipelineStage( return new NetworkPipelineStage(
ReceiveFunction, s_ReceiveFunction,
SendFunction, s_SendFunction,
InitializeConnectionFunction, s_InitializeConnectionFunction,
ReceiveCapacity: 0, ReceiveCapacity: 0,
SendCapacity: 0, SendCapacity: 0,
HeaderCapacity: 0, HeaderCapacity: 0,

View File

@@ -0,0 +1,188 @@
using System;
using System.IO;
using UnityEngine;
namespace Unity.Netcode.Transports.UTP
{
/// <summary>
/// Component to add to a NetworkManager if you want the certificates to be loaded from files.
/// Mostly helpful to ease development and testing, especially with self-signed certificates
///
/// Shipping code should make the calls to
/// - SetServerSecrets
/// - SetClientSecrets
/// directly, instead of relying on this.
/// </summary>
public class SecretsLoaderHelper : MonoBehaviour
{
internal struct ServerSecrets
{
public string ServerPrivate;
public string ServerCertificate;
};
internal struct ClientSecrets
{
public string ServerCommonName;
public string ClientCertificate;
};
private void Awake()
{
var serverSecrets = new ServerSecrets();
try
{
serverSecrets.ServerCertificate = ServerCertificate;
}
catch (Exception exception)
{
Debug.Log(exception);
}
try
{
serverSecrets.ServerPrivate = ServerPrivate;
}
catch (Exception exception)
{
Debug.Log(exception);
}
var clientSecrets = new ClientSecrets();
try
{
clientSecrets.ClientCertificate = ClientCA;
}
catch (Exception exception)
{
Debug.Log(exception);
}
try
{
clientSecrets.ServerCommonName = ServerCommonName;
}
catch (Exception exception)
{
Debug.Log(exception);
}
var unityTransportComponent = GetComponent<UnityTransport>();
if (unityTransportComponent == null)
{
Debug.LogError($"You need to select the UnityTransport protocol, in the NetworkManager, in order for the SecretsLoaderHelper component to be useful.");
return;
}
unityTransportComponent.SetServerSecrets(serverSecrets.ServerCertificate, serverSecrets.ServerPrivate);
unityTransportComponent.SetClientSecrets(clientSecrets.ServerCommonName, clientSecrets.ClientCertificate);
}
[Tooltip("Hostname")]
[SerializeField]
private string m_ServerCommonName = "localhost";
/// <summary>Common name of the server (typically its hostname).</summary>
public string ServerCommonName
{
get => m_ServerCommonName;
set => m_ServerCommonName = value;
}
[Tooltip("Client CA filepath. Useful with self-signed certificates")]
[SerializeField]
private string m_ClientCAFilePath = ""; // "Assets/Secure/myGameClientCA.pem"
/// <summary>Client CA filepath. Useful with self-signed certificates</summary>
public string ClientCAFilePath
{
get => m_ClientCAFilePath;
set => m_ClientCAFilePath = value;
}
[Tooltip("Client CA Override. Only useful for development with self-signed certificates. Certificate content, for platforms that lack file access (WebGL)")]
[SerializeField]
private string m_ClientCAOverride = "";
/// <summary>
/// Client CA Override. Only useful for development with self-signed certificates.
/// Certificate content, for platforms that lack file access (WebGL)
/// </summary>
public string ClientCAOverride
{
get => m_ClientCAOverride;
set => m_ClientCAOverride = value;
}
[Tooltip("Server Certificate filepath")]
[SerializeField]
private string m_ServerCertificateFilePath = ""; // "Assets/Secure/myGameServerCertificate.pem"
/// <summary>Server Certificate filepath</summary>
public string ServerCertificateFilePath
{
get => m_ServerCertificateFilePath;
set => m_ServerCertificateFilePath = value;
}
[Tooltip("Server Private Key filepath")]
[SerializeField]
private string m_ServerPrivateFilePath = ""; // "Assets/Secure/myGameServerPrivate.pem"
/// <summary>Server Private Key filepath</summary>
public string ServerPrivateFilePath
{
get => m_ServerPrivateFilePath;
set => m_ServerPrivate = value;
}
private string m_ClientCA;
/// <summary>CA certificate used by the client.</summary>
public string ClientCA
{
get
{
if (m_ClientCAOverride != "")
{
return m_ClientCAOverride;
}
return ReadFile(m_ClientCAFilePath, "Client Certificate");
}
set => m_ClientCA = value;
}
private string m_ServerCertificate;
/// <summary>Certificate used by the server.</summary>
public string ServerCertificate
{
get => ReadFile(m_ServerCertificateFilePath, "Server Certificate");
set => m_ServerCertificate = value;
}
private string m_ServerPrivate;
/// <summary>Private key used by the server.</summary>
public string ServerPrivate
{
get => ReadFile(m_ServerPrivateFilePath, "Server Key");
set => m_ServerPrivate = value;
}
private static string ReadFile(string path, string label)
{
if (path == null || path == "")
{
return "";
}
var reader = new StreamReader(path);
string fileContent = reader.ReadToEnd();
Debug.Log((fileContent.Length > 1) ? ("Successfully loaded " + fileContent.Length + " byte(s) from " + label) : ("Could not read " + label + " file"));
return fileContent;
}
}
}

View File

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

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