9 Commits

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

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

## [1.4.0] - 2023-04-10

### Added

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

### Changed

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

### Fixed

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

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

## [1.3.1] - 2023-03-27

### Added

- Added detection and graceful handling of corrupt packets for additional safety. (#2419)

### Changed

- The UTP component UI has been updated to be more user-friendly for new users by adding a simple toggle to switch between local-only (127.0.0.1) and remote (0.0.0.0) binding modes, using the toggle "Allow Remote Connections" (#2408)
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.3. (#2450)
- `NetworkShow()` of `NetworkObject`s are delayed until the end of the frame to ensure consistency of delta-driven variables like `NetworkList`.
- Dirty `NetworkObject` are reset at end-of-frame and not at serialization time.
- `NetworkHide()` of an object that was just `NetworkShow()`n produces a warning, as remote clients will _not_ get a spawn/despawn pair.
- Renamed the NetworkTransform.SetState parameter `shouldGhostsInterpolate` to `teleportDisabled` for better clarity of what that parameter does. (#2228)
- Network prefabs are now stored in a ScriptableObject that can be shared between NetworkManagers, and have been exposed for public access. By default, a Default Prefabs List is created that contains all NetworkObject prefabs in the project, and new NetworkManagers will default to using that unless that option is turned off in the Netcode for GameObjects settings. Existing NetworkManagers will maintain their existing lists, which can be migrated to the new format via a button in their inspector. (#2322)

### Fixed

- Fixed issue where changes to a layer's weight would not synchronize unless a state transition was occurring.(#2399)
- Fixed issue where `NetworkManager.LocalClientId` was returning the `NetworkTransport.ServerClientId` as opposed to the `NetworkManager.m_LocalClientId`. (#2398)
- Fixed issue where a dynamically spawned `NetworkObject` parented under an in-scene placed `NetworkObject` would have its `InScenePlaced` value changed to `true`. This would result in a soft synchronization error for late joining clients. (#2396)
- Fixed a UTP test that was failing when you install Unity Transport package 2.0.0 or newer. (#2347)
- Fixed issue where `NetcodeSettingsProvider` would throw an exception in Unity 2020.3.x versions. (#2345)
- Fixed server side issue where, depending upon component ordering, some NetworkBehaviour components might not have their OnNetworkDespawn method invoked if the client side disconnected. (#2323)
- Fixed a case where data corruption could occur when using UnityTransport when reaching a certain level of send throughput. (#2332)
- Fixed an issue in `UnityTransport` where an exception would be thrown if starting a Relay host/server on WebGL. This exception should only be thrown if using direct connections (where WebGL can't act as a host/server). (#2321)
- Fixed `NetworkAnimator` issue where it was not checking for `AnimatorStateTtansition.destinationStateMachine` and any possible sub-states defined within it. (#2309)
- Fixed `NetworkAnimator` issue where the host client was receiving the ClientRpc animation updates when the host was the owner.(#2309)
- Fixed `NetworkAnimator` issue with using pooled objects and when specific properties are cleaned during despawn and destroy.(#2309)
- Fixed issue where `NetworkAnimator` was checking for animation changes when the associated `NetworkObject` was not spawned.(#2309)
- Corrected an issue with the documentation for BufferSerializer (#2401)
2023-03-27 00:00:00 +00:00
Unity Technologies
fe02ca682e com.unity.netcode.gameobjects@1.2.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.2.0] - 2022-11-21

### Added

- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298)
- Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290)
- Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285)
- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280)

### Changed

- Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310)
- When `NetworkConfig.EnsureNetworkVariableLengthSafety` is disabled `NetworkVariable` fields do not write the additional `ushort` size value (_which helps to reduce the total synchronization message size_), but when enabled it still writes the additional `ushort` value. (#2298)
- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276)

### Fixed

- Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component  (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298)
- Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298)
- Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298)
- Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296)
- Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292)
- Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289)
- Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281)
- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277)
- Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265)

### Removed

- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285)
2022-11-21 00:00:00 +00:00
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
Unity Technologies
0f7a30d285 com.unity.netcode.gameobjects@1.0.0-pre.10
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.0.0-pre.10] - 2022-06-21

### Added

- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)

### Changed

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

### Removed

### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
2022-06-21 00:00:00 +00:00
Unity Technologies
5b1fc203ed com.unity.netcode.gameobjects@1.0.0-pre.9
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [1.0.0-pre.9] - 2022-05-10

### Fixed

- Fixed Hosting again after failing to host now works correctly (#1938)
- Fixed NetworkManager to cleanup connected client lists after stopping (#1945)
- Fixed NetworkHide followed by NetworkShow on the same frame works correctly (#1940)
2022-05-10 00:00:00 +00:00
301 changed files with 28716 additions and 7330 deletions

View File

@@ -6,29 +6,282 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.4.0] - 2023-04-10
### Added
- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)
- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420)
- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420)
- Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388)
- Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388)
- Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388)
- Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388)
- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388)
- Added `HalfVector3` used for scale when half float precision is enabled. (#2388)
- Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388)
- Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388)
- Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388)
- Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388)
- Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388)
- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)
### Changed
- Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463)
- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388)
- Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388)
- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388)
- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)
### Fixed
- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)
- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441)
- Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426)
- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416)
- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416)
- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388)
- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
## [1.3.1] - 2023-03-27
### Added
- Added detection and graceful handling of corrupt packets for additional safety. (#2419)
### Changed
- The UTP component UI has been updated to be more user-friendly for new users by adding a simple toggle to switch between local-only (127.0.0.1) and remote (0.0.0.0) binding modes, using the toggle "Allow Remote Connections" (#2408)
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.3. (#2450)
- `NetworkShow()` of `NetworkObject`s are delayed until the end of the frame to ensure consistency of delta-driven variables like `NetworkList`.
- Dirty `NetworkObject` are reset at end-of-frame and not at serialization time.
- `NetworkHide()` of an object that was just `NetworkShow()`n produces a warning, as remote clients will _not_ get a spawn/despawn pair.
- Renamed the NetworkTransform.SetState parameter `shouldGhostsInterpolate` to `teleportDisabled` for better clarity of what that parameter does. (#2228)
- Network prefabs are now stored in a ScriptableObject that can be shared between NetworkManagers, and have been exposed for public access. By default, a Default Prefabs List is created that contains all NetworkObject prefabs in the project, and new NetworkManagers will default to using that unless that option is turned off in the Netcode for GameObjects settings. Existing NetworkManagers will maintain their existing lists, which can be migrated to the new format via a button in their inspector. (#2322)
### Fixed
- Fixed issue where changes to a layer's weight would not synchronize unless a state transition was occurring.(#2399)
- Fixed issue where `NetworkManager.LocalClientId` was returning the `NetworkTransport.ServerClientId` as opposed to the `NetworkManager.m_LocalClientId`. (#2398)
- Fixed issue where a dynamically spawned `NetworkObject` parented under an in-scene placed `NetworkObject` would have its `InScenePlaced` value changed to `true`. This would result in a soft synchronization error for late joining clients. (#2396)
- Fixed a UTP test that was failing when you install Unity Transport package 2.0.0 or newer. (#2347)
- Fixed issue where `NetcodeSettingsProvider` would throw an exception in Unity 2020.3.x versions. (#2345)
- Fixed server side issue where, depending upon component ordering, some NetworkBehaviour components might not have their OnNetworkDespawn method invoked if the client side disconnected. (#2323)
- Fixed a case where data corruption could occur when using UnityTransport when reaching a certain level of send throughput. (#2332)
- Fixed an issue in `UnityTransport` where an exception would be thrown if starting a Relay host/server on WebGL. This exception should only be thrown if using direct connections (where WebGL can't act as a host/server). (#2321)
- Fixed `NetworkAnimator` issue where it was not checking for `AnimatorStateTtansition.destinationStateMachine` and any possible sub-states defined within it. (#2309)
- Fixed `NetworkAnimator` issue where the host client was receiving the ClientRpc animation updates when the host was the owner.(#2309)
- Fixed `NetworkAnimator` issue with using pooled objects and when specific properties are cleaned during despawn and destroy.(#2309)
- Fixed issue where `NetworkAnimator` was checking for animation changes when the associated `NetworkObject` was not spawned.(#2309)
- Corrected an issue with the documentation for BufferSerializer (#2401)
## [1.2.0] - 2022-11-21
### Added
- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298)
- Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290)
- Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285)
- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client. (#2280)
### Changed
- Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310)
- When `NetworkConfig.EnsureNetworkVariableLengthSafety` is disabled `NetworkVariable` fields do not write the additional `ushort` size value (_which helps to reduce the total synchronization message size_), but when enabled it still writes the additional `ushort` value. (#2298)
- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276)
### Fixed
- Fixed `IsSpawnedObjectsPendingInDontDestroyOnLoad` is only set to true when loading a scene using `LoadSceneMode.Singleonly`. (#2330)
- Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298)
- Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298)
- Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298)
- Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296)
- Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292)
- Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289)
- Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281)
- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277)
- Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265)
### Removed
- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285)
## [1.1.0] - 2022-10-21
### 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
### Added
- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)
### Changed
- 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 an `Action<>` taking a `ConnectionApprovalRequest` and a `ConnectionApprovalResponse` that the client code must fill (#1972) (#2002)
### Removed
### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
## [1.0.0-pre.9] - 2022-05-10
### Fixed
- Fixed Hosting again after failing to host now works correctly (#1938)
- Fixed NetworkManager to cleanup connected client lists after stopping (#1945)
- Fixed NetworkHide followed by NetworkShow on the same frame works correctly (#1940)
## [1.0.0-pre.8] - 2022-04-27
### Changed
- `unmanaged` structs are no longer universally accepted as RPC parameters because some structs (i.e., structs with pointers in them, such as `NativeList<T>`) can't be supported by the default memcpy struct serializer. Structs that are intended to be serialized across the network must add `INetworkSerializeByMemcpy` to the interface list (i.e., `struct Foo : INetworkSerializeByMemcpy`). This interface is empty and just serves to mark the struct as compatible with memcpy serialization. For external structs you can't edit, you can pass them to RPCs by wrapping them in `ForceNetworkSerializeByMemcpy<T>`. (#1901)
- Changed requirement to register in-scene placed NetworkObjects with `NetworkManager` in order to respawn them. In-scene placed NetworkObjects are now automatically tracked during runtime and no longer need to be registered as a NetworkPrefab. (#1898)
### Removed
- Removed `SIPTransport` (#1870)
- 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).
- Removed `SIPTransport` (#1870)
- Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs) (#1912)
### Fixed
- Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898)
- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890)
- Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
- Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883)
- Fixed an issue where UNetTransport.StartServer would return success even if the underlying transport failed to start (#854)
- Passing generic types to RPCs no longer causes a native crash (#1901)
- Fixed a compile failure when compiling against com.unity.nuget.mono-cecil >= 1.11.4 (#1920)
- Fixed an issue where calling `Shutdown` on a `NetworkManager` that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877)
## [1.0.0-pre.7] - 2022-04-06
### Added
- Added editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828)
- Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823)
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
@@ -69,10 +322,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
## [1.0.0-pre.6] - 2022-03-02
### Added
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765)
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)
### Fixed
- Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683)
- Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683)
- Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683)
@@ -124,6 +379,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
@@ -138,6 +394,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
### Changed
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
- Updated com.unity.collections to 1.1.0 (#1451)
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
@@ -149,7 +406,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- ResetTrigger function to NetworkAnimator (#1327)
### Fixed
### Fixed
- Overflow exception when syncing Animator state. (#1327)
- Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329)
@@ -174,7 +431,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Added `ClientNetworkTransform` sample to the SDK package (#1168)
- Added `Bootstrap` sample to the SDK package (#1140)
- Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913)
- `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons
- `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons
- Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101)
- Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060)
- Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727)
@@ -231,7 +488,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Removed `NetworkDictionary`, `NetworkSet` (#1149)
- Removed `NetworkVariableSettings` (#1097)
- Removed predefined `NetworkVariable<T>` types (#1093)
- Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion`
- Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion`
- Removed `NetworkChannel` and `MultiplexTransportAdapter` (#1133)
- Removed ILPP backend for 2019.4, minimum required version is 2020.3+ (#895)
- `NetworkManager.NetworkConfig` had the following properties removed: (#1080)
@@ -303,14 +560,14 @@ This is the initial experimental Unity MLAPI Package, v0.1.0.
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later:
- Added new profiler modules for MLAPI that report important network data.
- Attached the profiler to a remote player to view network data over the wire.
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`.
### Changed
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management.
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings.
For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514).
@@ -343,7 +600,7 @@ For users of previous versions of MLAPI, this release renames APIs due to refact
### Fixed
- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame.
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method.
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode.
@@ -358,10 +615,10 @@ With a new release of MLAPI in Unity, some features have been removed:
- SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 -->
- [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo.
- [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes:
- Removed `SecuritySendFlags` from all APIs.
- Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`.
- Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries.
- Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing.
- Removed `SecuritySendFlags` from all APIs.
- Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`.
- Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries.
- Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing.
- Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later.
- Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details.
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer.
@@ -374,7 +631,7 @@ With a new release of MLAPI in Unity, some features have been removed:
- For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel.
- `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them
- `NetworkTransform` have the following issues:
- Replicated objects may have jitter.
- Replicated objects may have jitter.
- The owner is always authoritative about the object's position.
- Scale is not synchronized.
- Connection Approval is not called on the host client.

View File

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

159
Components/HalfVector3.cs Normal file
View File

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

View File

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

137
Components/HalfVector4.cs Normal file
View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: e54b65208bd3bbe4eaf62ca0384ae21f
guid: 03c78136f41ff84499e2a6ac4a7dd7a5
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -8,8 +8,10 @@ namespace Unity.Netcode
/// Solves for incoming values that are jittered
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
/// </summary>
/// <typeparam name="T">The type of interpolated value</typeparam>
public abstract class BufferedLinearInterpolator<T> where T : struct
{
internal float MaxInterpolationBound = 3.0f;
private struct BufferedItem
{
public T Item;
@@ -23,7 +25,7 @@ namespace Unity.Netcode
}
/// <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>
public float MaximumInterpolationTime = 0.1f;
@@ -72,7 +74,7 @@ namespace Unity.Netcode
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
/// <summary>
/// Resets Interpolator to initial state
/// Resets interpolator to initial state
/// </summary>
public void Clear()
{
@@ -84,6 +86,8 @@ namespace Unity.Netcode
/// <summary>
/// Teleports current interpolation value to targetValue.
/// </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)
{
m_LifetimeConsumedCount = 1;
@@ -158,6 +162,7 @@ namespace Unity.Netcode
/// </summary>
/// <param name="deltaTime">time since call</param>
/// <param name="serverTime">current server time</param>
/// <returns>The newly interpolated value of type 'T'</returns>
public T Update(float deltaTime, NetworkTime serverTime)
{
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
@@ -169,6 +174,7 @@ namespace Unity.Netcode
/// <param name="deltaTime">time since last call</param>
/// <param name="renderTime">our current 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)
{
TryConsumeFromBuffer(renderTime, serverTime);
@@ -203,10 +209,9 @@ namespace Unity.Netcode
t = 0.0f;
}
if (t > 3.0f) // max extrapolation
if (t > MaxInterpolationBound) // max extrapolation
{
// TODO this causes issues with teleport, investigate
// todo make this configurable
t = 1.0f;
}
}
@@ -222,6 +227,8 @@ namespace Unity.Netcode
/// <summary>
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
/// </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)
{
m_NbItemsReceivedThisFrame++;
@@ -241,6 +248,8 @@ namespace Unity.Netcode
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
{
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
@@ -251,6 +260,7 @@ namespace Unity.Netcode
/// <summary>
/// Gets latest value from the interpolator. This is updated every update as time goes by.
/// </summary>
/// <returns>The current interpolated value of type 'T'</returns>
public T GetInterpolatedValue()
{
return m_CurrentInterpValue;
@@ -259,36 +269,122 @@ namespace Unity.Netcode
/// <summary>
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
/// </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);
/// <summary>
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
/// </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);
}
/// <inheritdoc />
/// <remarks>
/// This is a buffered linear interpolator for a <see cref="float"/> type value
/// </remarks>
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
{
/// <inheritdoc />
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)
{
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>
{
/// <summary>
/// Use <see cref="Quaternion.Slerp"/> when <see cref="true"/>.
/// Use <see cref="Quaternion.Lerp"/> when <see cref="false"/>
/// </summary>
/// <remarks>
/// When using half precision (due to the imprecision) using <see cref="Quaternion.Lerp"/> is
/// less processor intensive (i.e. precision is already "imprecise").
/// When using full precision (to maintain precision) using <see cref="Quaternion.Slerp"/> is
/// more processor intensive yet yields more precise results.
/// </remarks>
public bool IsSlerp;
/// <inheritdoc />
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
{
return Quaternion.SlerpUnclamped(start, end, time);
if (IsSlerp)
{
return Quaternion.Slerp(start, end, time);
}
else
{
return Quaternion.Lerp(start, end, time);
}
}
/// <inheritdoc />
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
{
return Quaternion.SlerpUnclamped(start, end, time);
if (IsSlerp)
{
return Quaternion.Slerp(start, end, time);
}
else
{
return Quaternion.Lerp(start, end, time);
}
}
}
/// <summary>
/// A <see cref="BufferedLinearInterpolator<T>"/> <see cref="Vector3"/> implementation.
/// </summary>
public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator<Vector3>
{
/// <summary>
/// Use <see cref="Vector3.Slerp"/> when <see cref="true"/>.
/// Use <see cref="Vector3.Lerp"/> when <see cref="false"/>
/// </summary>
public bool IsSlerp;
/// <inheritdoc />
protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time)
{
if (IsSlerp)
{
return Vector3.Slerp(start, end, time);
}
else
{
return Vector3.Lerp(start, end, time);
}
}
/// <inheritdoc />
protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time)
{
if (IsSlerp)
{
return Vector3.Slerp(start, end, time);
}
else
{
return Vector3.Lerp(start, end, time);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace Unity.Netcode.Editor.CodeGen
{
internal sealed class INetworkSerializableILPP : ILPPInterface
{
public override ILPPInterface GetInstance() => this;
@@ -71,139 +67,31 @@ namespace Unity.Netcode.Editor.CodeGen
{
try
{
if (ImportReferences(mainModule))
var structTypes = mainModule.GetTypes()
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList();
foreach (var type in structTypes)
{
// Initialize all the delegates for various NetworkVariable types to ensure they can be serailized
// Find all types we know we're going to want to serialize.
// The list of these types includes:
// - Non-generic INetworkSerializable types
// - Non-Generic INetworkSerializeByMemcpy types
// - Enums that are not declared within generic types
// We can't process generic types because, to initialize a generic, we need a value
// for `T` to initialize it with.
var networkSerializableTypes = mainModule.GetTypes()
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList();
var structTypes = mainModule.GetTypes()
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList();
var enumTypes = mainModule.GetTypes()
.Where(t => t.Resolve().IsEnum && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
.ToList();
// Now, to support generics, we have to do an extra pass.
// We look for any type that's a NetworkBehaviour type
// Then we look through all the fields in that type, finding any field whose type is
// descended from `NetworkVariableSerialization`. Then we check `NetworkVariableSerialization`'s
// `T` value, and if it's a generic, then we know it was missed in the above sweep and needs
// to be initialized. Now we have a full generic instance rather than a generic definition,
// so we can validly generate an initializer for that particular instance of the generic type.
var networkSerializableTypesSet = new HashSet<TypeReference>(networkSerializableTypes);
var structTypesSet = new HashSet<TypeReference>(structTypes);
var enumTypesSet = new HashSet<TypeReference>(enumTypes);
var typeStack = new List<TypeReference>();
foreach (var type in mainModule.GetTypes())
// We'll avoid some confusion by ensuring users only choose one of the two
// serialization schemes - by method OR by memcpy, not both. We'll also do a cursory
// check that INetworkSerializeByMemcpy types are unmanaged.
if (type.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
{
// Check if it's a NetworkBehaviour
if (type.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName))
if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
{
// Iterate fields looking for NetworkVariableSerialization fields
foreach (var field in type.Fields)
{
// Get the field type and its base type
var fieldType = field.FieldType;
var baseType = fieldType.Resolve().BaseType;
if (baseType == null)
{
continue;
}
// This type stack is used for resolving NetworkVariableSerialization's T value
// When looking at base types, we get the type definition rather than the
// type reference... which means that we get the generic definition with an
// undefined T rather than the instance with the type filled in.
// We then have to walk backward back down the type stack to resolve what T
// is.
typeStack.Clear();
typeStack.Add(fieldType);
// Iterate through the base types until we get to Object.
// Object is the base for everything so we'll stop when we hit that.
while (baseType.Name != mainModule.TypeSystem.Object.Name)
{
// If we've found a NetworkVariableSerialization type...
if (baseType.IsGenericInstance && baseType.Resolve() == m_NetworkVariableSerializationType)
{
// Then we need to figure out what T is
var genericType = (GenericInstanceType)baseType;
var underlyingType = genericType.GenericArguments[0];
if (underlyingType.Resolve() == null)
{
underlyingType = ResolveGenericType(underlyingType, typeStack);
}
// If T is generic...
if (underlyingType.IsGenericInstance)
{
// Then we pick the correct set to add it to and set it up
// for initialization.
if (underlyingType.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
{
networkSerializableTypesSet.Add(underlyingType);
}
if (underlyingType.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
{
structTypesSet.Add(underlyingType);
}
if (underlyingType.Resolve().IsEnum)
{
enumTypesSet.Add(underlyingType);
}
}
break;
}
typeStack.Add(baseType);
baseType = baseType.Resolve().BaseType;
}
}
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other.");
}
// We'll also avoid some confusion by ensuring users only choose one of the two
// serialization schemes - by method OR by memcpy, not both. We'll also do a cursory
// check that INetworkSerializeByMemcpy types are unmanaged.
else if (type.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
if (!type.IsValueType)
{
if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
{
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other.");
}
if (!type.IsValueType)
{
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types.");
}
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types.");
}
}
if (networkSerializableTypes.Count + structTypes.Count + enumTypes.Count == 0)
{
return null;
}
// Finally we add to the module initializer some code to initialize the delegates in
// NetworkVariableSerialization<T> for all necessary values of T, by calling initialization
// methods in NetworkVariableHelpers.
CreateModuleInitializer(assemblyDefinition, networkSerializableTypesSet.ToList(), structTypesSet.ToList(), enumTypesSet.ToList());
}
else
{
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
}
}
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
@@ -228,102 +116,5 @@ namespace Unity.Netcode.Editor.CodeGen
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
}
private MethodReference m_InitializeDelegatesNetworkSerializable_MethodRef;
private MethodReference m_InitializeDelegatesStruct_MethodRef;
private MethodReference m_InitializeDelegatesEnum_MethodRef;
private TypeDefinition m_NetworkVariableSerializationType;
private const string k_InitializeNetworkSerializableMethodName = nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable);
private const string k_InitializeStructMethodName = nameof(NetworkVariableHelper.InitializeDelegatesStruct);
private const string k_InitializeEnumMethodName = nameof(NetworkVariableHelper.InitializeDelegatesEnum);
private bool ImportReferences(ModuleDefinition moduleDefinition)
{
var helperType = typeof(NetworkVariableHelper);
foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
switch (methodInfo.Name)
{
case k_InitializeNetworkSerializableMethodName:
m_InitializeDelegatesNetworkSerializable_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_InitializeStructMethodName:
m_InitializeDelegatesStruct_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_InitializeEnumMethodName:
m_InitializeDelegatesEnum_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
}
}
m_NetworkVariableSerializationType = moduleDefinition.ImportReference(typeof(NetworkVariableSerialization<>)).Resolve();
return true;
}
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;
}
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
// message types in the assembly with MessagingSystem.
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeReference> networkSerializableTypes, List<TypeReference> structTypes, List<TypeReference> enumTypes)
{
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 structTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegatesStruct_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
foreach (var type in networkSerializableTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegatesNetworkSerializable_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
foreach (var type in enumTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegatesEnum_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
break;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -52,9 +52,6 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(NetworkBehaviour):
ProcessNetworkBehaviour(typeDefinition);
break;
case nameof(NetworkVariableHelper):
ProcessNetworkVariableHelper(typeDefinition);
break;
case nameof(__RpcParams):
typeDefinition.IsPublic = true;
break;
@@ -103,25 +100,6 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
private void ProcessNetworkVariableHelper(TypeDefinition typeDefinition)
{
foreach (var methodDefinition in typeDefinition.Methods)
{
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesEnum))
{
methodDefinition.IsPublic = true;
}
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesStruct))
{
methodDefinition.IsPublic = true;
}
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable))
{
methodDefinition.IsPublic = true;
}
}
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
{
foreach (var nestedType in typeDefinition.NestedTypes)

View File

@@ -2,11 +2,13 @@
"name": "Unity.Netcode.Editor.CodeGen",
"rootNamespace": "Unity.Netcode.Editor.CodeGen",
"references": [
"Unity.Netcode.Runtime"
"Unity.Netcode.Runtime",
"Unity.Collections"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
@@ -15,5 +17,14 @@
"Mono.Cecil.Pdb.dll",
"Mono.Cecil.Rocks.dll"
],
"autoReferenced": false
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.nuget.mono-cecil",
"expression": "(0,1.11.4)",
"define": "CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES"
}
],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,53 @@
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor.Configuration
{
internal class NetcodeForGameObjectsEditorSettings
{
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
internal static int GetNetcodeInstallMultiplayerToolTips()
{
if (EditorPrefs.HasKey(InstallMultiplayerToolsTipDismissedPlayerPrefKey))
{
return EditorPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey);
}
return 0;
}
internal static void SetNetcodeInstallMultiplayerToolTips(int toolTipPrefSetting)
{
EditorPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, toolTipPrefSetting);
}
internal static bool GetAutoAddNetworkObjectSetting()
{
if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists))
{
return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists);
}
return false;
}
internal static void SetAutoAddNetworkObjectSetting(bool autoAddSetting)
{
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting);
}
}
[FilePath("ProjectSettings/NetcodeForGameObjects.settings", FilePathAttribute.Location.ProjectFolder)]
internal class NetcodeForGameObjectsProjectSettings : ScriptableSingleton<NetcodeForGameObjectsProjectSettings>
{
[SerializeField] public bool GenerateDefaultNetworkPrefabs = true;
internal void SaveSettings()
{
Save(true);
}
}
}

View File

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

View File

@@ -0,0 +1,179 @@
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor.Configuration
{
internal static class NetcodeSettingsProvider
{
private const float k_MaxLabelWidth = 450f;
private static float s_MaxLabelWidth;
private static bool s_ShowEditorSettingFields = true;
private static bool s_ShowProjectSettingFields = true;
[SettingsProvider]
public static SettingsProvider CreateNetcodeSettingsProvider()
{
// First parameter is the path in the Settings window.
// Second parameter is the scope of this setting: it only appears in the Settings window for the Project scope.
var provider = new SettingsProvider("Project/NetcodeForGameObjects", SettingsScope.Project)
{
label = "Netcode for GameObjects",
keywords = new[] { "netcode", "editor" },
guiHandler = OnGuiHandler,
};
return provider;
}
internal static NetcodeSettingsLabel NetworkObjectsSectionLabel;
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle;
internal static NetcodeSettingsLabel MultiplayerToolsLabel;
internal static NetcodeSettingsToggle MultiplayerToolTipStatusToggle;
/// <summary>
/// Creates an instance of the settings UI Elements if they don't already exist.
/// </summary>
/// <remarks>
/// We have to construct any NetcodeGUISettings derived classes here because in
/// version 2020.x.x EditorStyles.label does not exist yet (higher versions it does)
/// </remarks>
private static void CheckForInitialize()
{
if (NetworkObjectsSectionLabel == null)
{
NetworkObjectsSectionLabel = new NetcodeSettingsLabel("NetworkObject Helper Settings", 20);
}
if (AutoAddNetworkObjectToggle == null)
{
AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObject Component", "When enabled, NetworkObject components are automatically added to GameObjects when NetworkBehaviour components are added first.", 20);
}
if (MultiplayerToolsLabel == null)
{
MultiplayerToolsLabel = new NetcodeSettingsLabel("Multiplayer Tools", 20);
}
if (MultiplayerToolTipStatusToggle == null)
{
MultiplayerToolTipStatusToggle = new NetcodeSettingsToggle("Multiplayer Tools Install Reminder", "When enabled, the NetworkManager will display the notification to install the multiplayer tools package.", 20);
}
}
private static void OnGuiHandler(string obj)
{
// Make sure all NetcodeGUISettings derived classes are instantiated first
CheckForInitialize();
var autoAddNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting();
var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
var settings = NetcodeForGameObjectsProjectSettings.instance;
var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs;
EditorGUI.BeginChangeCheck();
GUILayout.BeginVertical("Box");
s_ShowEditorSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowEditorSettingFields, "Editor Settings");
if (s_ShowEditorSettingFields)
{
GUILayout.BeginVertical("Box");
NetworkObjectsSectionLabel.DrawLabel();
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
GUILayout.EndVertical();
GUILayout.BeginVertical("Box");
MultiplayerToolsLabel.DrawLabel();
multiplayerToolsTipStatus = MultiplayerToolTipStatusToggle.DrawToggle(multiplayerToolsTipStatus);
GUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
GUILayout.EndVertical();
GUILayout.BeginVertical("Box");
s_ShowProjectSettingFields = EditorGUILayout.BeginFoldoutHeaderGroup(s_ShowProjectSettingFields, "Project Settings");
if (s_ShowProjectSettingFields)
{
GUILayout.BeginVertical("Box");
const string generateNetworkPrefabsString = "Generate Default Network Prefabs List";
if (s_MaxLabelWidth == 0)
{
s_MaxLabelWidth = EditorStyles.label.CalcSize(new GUIContent(generateNetworkPrefabsString)).x;
s_MaxLabelWidth = Mathf.Min(k_MaxLabelWidth, s_MaxLabelWidth);
}
EditorGUIUtility.labelWidth = s_MaxLabelWidth;
GUILayout.Label("Network Prefabs", EditorStyles.boldLabel);
generateDefaultPrefabs = EditorGUILayout.Toggle(
new GUIContent(
generateNetworkPrefabsString,
"When enabled, a default NetworkPrefabsList object will be added to your project and kept up " +
"to date with all NetworkObject prefabs."),
generateDefaultPrefabs,
GUILayout.Width(s_MaxLabelWidth + 20));
GUILayout.EndVertical();
}
EditorGUILayout.EndFoldoutHeaderGroup();
GUILayout.EndVertical();
if (EditorGUI.EndChangeCheck())
{
NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs;
settings.SaveSettings();
}
}
}
internal class NetcodeSettingsLabel : NetcodeGUISettings
{
private string m_LabelContent;
public void DrawLabel()
{
EditorGUIUtility.labelWidth = m_LabelSize;
GUILayout.Label(m_LabelContent, EditorStyles.boldLabel, m_LayoutWidth);
}
public NetcodeSettingsLabel(string labelText, float layoutOffset = 0.0f)
{
m_LabelContent = labelText;
AdjustLabelSize(labelText, layoutOffset);
}
}
internal class NetcodeSettingsToggle : NetcodeGUISettings
{
private GUIContent m_ToggleContent;
public bool DrawToggle(bool currentSetting)
{
EditorGUIUtility.labelWidth = m_LabelSize;
return EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth);
}
public NetcodeSettingsToggle(string labelText, string toolTip, float layoutOffset)
{
AdjustLabelSize(labelText, layoutOffset);
m_ToggleContent = new GUIContent(labelText, toolTip);
}
}
internal class NetcodeGUISettings
{
private const float k_MaxLabelWidth = 450f;
protected float m_LabelSize { get; private set; }
protected GUILayoutOption m_LayoutWidth { get; private set; }
protected void AdjustLabelSize(string labelText, float offset = 0.0f)
{
m_LabelSize = Mathf.Min(k_MaxLabelWidth, EditorStyles.label.CalcSize(new GUIContent(labelText)).x);
m_LayoutWidth = GUILayout.Width(m_LabelSize + offset);
}
}
}

View File

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

View File

@@ -0,0 +1,187 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor.Configuration
{
/// <summary>
/// Updates the default <see cref="NetworkPrefabsList"/> instance when prefabs are updated (created, moved, deleted) in the project.
/// </summary>
public class NetworkPrefabProcessor : AssetPostprocessor
{
private static string s_DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset";
public static string DefaultNetworkPrefabsPath
{
get
{
return s_DefaultNetworkPrefabsPath;
}
internal set
{
s_DefaultNetworkPrefabsPath = value;
// Force a recache of the prefab list
s_PrefabsList = null;
}
}
private static NetworkPrefabsList s_PrefabsList;
private static Dictionary<string, NetworkPrefab> s_PrefabsListPath = new Dictionary<string, NetworkPrefab>();
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
var settings = NetcodeForGameObjectsProjectSettings.instance;
if (!settings.GenerateDefaultNetworkPrefabs)
{
return;
}
bool ProcessImportedAssets(string[] importedAssets1)
{
var dirty = false;
foreach (var assetPath in importedAssets1)
{
// We only care about GameObjects, skip everything else. Can't use the more targeted
// OnPostProcessPrefabs since that's not called for moves or deletes
if (AssetDatabase.GetMainAssetTypeAtPath(assetPath) != typeof(GameObject))
{
continue;
}
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (go.TryGetComponent<NetworkObject>(out _))
{
// Make sure we are not duplicating an already existing entry
if (s_PrefabsListPath.ContainsKey(assetPath))
{
// Is the imported asset different from the one we already have in the list?
if (s_PrefabsListPath[assetPath].Prefab.GetHashCode() != go.GetHashCode())
{
// If so remove the one in the list and continue on to add the imported one
s_PrefabsList.List.Remove(s_PrefabsListPath[assetPath]);
}
else // If they are identical, then just ignore the import
{
continue;
}
}
s_PrefabsList.List.Add(new NetworkPrefab { Prefab = go });
dirty = true;
}
}
return dirty;
}
bool ProcessDeletedAssets(string[] strings)
{
var dirty = false;
var deleted = new List<string>(strings);
for (int i = s_PrefabsList.List.Count - 1; i >= 0 && deleted.Count > 0; --i)
{
GameObject prefab;
try
{
prefab = s_PrefabsList.List[i].Prefab;
}
catch (MissingReferenceException)
{
s_PrefabsList.List.RemoveAt(i);
continue;
}
if (prefab == null)
{
s_PrefabsList.List.RemoveAt(i);
}
else
{
string noPath = AssetDatabase.GetAssetPath(prefab);
for (int j = strings.Length - 1; j >= 0; --j)
{
if (noPath == strings[j])
{
s_PrefabsList.List.RemoveAt(i);
deleted.RemoveAt(j);
dirty = true;
}
}
}
}
return dirty;
}
if (s_PrefabsList == null)
{
s_PrefabsList = GetOrCreateNetworkPrefabs(DefaultNetworkPrefabsPath, out var newList, true);
// A new list already processed all existing assets, no need to double-process imports & deletes
if (newList)
{
return;
}
}
// Clear our asset path to prefab table each time
s_PrefabsListPath.Clear();
// Create our asst path to prefab table
foreach (var prefabEntry in s_PrefabsList.List)
{
if (!s_PrefabsListPath.ContainsKey(AssetDatabase.GetAssetPath(prefabEntry.Prefab)))
{
s_PrefabsListPath.Add(AssetDatabase.GetAssetPath(prefabEntry.Prefab), prefabEntry);
}
}
// Process the imported and deleted assets
var markDirty = ProcessImportedAssets(importedAssets);
markDirty &= ProcessDeletedAssets(deletedAssets);
if (markDirty)
{
EditorUtility.SetDirty(s_PrefabsList);
}
}
internal static NetworkPrefabsList GetOrCreateNetworkPrefabs(string path, out bool isNew, bool addAll)
{
var defaultPrefabs = AssetDatabase.LoadAssetAtPath<NetworkPrefabsList>(path);
if (defaultPrefabs == null)
{
isNew = true;
defaultPrefabs = ScriptableObject.CreateInstance<NetworkPrefabsList>();
defaultPrefabs.IsDefault = true;
AssetDatabase.CreateAsset(defaultPrefabs, path);
if (addAll)
{
// This could be very expensive in large projects... maybe make it manually triggered via a menu?
defaultPrefabs.List = FindAll();
}
EditorUtility.SetDirty(defaultPrefabs);
AssetDatabase.SaveAssetIfDirty(defaultPrefabs);
return defaultPrefabs;
}
isNew = false;
return defaultPrefabs;
}
private static List<NetworkPrefab> FindAll()
{
var list = new List<NetworkPrefab>();
string[] guids = AssetDatabase.FindAssets("t:GameObject");
foreach (var guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (go.TryGetComponent(out NetworkObject _))
{
list.Add(new NetworkPrefab { Prefab = go });
}
}
return list;
}
}
}

View File

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

View File

@@ -0,0 +1,97 @@
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Unity.Netcode.Editor
{
[CustomEditor(typeof(NetworkPrefabsList), true)]
[CanEditMultipleObjects]
public class NetworkPrefabsEditor : UnityEditor.Editor
{
private ReorderableList m_NetworkPrefabsList;
private SerializedProperty m_IsDefaultBool;
private void OnEnable()
{
m_IsDefaultBool = serializedObject.FindProperty(nameof(NetworkPrefabsList.IsDefault));
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty("List"), true, true, true, true);
m_NetworkPrefabsList.elementHeightCallback = index =>
{
var networkOverrideInt = 0;
if (m_NetworkPrefabsList.count > 0)
{
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
networkOverrideInt = networkOverrideProp.enumValueIndex;
}
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
};
m_NetworkPrefabsList.drawElementCallback = (rect, index, isActive, isFocused) =>
{
rect.y += 5;
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
var networkPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Prefab));
var networkSourceHashProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourceHashToOverride));
var networkSourcePrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourcePrefabToOverride));
var networkTargetPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.OverridingTargetPrefab));
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
var networkOverrideInt = networkOverrideProp.enumValueIndex;
var networkOverrideEnum = (NetworkPrefabOverride)networkOverrideInt;
EditorGUI.LabelField(new Rect(rect.x + rect.width - 70, rect.y, 60, EditorGUIUtility.singleLineHeight), "Override");
if (networkOverrideEnum == NetworkPrefabOverride.None)
{
if (EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), false))
{
networkOverrideProp.enumValueIndex = (int)NetworkPrefabOverride.Prefab;
}
}
else
{
if (!EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), true))
{
networkOverrideProp.enumValueIndex = 0;
networkOverrideEnum = NetworkPrefabOverride.None;
}
}
if (networkOverrideEnum == NetworkPrefabOverride.None)
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 80, EditorGUIUtility.singleLineHeight), networkPrefabProp, GUIContent.none);
}
else
{
networkOverrideProp.enumValueIndex = GUI.Toolbar(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), networkOverrideInt - 1, new[] { "Prefab", "Hash" }) + 1;
if (networkOverrideEnum == NetworkPrefabOverride.Prefab)
{
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourcePrefabProp, GUIContent.none);
}
else
{
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourceHashProp, GUIContent.none);
}
rect.y += EditorGUIUtility.singleLineHeight + 5;
EditorGUI.LabelField(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), "Overriding Prefab");
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 110, EditorGUIUtility.singleLineHeight), networkTargetPrefabProp, GUIContent.none);
}
};
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
}
public override void OnInspectorGUI()
{
using (new EditorGUI.DisabledScope(true))
{
EditorGUILayout.PropertyField(m_IsDefaultBool);
}
m_NetworkPrefabsList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

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

View File

@@ -0,0 +1,184 @@
using Unity.Netcode.Components;
#if UNITY_UNET_PRESENT
using Unity.Netcode.Transports.UNET;
#endif
using Unity.Netcode.Transports.UTP;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor
{
/// <summary>
/// Internal use. Hides the script field for the given component.
/// </summary>
public class HiddenScriptEditor : UnityEditor.Editor
{
private static readonly string[] k_HiddenFields = { "m_Script" };
/// <summary>
/// Draws inspector properties without the script field.
/// </summary>
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
serializedObject.UpdateIfRequiredOrScript();
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
}
}
#if UNITY_UNET_PRESENT
/// <summary>
/// Internal use. Hides the script field for UNetTransport.
/// </summary>
[CustomEditor(typeof(UNetTransport), true)]
public class UNetTransportEditor : HiddenScriptEditor
{
}
#endif
/// <summary>
/// Internal use. Hides the script field for UnityTransport.
/// </summary>
[CustomEditor(typeof(UnityTransport), true)]
public class UnityTransportEditor : HiddenScriptEditor
{
private static readonly string[] k_HiddenFields = { "m_Script", "ConnectionData" };
private bool m_AllowIncomingConnections;
private bool m_Initialized;
private UnityTransport m_UnityTransport;
private SerializedProperty m_ServerAddressProperty;
private SerializedProperty m_ServerPortProperty;
private SerializedProperty m_OverrideBindIpProperty;
private const string k_LoopbackIpv4 = "127.0.0.1";
private const string k_LoopbackIpv6 = "::1";
private const string k_AnyIpv4 = "0.0.0.0";
private const string k_AnyIpv6 = "::";
private void Initialize()
{
if (m_Initialized)
{
return;
}
m_Initialized = true;
m_UnityTransport = (UnityTransport)target;
var connectionDataProperty = serializedObject.FindProperty(nameof(UnityTransport.ConnectionData));
m_ServerAddressProperty = connectionDataProperty.FindPropertyRelative(nameof(UnityTransport.ConnectionAddressData.Address));
m_ServerPortProperty = connectionDataProperty.FindPropertyRelative(nameof(UnityTransport.ConnectionAddressData.Port));
m_OverrideBindIpProperty = connectionDataProperty.FindPropertyRelative(nameof(UnityTransport.ConnectionAddressData.ServerListenAddress));
}
/// <summary>
/// Draws inspector properties without the script field.
/// </summary>
public override void OnInspectorGUI()
{
Initialize();
EditorGUI.BeginChangeCheck();
serializedObject.UpdateIfRequiredOrScript();
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
EditorGUILayout.PropertyField(m_ServerAddressProperty);
EditorGUILayout.PropertyField(m_ServerPortProperty);
serializedObject.ApplyModifiedProperties();
EditorGUILayout.HelpBox("It's recommended to leave remote connections disabled for local testing to avoid exposing ports on your device.", MessageType.Info);
bool allowRemoteConnections = m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv4 && m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv6 && !string.IsNullOrEmpty(m_UnityTransport.ConnectionData.ServerListenAddress);
allowRemoteConnections = EditorGUILayout.Toggle(new GUIContent("Allow Remote Connections?", $"Bind IP: {m_UnityTransport.ConnectionData.ServerListenAddress}"), allowRemoteConnections);
bool isIpV6 = m_UnityTransport.ConnectionData.IsIpv6;
if (!allowRemoteConnections)
{
if (m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv4 && m_UnityTransport.ConnectionData.ServerListenAddress != k_LoopbackIpv6)
{
if (isIpV6)
{
m_UnityTransport.ConnectionData.ServerListenAddress = k_LoopbackIpv6;
}
else
{
m_UnityTransport.ConnectionData.ServerListenAddress = k_LoopbackIpv4;
}
EditorUtility.SetDirty(m_UnityTransport);
}
}
using (new EditorGUI.DisabledScope(!allowRemoteConnections))
{
string overrideIp = m_UnityTransport.ConnectionData.ServerListenAddress;
if (overrideIp == k_AnyIpv4 || overrideIp == k_AnyIpv6 || overrideIp == k_LoopbackIpv4 || overrideIp == k_LoopbackIpv6)
{
overrideIp = "";
}
overrideIp = EditorGUILayout.TextField("Override Bind IP (optional)", overrideIp);
if (allowRemoteConnections)
{
if (overrideIp == "")
{
if (isIpV6)
{
overrideIp = k_AnyIpv6;
}
else
{
overrideIp = k_AnyIpv4;
}
}
if (m_UnityTransport.ConnectionData.ServerListenAddress != overrideIp)
{
m_UnityTransport.ConnectionData.ServerListenAddress = overrideIp;
EditorUtility.SetDirty(m_UnityTransport);
}
}
}
}
}
#if COM_UNITY_MODULES_ANIMATION
/// <summary>
/// Internal use. Hides the script field for NetworkAnimator.
/// </summary>
[CustomEditor(typeof(NetworkAnimator), true)]
public class NetworkAnimatorEditor : HiddenScriptEditor
{
}
#endif
#if COM_UNITY_MODULES_PHYSICS
/// <summary>
/// Internal use. Hides the script field for NetworkRigidbody.
/// </summary>
[CustomEditor(typeof(NetworkRigidbody), true)]
public class NetworkRigidbodyEditor : HiddenScriptEditor
{
}
#endif
#if COM_UNITY_MODULES_PHYSICS2D
/// <summary>
/// Internal use. Hides the script field for NetworkRigidbody2D.
/// </summary>
[CustomEditor(typeof(NetworkRigidbody2D), true)]
public class NetworkRigidbody2DEditor : HiddenScriptEditor
{
}
#endif
}

View File

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

View File

@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor
{
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="NetworkBehaviour"/>
/// </summary>
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourEditor : UnityEditor.Editor
@@ -16,6 +20,7 @@ namespace Unity.Netcode.Editor
private readonly Dictionary<string, object> m_NetworkVariableObjects = new Dictionary<string, object>();
private GUIContent m_NetworkVariableLabelGuiContent;
private GUIContent m_NetworkListLabelGuiContent;
private void Init(MonoScript script)
{
@@ -26,6 +31,7 @@ namespace Unity.Netcode.Editor
m_NetworkVariableObjects.Clear();
m_NetworkVariableLabelGuiContent = new GUIContent("NetworkVariable", "This variable is a NetworkVariable. It can not be serialized and can only be changed during runtime.");
m_NetworkListLabelGuiContent = new GUIContent("NetworkList", "This variable is a NetworkList. It is rendered, but you can't serialize or change it.");
var fields = script.GetClass().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
for (int i = 0; i < fields.Length; i++)
@@ -33,8 +39,15 @@ namespace Unity.Netcode.Editor
var ft = fields[i].FieldType;
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
{
m_NetworkVariableNames.Add(fields[i].Name);
m_NetworkVariableFields.Add(fields[i].Name, fields[i]);
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
Debug.Log($"Adding NetworkVariable {fields[i].Name}");
}
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
{
m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name));
m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]);
Debug.Log($"Adding NetworkList {fields[i].Name}");
}
}
}
@@ -68,25 +81,48 @@ namespace Unity.Netcode.Editor
EditorGUILayout.BeginHorizontal();
if (genericType.IsValueType)
{
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkVariableValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkContainerValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
var genericMethod = method.MakeGenericMethod(genericType);
genericMethod.Invoke(this, new[] { (object)index });
}
else
{
EditorGUILayout.LabelField("Type not renderable");
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
EditorGUILayout.EndHorizontal();
}
}
private void RenderNetworkContainerValueType<T>(int index) where T : unmanaged, IEquatable<T>
{
try
{
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
RenderNetworkVariableValueType(index, networkVariable);
}
catch (Exception)
{
try
{
var networkList = (NetworkList<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
RenderNetworkListValueType(index, networkList);
}
catch (Exception e)
{
Debug.Log(e);
throw;
}
}
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
EditorGUILayout.EndHorizontal();
}
private void RenderNetworkVariableValueType<T>(int index) where T : unmanaged
private void RenderNetworkVariableValueType<T>(int index, NetworkVariable<T> networkVariable) where T : unmanaged
{
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
var type = typeof(T);
object val = networkVariable.Value;
string name = m_NetworkVariableNames[index];
string variableName = m_NetworkVariableNames[index];
var behaviour = (NetworkBehaviour)target;
@@ -95,47 +131,51 @@ namespace Unity.Netcode.Editor
{
if (type == typeof(int))
{
val = EditorGUILayout.IntField(name, (int)val);
val = EditorGUILayout.IntField(variableName, (int)val);
}
else if (type == typeof(uint))
{
val = (uint)EditorGUILayout.LongField(name, (long)((uint)val));
val = (uint)EditorGUILayout.LongField(variableName, (uint)val);
}
else if (type == typeof(short))
{
val = (short)EditorGUILayout.IntField(name, (int)((short)val));
val = (short)EditorGUILayout.IntField(variableName, (short)val);
}
else if (type == typeof(ushort))
{
val = (ushort)EditorGUILayout.IntField(name, (int)((ushort)val));
val = (ushort)EditorGUILayout.IntField(variableName, (ushort)val);
}
else if (type == typeof(sbyte))
{
val = (sbyte)EditorGUILayout.IntField(name, (int)((sbyte)val));
val = (sbyte)EditorGUILayout.IntField(variableName, (sbyte)val);
}
else if (type == typeof(byte))
{
val = (byte)EditorGUILayout.IntField(name, (int)((byte)val));
val = (byte)EditorGUILayout.IntField(variableName, (byte)val);
}
else if (type == typeof(long))
{
val = EditorGUILayout.LongField(name, (long)val);
val = EditorGUILayout.LongField(variableName, (long)val);
}
else if (type == typeof(ulong))
{
val = (ulong)EditorGUILayout.LongField(name, (long)((ulong)val));
val = (ulong)EditorGUILayout.LongField(variableName, (long)((ulong)val));
}
else if (type == typeof(float))
{
val = EditorGUILayout.FloatField(variableName, (float)((float)val));
}
else if (type == typeof(bool))
{
val = EditorGUILayout.Toggle(name, (bool)val);
val = EditorGUILayout.Toggle(variableName, (bool)val);
}
else if (type == typeof(string))
{
val = EditorGUILayout.TextField(name, (string)val);
val = EditorGUILayout.TextField(variableName, (string)val);
}
else if (type.IsEnum)
{
val = EditorGUILayout.EnumPopup(name, (Enum)val);
val = EditorGUILayout.EnumPopup(variableName, (Enum)val);
}
else
{
@@ -146,11 +186,33 @@ namespace Unity.Netcode.Editor
}
else
{
EditorGUILayout.LabelField(name, EditorStyles.wordWrappedLabel);
EditorGUILayout.LabelField(variableName, EditorStyles.wordWrappedLabel);
EditorGUILayout.SelectableLabel(val.ToString(), EditorStyles.wordWrappedLabel);
}
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
}
private void RenderNetworkListValueType<T>(int index, NetworkList<T> networkList)
where T : unmanaged, IEquatable<T>
{
string variableName = m_NetworkVariableNames[index];
string value = "";
bool addComma = false;
foreach (var v in networkList)
{
if (addComma)
{
value += ", ";
}
value += v.ToString();
addComma = true;
}
EditorGUILayout.LabelField(variableName, value);
GUILayout.Label(m_NetworkListLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkListLabelGuiContent).x));
}
/// <inheritdoc/>
public override void OnInspectorGUI()
{
if (!m_Initialized)
@@ -218,13 +280,21 @@ namespace Unity.Netcode.Editor
/// </summary>
private void OnEnable()
{
// This can be null and throw an exception when running test runner in the editor
if (target == null)
{
return;
}
// When we first add a NetworkBehaviour this editor will be enabled
// so we go ahead and check for an already existing NetworkObject here
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)
{
if (transform.parent == null || transform.parent == transform)
@@ -239,6 +309,8 @@ namespace Unity.Netcode.Editor
/// does not already have a NetworkObject component. If not it will notify
/// the user that NetworkBehaviours require a NetworkObject.
/// </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)
{
// If there are no NetworkBehaviours or no gameObject, then exit early
@@ -249,11 +321,42 @@ namespace Unity.Netcode.Editor
// Now get the root parent transform to the current GameObject (or itself)
var rootTransform = GetRootParentTransform(gameObject.transform);
if (!rootTransform.TryGetComponent<NetworkManager>(out var networkManager))
{
networkManager = rootTransform.GetComponentInChildren<NetworkManager>();
}
// If there is a NetworkManager, then notify the user that a NetworkManager cannot have NetworkBehaviour components
if (networkManager != null)
{
var networkBehaviours = networkManager.gameObject.GetComponents<NetworkBehaviour>();
var networkBehavioursChildren = networkManager.gameObject.GetComponentsInChildren<NetworkBehaviour>();
if (networkBehaviours.Length > 0 || networkBehavioursChildren.Length > 0)
{
if (EditorUtility.DisplayDialog("NetworkBehaviour or NetworkManager Cannot Be Added", $"{nameof(NetworkManager)}s cannot have {nameof(NetworkBehaviour)} components added to the root parent or any of its children." +
$" Would you like to remove the NetworkManager or NetworkBehaviour?", "NetworkManager", "NetworkBehaviour"))
{
DestroyImmediate(networkManager);
}
else
{
foreach (var networkBehaviour in networkBehaviours)
{
DestroyImmediate(networkBehaviour);
}
foreach (var networkBehaviour in networkBehaviours)
{
DestroyImmediate(networkBehaviour);
}
}
return;
}
}
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
var networkObject = rootTransform.GetComponent<NetworkObject>();
if (networkObject == null)
if (!rootTransform.TryGetComponent<NetworkObject>(out var networkObject))
{
networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
@@ -263,7 +366,7 @@ namespace Unity.Netcode.Editor
// and the user has already turned "Auto-Add NetworkObject" on when first notified about the requirement
// then just send a reminder to the user why the NetworkObject they just deleted seemingly "re-appeared"
// again.
if (networkObjectRemoved && EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists) && EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists))
if (networkObjectRemoved && NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting())
{
Debug.LogWarning($"{gameObject.name} still has {nameof(NetworkBehaviour)}s and Auto-Add NetworkObjects is enabled. A NetworkObject is being added back to {gameObject.name}.");
Debug.Log($"To reset Auto-Add NetworkObjects: Select the Netcode->General->Reset Auto-Add NetworkObject menu item.");
@@ -272,7 +375,7 @@ namespace Unity.Netcode.Editor
// Notify and provide the option to add it one time, always add a NetworkObject, or do nothing and let the user manually add it
if (EditorUtility.DisplayDialog($"{nameof(NetworkBehaviour)}s require a {nameof(NetworkObject)}",
$"{gameObject.name} does not have a {nameof(NetworkObject)} component. Would you like to add one now?", "Yes", "No (manually add it)",
DialogOptOutDecisionType.ForThisMachine, AutoAddNetworkObjectIfNoneExists))
DialogOptOutDecisionType.ForThisMachine, NetcodeForGameObjectsEditorSettings.AutoAddNetworkObjectIfNoneExists))
{
gameObject.AddComponent<NetworkObject>();
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
@@ -282,20 +385,5 @@ namespace Unity.Netcode.Editor
}
}
}
/// <summary>
/// This allows users to reset the Auto-Add NetworkObject preference
/// so the next time they add a NetworkBehaviour to a GameObject without
/// a NetworkObject it will display the dialog box again and not
/// automatically add a NetworkObject.
/// </summary>
[MenuItem("Netcode/General/Reset Auto-Add NetworkObject", false, 1)]
private static void ResetMultiplayerToolsTipStatus()
{
if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists))
{
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, false);
}
}
}
}

View File

@@ -1,16 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
using UnityEditorInternal;
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)]
[CanEditMultipleObjects]
public class NetworkManagerEditor : UnityEditor.Editor
{
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
private static GUIStyle s_CenteredWordWrappedLabelStyle;
private static GUIStyle s_HelpBoxStyle;
@@ -36,8 +40,7 @@ namespace Unity.Netcode.Editor
private SerializedProperty m_NetworkIdRecycleDelayProperty;
private SerializedProperty m_RpcHashSizeProperty;
private SerializedProperty m_LoadSceneTimeOutProperty;
private ReorderableList m_NetworkPrefabsList;
private SerializedProperty m_PrefabsList;
private NetworkManager m_NetworkManager;
private bool m_Initialized;
@@ -102,7 +105,9 @@ namespace Unity.Netcode.Editor
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
m_PrefabsList = m_NetworkConfigProperty
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
ReloadTransports();
}
@@ -128,78 +133,12 @@ namespace Unity.Netcode.Editor
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
m_PrefabsList = m_NetworkConfigProperty
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
}
private void OnEnable()
{
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true);
m_NetworkPrefabsList.elementHeightCallback = index =>
{
var networkOverrideInt = 0;
if (m_NetworkPrefabsList.count > 0)
{
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
networkOverrideInt = networkOverrideProp.enumValueIndex;
}
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
};
m_NetworkPrefabsList.drawElementCallback = (rect, index, isActive, isFocused) =>
{
rect.y += 5;
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
var networkPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Prefab));
var networkSourceHashProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourceHashToOverride));
var networkSourcePrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourcePrefabToOverride));
var networkTargetPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.OverridingTargetPrefab));
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
var networkOverrideInt = networkOverrideProp.enumValueIndex;
var networkOverrideEnum = (NetworkPrefabOverride)networkOverrideInt;
EditorGUI.LabelField(new Rect(rect.x + rect.width - 70, rect.y, 60, EditorGUIUtility.singleLineHeight), "Override");
if (networkOverrideEnum == NetworkPrefabOverride.None)
{
if (EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), false))
{
networkOverrideProp.enumValueIndex = (int)NetworkPrefabOverride.Prefab;
}
}
else
{
if (!EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), true))
{
networkOverrideProp.enumValueIndex = 0;
networkOverrideEnum = NetworkPrefabOverride.None;
}
}
if (networkOverrideEnum == NetworkPrefabOverride.None)
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 80, EditorGUIUtility.singleLineHeight), networkPrefabProp, GUIContent.none);
}
else
{
networkOverrideProp.enumValueIndex = GUI.Toolbar(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), networkOverrideInt - 1, new[] { "Prefab", "Hash" }) + 1;
if (networkOverrideEnum == NetworkPrefabOverride.Prefab)
{
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourcePrefabProp, GUIContent.none);
}
else
{
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourceHashProp, GUIContent.none);
}
rect.y += EditorGUIUtility.singleLineHeight + 5;
EditorGUI.LabelField(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), "Overriding Prefab");
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 110, EditorGUIUtility.singleLineHeight), networkTargetPrefabProp, GUIContent.none);
}
};
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
}
/// <inheritdoc/>
public override void OnInspectorGUI()
{
Initialize();
@@ -209,18 +148,6 @@ namespace Unity.Netcode.Editor
DrawInstallMultiplayerToolsTip();
#endif
{
var iterator = serializedObject.GetIterator();
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
{
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
{
EditorGUILayout.PropertyField(iterator, false);
}
}
}
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
{
serializedObject.Update();
@@ -231,7 +158,62 @@ namespace Unity.Netcode.Editor
EditorGUILayout.PropertyField(m_PlayerPrefabProperty);
EditorGUILayout.Space();
m_NetworkPrefabsList.DoLayoutList();
if (m_NetworkManager.NetworkConfig.HasOldPrefabList())
{
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
if (GUILayout.Button(new GUIContent("Migrate Prefab List", "Converts the old format Network Prefab list to a new Scriptable Object")))
{
// Default directory
var directory = "Assets/";
var assetPath = AssetDatabase.GetAssetPath(m_NetworkManager);
if (assetPath == "")
{
assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(m_NetworkManager);
}
if (assetPath != "")
{
directory = Path.GetDirectoryName(assetPath);
}
else
{
#if UNITY_2021_1_OR_NEWER
var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
#else
var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(m_NetworkManager.gameObject);
#endif
if (prefabStage != null)
{
var prefabPath = prefabStage.assetPath;
if (!string.IsNullOrEmpty(prefabPath))
{
directory = Path.GetDirectoryName(prefabPath);
}
}
if (m_NetworkManager.gameObject.scene != null)
{
var scenePath = m_NetworkManager.gameObject.scene.path;
if (!string.IsNullOrEmpty(scenePath))
{
directory = Path.GetDirectoryName(scenePath);
}
}
}
var networkPrefabs = m_NetworkManager.NetworkConfig.MigrateOldNetworkPrefabsToNetworkPrefabsList();
string path = Path.Combine(directory, $"NetworkPrefabs-{m_NetworkManager.GetInstanceID()}.asset");
Debug.Log("Saving migrated Network Prefabs List to " + path);
AssetDatabase.CreateAsset(networkPrefabs, path);
EditorUtility.SetDirty(m_NetworkManager);
}
}
else
{
if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabsLists.Count == 0)
{
EditorGUILayout.HelpBox("You have no prefab list selected. You will have to add your prefabs manually at runtime for netcode to work.", MessageType.Warning);
}
EditorGUILayout.PropertyField(m_PrefabsList);
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
@@ -249,13 +231,7 @@ namespace Unity.Netcode.Editor
{
ReloadTransports();
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]);
if (transportComponent == null)
{
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
}
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
Repaint();
@@ -366,22 +342,26 @@ namespace Unity.Netcode.Editor
const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
const string infoIconName = "console.infoicon";
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
if (NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() != 0)
{
return;
}
if (s_CenteredWordWrappedLabelStyle == null)
{
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label);
s_CenteredWordWrappedLabelStyle.wordWrap = true;
s_CenteredWordWrappedLabelStyle.alignment = TextAnchor.MiddleLeft;
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label)
{
wordWrap = true,
alignment = TextAnchor.MiddleLeft
};
}
if (s_HelpBoxStyle == null)
{
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox);
s_HelpBoxStyle.padding = new RectOffset(10, 10, 10, 10);
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox)
{
padding = new RectOffset(10, 10, 10, 10)
};
}
var openDocsButtonStyle = GUI.skin.button;
@@ -412,7 +392,7 @@ namespace Unity.Netcode.Editor
GUILayout.FlexibleSpace();
if (GUILayout.Button(dismissButtonText, dismissButtonStyle, GUILayout.ExpandWidth(false)))
{
PlayerPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 1);
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(1);
}
EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);
GUILayout.FlexibleSpace();

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor;
namespace Unity.Netcode.Editor
{
@@ -32,6 +33,24 @@ namespace Unity.Netcode.Editor
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
// Initialize default values for new NetworkManagers
//
// When the default prefab list is enabled, this will default
// new NetworkManagers to using it.
//
// This will get run when new NetworkManagers are added, and
// when the user presses the "reset" button on a NetworkManager
// in the inspector.
NetworkManager.OnNetworkManagerReset = manager =>
{
var settings = NetcodeForGameObjectsProjectSettings.instance;
if (settings.GenerateDefaultNetworkPrefabs)
{
manager.NetworkConfig = new NetworkConfig();
manager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List<NetworkPrefabsList> { NetworkPrefabProcessor.GetOrCreateNetworkPrefabs(NetworkPrefabProcessor.DefaultNetworkPrefabsPath, out _, true) };
}
};
}
private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
@@ -64,8 +83,12 @@ namespace Unity.Netcode.Editor
{
var scenesList = EditorBuildSettings.scenes.ToList();
var activeScene = SceneManager.GetActiveScene();
var isSceneInBuildSettings = scenesList.Where((c) => c.path == activeScene.path).Count() == 1;
var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1;
#if UNITY_2023_1_OR_NEWER
var networkManager = Object.FindFirstObjectByType<NetworkManager>();
#else
var networkManager = Object.FindObjectOfType<NetworkManager>();
#endif
if (!isSceneInBuildSettings && networkManager != null)
{
if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement)
@@ -109,9 +132,8 @@ namespace Unity.Netcode.Editor
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
{
// Check for any NetworkObject at the same gameObject relative layer
var networkObject = networkManager.gameObject.GetComponent<NetworkObject>();
if (networkObject == null)
if (!networkManager.gameObject.TryGetComponent<NetworkObject>(out var networkObject))
{
// if none is found, check to see if any children have a NetworkObject
networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>();

View File

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

View File

@@ -1,9 +1,12 @@
using Unity.Netcode.Components;
using UnityEditor;
using UnityEngine;
using Unity.Netcode.Components;
namespace Unity.Netcode.Editor
{
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="NetworkTransform"/>
/// </summary>
[CustomEditor(typeof(NetworkTransform), true)]
public class NetworkTransformEditor : UnityEditor.Editor
{
@@ -22,12 +25,18 @@ namespace Unity.Netcode.Editor
private SerializedProperty m_InLocalSpaceProperty;
private SerializedProperty m_InterpolateProperty;
private SerializedProperty m_UseQuaternionSynchronization;
private SerializedProperty m_UseQuaternionCompression;
private SerializedProperty m_UseHalfFloatPrecision;
private SerializedProperty m_SlerpPosition;
private static int s_ToggleOffset = 45;
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5;
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation");
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");
/// <inheritdoc/>
public void OnEnable()
{
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
@@ -44,8 +53,13 @@ namespace Unity.Netcode.Editor
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold));
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace));
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization));
m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression));
m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision));
m_SlerpPosition = serializedObject.FindProperty(nameof(NetworkTransform.SlerpPosition));
}
/// <inheritdoc/>
public override void OnInspectorGUI()
{
EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel);
@@ -66,6 +80,8 @@ namespace Unity.Netcode.Editor
GUILayout.EndHorizontal();
}
if (!m_UseQuaternionSynchronization.boolValue)
{
GUILayout.BeginHorizontal();
@@ -83,6 +99,13 @@ namespace Unity.Netcode.Editor
GUILayout.EndHorizontal();
}
else
{
m_SyncRotationXProperty.boolValue = true;
m_SyncRotationYProperty.boolValue = true;
m_SyncRotationZProperty.boolValue = true;
}
{
GUILayout.BeginHorizontal();
@@ -111,6 +134,17 @@ namespace Unity.Netcode.Editor
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
EditorGUILayout.PropertyField(m_InterpolateProperty);
EditorGUILayout.PropertyField(m_SlerpPosition);
EditorGUILayout.PropertyField(m_UseQuaternionSynchronization);
if (m_UseQuaternionSynchronization.boolValue)
{
EditorGUILayout.PropertyField(m_UseQuaternionCompression);
}
else
{
m_UseQuaternionCompression.boolValue = false;
}
EditorGUILayout.PropertyField(m_UseHalfFloatPrecision);
#if COM_UNITY_MODULES_PHYSICS
// if rigidbody is present but network rigidbody is not present

View File

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

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

View File

@@ -1,14 +1,25 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Netcode.Components")]
#if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
#endif
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
#endif // UNITY_EDITOR
#if MULTIPLAYER_TOOLS
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")]
#endif // MULTIPLAYER_TOOLS
#if COM_UNITY_NETCODE_ADAPTER_UTP
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
#endif // COM_UNITY_NETCODE_ADAPTER_UTP
#if UNITY_INCLUDE_TESTS
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
#if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
#endif // UNITY_EDITOR
#if MULTIPLAYER_TOOLS
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
#endif // MULTIPLAYER_TOOLS
#endif // UNITY_INCLUDE_TESTS

View File

@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Netcode
{
@@ -30,20 +31,8 @@ namespace Unity.Netcode
[Tooltip("When set, NetworkManager will automatically create and spawn the assigned player prefab. This can be overridden by adding it to the NetworkPrefabs list and selecting override.")]
public GameObject PlayerPrefab;
/// <summary>
/// A list of prefabs that can be dynamically spawned.
/// </summary>
[SerializeField]
[Tooltip("The prefabs that can be spawned across the network")]
internal List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
/// <summary>
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
/// Generated at runtime and OnValidate
/// </summary>
internal Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
internal Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
public NetworkPrefabs Prefabs = new NetworkPrefabs();
/// <summary>
@@ -53,9 +42,21 @@ namespace Unity.Netcode
public uint TickRate = 30;
/// <summary>
/// The amount of seconds to wait for handshake to complete before timing out a client
/// The amount of seconds for the server to wait for the connection approval handshake to complete before the client is disconnected.
///
/// If the timeout is reached before approval is completed the client will be disconnected.
/// </summary>
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
/// <remarks>
/// The period begins after the <see cref="NetworkEvent.Connect"/> is received on the server.
/// The period ends once the server finishes processing a <see cref="ConnectionRequestMessage"/> from the client.
///
/// This setting is independent of any Transport-level timeouts that may be in effect. It covers the time between
/// the connection being established on the Transport layer, the client sending a
/// <see cref="ConnectionRequestMessage"/>, and the server processing that message through <see cref="ConnectionApproval"/>.
///
/// This setting is server-side only.
/// </remarks>
[Tooltip("The amount of seconds for the server to wait for the connection approval handshake to complete before the client is disconnected")]
public int ClientConnectionBufferTimeout = 10;
/// <summary>
@@ -138,8 +139,16 @@ namespace Unity.Netcode
/// </summary>
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)
/// <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)
/// <summary>
/// Returns a base64 encoded version of the configuration
/// </summary>
@@ -199,6 +208,14 @@ namespace Unity.Netcode
private ulong? m_ConfigHash = null;
/// <summary>
/// Clears out the configuration hash value generated for a specific network session
/// </summary>
internal void ClearConfigHash()
{
m_ConfigHash = null;
}
/// <summary>
/// Gets a SHA256 hash of parts of the NetworkConfig instance
/// </summary>
@@ -219,7 +236,7 @@ namespace Unity.Netcode
if (ForceSamePrefabs)
{
var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
var sortedDictionary = Prefabs.NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
foreach (var sortedEntry in sortedDictionary)
{
@@ -253,6 +270,75 @@ namespace Unity.Netcode
{
return hash == GetConfig();
}
internal void InitializePrefabs()
{
if (HasOldPrefabList())
{
MigrateOldNetworkPrefabsToNetworkPrefabsList();
}
Prefabs.Initialize();
}
[NonSerialized]
private bool m_DidWarnOldPrefabList = false;
private void WarnOldPrefabList()
{
if (!m_DidWarnOldPrefabList)
{
Debug.LogWarning("Using Legacy Network Prefab List. Consider Migrating.");
m_DidWarnOldPrefabList = true;
}
}
/// <summary>
/// Returns true if the old List&lt;NetworkPrefab&gt; serialized data is present.
/// </summary>
/// <remarks>
/// Internal use only to help migrate projects. <seealso cref="MigrateOldNetworkPrefabsToNetworkPrefabsList"/></remarks>
internal bool HasOldPrefabList()
{
return OldPrefabList?.Count > 0;
}
/// <summary>
/// Migrate the old format List&lt;NetworkPrefab&gt; prefab registration to the new NetworkPrefabsList ScriptableObject.
/// </summary>
/// <remarks>
/// OnAfterDeserialize cannot instantiate new objects (e.g. NetworkPrefabsList SO) since it executes in a thread, so we have to do it later.
/// Since NetworkConfig isn't a Unity.Object it doesn't get an Awake callback, so we have to do this in NetworkManager and expose this API.
/// </remarks>
internal NetworkPrefabsList MigrateOldNetworkPrefabsToNetworkPrefabsList()
{
if (OldPrefabList == null || OldPrefabList.Count == 0)
{
return null;
}
if (Prefabs == null)
{
throw new Exception("Prefabs field is null.");
}
Prefabs.NetworkPrefabsLists.Add(ScriptableObject.CreateInstance<NetworkPrefabsList>());
if (OldPrefabList?.Count > 0)
{
// Migrate legacy types/fields
foreach (var networkPrefab in OldPrefabList)
{
Prefabs.NetworkPrefabsLists[Prefabs.NetworkPrefabsLists.Count - 1].Add(networkPrefab);
}
}
OldPrefabList = null;
return Prefabs.NetworkPrefabsLists[Prefabs.NetworkPrefabsLists.Count - 1];
}
[FormerlySerializedAs("NetworkPrefabs")]
[SerializeField]
internal List<NetworkPrefab> OldPrefabList;
}
}

View File

@@ -3,10 +3,23 @@ using UnityEngine;
namespace Unity.Netcode
{
internal enum NetworkPrefabOverride
/// <summary>
/// The method of NetworkPrefab override used to identify the source prefab
/// </summary>
public enum NetworkPrefabOverride
{
/// <summary>
/// No oeverride is present
/// </summary>
None,
/// <summary>
/// Override the prefab when the given SourcePrefabToOverride is requested
/// </summary>
Prefab,
/// <summary>
/// Override the prefab when the given SourceHashToOverride is requested
/// Used in situations where the server assets do not exist in client builds
/// </summary>
Hash
}
@@ -14,10 +27,10 @@ namespace Unity.Netcode
/// Class that represents a NetworkPrefab
/// </summary>
[Serializable]
internal class NetworkPrefab
public class NetworkPrefab
{
/// <summary>
/// The override setttings for this NetworkPrefab
/// The override settings for this NetworkPrefab
/// </summary>
public NetworkPrefabOverride Override;
@@ -41,5 +54,168 @@ namespace Unity.Netcode
/// The prefab to replace (override) the source prefab with
/// </summary>
public GameObject OverridingTargetPrefab;
public bool Equals(NetworkPrefab other)
{
return Override == other.Override &&
Prefab == other.Prefab &&
SourcePrefabToOverride == other.SourcePrefabToOverride &&
SourceHashToOverride == other.SourceHashToOverride &&
OverridingTargetPrefab == other.OverridingTargetPrefab;
}
public uint SourcePrefabGlobalObjectIdHash
{
get
{
switch (Override)
{
case NetworkPrefabOverride.None:
if (Prefab != null && Prefab.TryGetComponent(out NetworkObject no))
{
return no.GlobalObjectIdHash;
}
throw new InvalidOperationException("Prefab field isn't set or isn't a Network Object");
case NetworkPrefabOverride.Prefab:
if (SourcePrefabToOverride != null && SourcePrefabToOverride.TryGetComponent(out no))
{
return no.GlobalObjectIdHash;
}
throw new InvalidOperationException("Source Prefab field isn't set or isn't a Network Object");
case NetworkPrefabOverride.Hash:
return SourceHashToOverride;
default:
throw new ArgumentOutOfRangeException();
}
}
}
public uint TargetPrefabGlobalObjectIdHash
{
get
{
switch (Override)
{
case NetworkPrefabOverride.None:
return 0;
case NetworkPrefabOverride.Prefab:
case NetworkPrefabOverride.Hash:
if (OverridingTargetPrefab != null && OverridingTargetPrefab.TryGetComponent(out NetworkObject no))
{
return no.GlobalObjectIdHash;
}
throw new InvalidOperationException("Target Prefab field isn't set or isn't a Network Object");
default:
throw new ArgumentOutOfRangeException();
}
}
}
public bool Validate(int index = -1)
{
NetworkObject networkObject;
if (Override == NetworkPrefabOverride.None)
{
if (Prefab == null)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {index})");
return false;
}
networkObject = Prefab.GetComponent<NetworkObject>();
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{NetworkManager.PrefabDebugHelper(this)} is missing " +
$"a {nameof(NetworkObject)} component (entry will be ignored).");
}
return false;
}
return true;
}
// Validate source prefab override values first
switch (Override)
{
case NetworkPrefabOverride.Hash:
{
if (SourceHashToOverride == 0)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(SourceHashToOverride)} is zero " +
"(entry will be ignored).");
}
return false;
}
break;
}
case NetworkPrefabOverride.Prefab:
{
if (SourcePrefabToOverride == null)
{
// This is a leftover side-effect from NetworkManager's OnValidate. It's a usability
// adjustment to automatically set the "Prefab" field as the source prefab when a user
// swaps from the default Inspector to the override one.
if (Prefab != null)
{
SourcePrefabToOverride = Prefab;
}
else if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(SourcePrefabToOverride)} is null (entry will be ignored).");
return false;
}
}
if (!SourcePrefabToOverride.TryGetComponent(out networkObject))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({SourcePrefabToOverride.name}) " +
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
}
return false;
}
break;
}
}
// Validate target prefab override values next
if (OverridingTargetPrefab == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(OverridingTargetPrefab)} is null!");
}
switch (Override)
{
case NetworkPrefabOverride.Hash:
{
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {SourceHashToOverride} will be removed and ignored.");
break;
}
case NetworkPrefabOverride.Prefab:
{
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({SourcePrefabToOverride.name}) will be removed and ignored.");
break;
}
}
return false;
}
return true;
}
public override string ToString()
{
return $"{{SourceHash: {SourceHashToOverride}, TargetHash: {TargetPrefabGlobalObjectIdHash}}}";
}
}
}

View File

@@ -0,0 +1,308 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// A class that represents the runtime aspect of network prefabs.
/// This class contains processed prefabs from the NetworkPrefabsList, as
/// well as additional modifications (additions and removals) made at runtime.
/// </summary>
[Serializable]
public class NetworkPrefabs
{
/// <summary>
/// Edit-time scripted object containing a list of NetworkPrefabs.
/// </summary>
/// <remarks>
/// This field can be null if no prefabs are pre-configured.
/// Runtime usages of <see cref="NetworkPrefabs"/> should not depend on this edit-time field for execution.
/// </remarks>
[SerializeField]
public List<NetworkPrefabsList> NetworkPrefabsLists = new List<NetworkPrefabsList>();
/// <summary>
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
/// Generated at runtime and OnValidate
/// </summary>
[NonSerialized]
public Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
[NonSerialized]
public Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
public IReadOnlyList<NetworkPrefab> Prefabs => m_Prefabs;
[NonSerialized]
private List<NetworkPrefab> m_Prefabs = new List<NetworkPrefab>();
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
{
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
}
}
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
{
m_Prefabs.Remove(networkPrefab);
}
~NetworkPrefabs()
{
Shutdown();
}
/// <summary>
/// Deregister from add and remove events
/// Clear the list
/// </summary>
internal void Shutdown()
{
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd -= AddTriggeredByNetworkPrefabList;
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
}
NetworkPrefabsLists.Clear();
}
/// <summary>
/// Processes the <see cref="NetworkPrefabsList"/> if one is present for use during runtime execution,
/// else processes <see cref="Prefabs"/>.
/// </summary>
public void Initialize(bool warnInvalid = true)
{
if (NetworkPrefabsLists.Count != 0 && m_Prefabs.Count > 0)
{
NetworkLog.LogWarning("Runtime Network Prefabs was not empty at initialization time. Network " +
"Prefab registrations made before initialization will be replaced by NetworkPrefabsList.");
m_Prefabs.Clear();
}
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd += AddTriggeredByNetworkPrefabList;
list.OnRemove += RemoveTriggeredByNetworkPrefabList;
}
NetworkPrefabOverrideLinks.Clear();
OverrideToNetworkPrefab.Clear();
var prefabs = NetworkPrefabsLists.Count != 0 ? new List<NetworkPrefab>() : m_Prefabs;
if (NetworkPrefabsLists.Count != 0)
{
foreach (var list in NetworkPrefabsLists)
{
foreach (var networkPrefab in list.PrefabList)
{
prefabs.Add(networkPrefab);
}
}
}
m_Prefabs = new List<NetworkPrefab>();
List<NetworkPrefab> removeList = null;
if (warnInvalid)
{
removeList = new List<NetworkPrefab>();
}
foreach (var networkPrefab in prefabs)
{
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
}
else
{
removeList?.Add(networkPrefab);
}
}
// Clear out anything that is invalid or not used
if (removeList?.Count > 0)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
var sb = new StringBuilder("Removing invalid prefabs from Network Prefab registration: ");
sb.Append(string.Join(", ", removeList));
NetworkLog.LogWarning(sb.ToString());
}
}
}
/// <summary>
/// Add a new NetworkPrefab instance to the list
/// </summary>
/// <remarks>
/// The framework does not synchronize this list between clients. Any runtime changes must be handled manually.
///
/// Any modifications made here are not persisted. Permanent configuration changes should be done
/// through the <see cref="NetworkPrefabsList"/> scriptable object property.
/// </remarks>
public bool Add(NetworkPrefab networkPrefab)
{
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
return true;
}
return false;
}
/// <summary>
/// Remove a NetworkPrefab instance from the list
/// </summary>
/// <remarks>
/// The framework does not synchronize this list between clients. Any runtime changes must be handled manually.
///
/// Any modifications made here are not persisted. Permanent configuration changes should be done
/// through the <see cref="NetworkPrefabsList"/> scriptable object property.
/// </remarks>
public void Remove(NetworkPrefab prefab)
{
if (prefab == null)
{
throw new ArgumentNullException(nameof(prefab));
}
m_Prefabs.Remove(prefab);
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
}
/// <summary>
/// Remove a NetworkPrefab instance with matching <see cref="NetworkPrefab.Prefab"/> from the list
/// </summary>
/// <remarks>
/// The framework does not synchronize this list between clients. Any runtime changes must be handled manually.
///
/// Any modifications made here are not persisted. Permanent configuration changes should be done
/// through the <see cref="NetworkPrefabsList"/> scriptable object property.
/// </remarks>
public void Remove(GameObject prefab)
{
if (prefab == null)
{
throw new ArgumentNullException(nameof(prefab));
}
for (int i = 0; i < m_Prefabs.Count; i++)
{
if (m_Prefabs[i].Prefab == prefab)
{
Remove(m_Prefabs[i]);
return;
}
}
}
/// <summary>
/// Check if the given GameObject is present as a prefab within the list
/// </summary>
/// <param name="prefab">The prefab to check</param>
/// <returns>Whether or not the prefab exists</returns>
public bool Contains(GameObject prefab)
{
for (int i = 0; i < m_Prefabs.Count; i++)
{
if (m_Prefabs[i].Prefab == prefab)
{
return true;
}
}
return false;
}
/// <summary>
/// Check if the given NetworkPrefab is present within the list
/// </summary>
/// <param name="prefab">The prefab to check</param>
/// <returns>Whether or not the prefab exists</returns>
public bool Contains(NetworkPrefab prefab)
{
for (int i = 0; i < m_Prefabs.Count; i++)
{
if (m_Prefabs[i].Equals(prefab))
{
return true;
}
}
return false;
}
/// <summary>
/// Configures <see cref="NetworkPrefabOverrideLinks"/> and <see cref="OverrideToNetworkPrefab"/> for the given <see cref="NetworkPrefab"/>
/// </summary>
private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
{
if (networkPrefab == null)
{
return false;
}
// Safeguard validation check since this method is called from outside of NetworkConfig and we can't control what's passed in.
if (!networkPrefab.Validate())
{
return false;
}
uint source = networkPrefab.SourcePrefabGlobalObjectIdHash;
uint target = networkPrefab.TargetPrefabGlobalObjectIdHash;
// Make sure the prefab isn't already registered.
if (NetworkPrefabOverrideLinks.ContainsKey(source))
{
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
return false;
}
// If we don't have an override configured, registration is simple!
if (networkPrefab.Override == NetworkPrefabOverride.None)
{
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
return true;
}
// Make sure we don't have several overrides targeting the same prefab. Apparently we don't support that... shame.
if (OverrideToNetworkPrefab.ContainsKey(target))
{
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {target}!");
return false;
}
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Prefab:
{
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
OverrideToNetworkPrefab.Add(target, source);
}
break;
case NetworkPrefabOverride.Hash:
{
NetworkPrefabOverrideLinks.Add(source, networkPrefab);
OverrideToNetworkPrefab.Add(target, source);
}
break;
}
return true;
}
}
}

View File

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

View File

@@ -0,0 +1,95 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Netcode
{
/// <summary>
/// A ScriptableObject for holding a network prefabs list, which can be
/// shared between multiple NetworkManagers.
///
/// When NetworkManagers hold references to this list, modifications to the
/// list at runtime will be picked up by all NetworkManagers that reference it.
/// </summary>
[CreateAssetMenu(fileName = "NetworkPrefabsList", menuName = "Netcode/Network Prefabs List")]
public class NetworkPrefabsList : ScriptableObject
{
internal delegate void OnAddDelegate(NetworkPrefab prefab);
internal OnAddDelegate OnAdd;
internal delegate void OnRemoveDelegate(NetworkPrefab prefab);
internal OnRemoveDelegate OnRemove;
[SerializeField]
internal bool IsDefault;
[FormerlySerializedAs("Prefabs")]
[SerializeField]
internal List<NetworkPrefab> List = new List<NetworkPrefab>();
/// <summary>
/// Read-only view into the prefabs list, enabling iterating and examining the list.
/// Actually modifying the list should be done using <see cref="Add"/>
/// and <see cref="Remove"/>.
/// </summary>
public IReadOnlyList<NetworkPrefab> PrefabList => List;
/// <summary>
/// Adds a prefab to the prefab list. Performing this here will apply the operation to all
/// <see cref="NetworkManager"/>s that reference this list.
/// </summary>
/// <param name="prefab"></param>
public void Add(NetworkPrefab prefab)
{
List.Add(prefab);
OnAdd?.Invoke(prefab);
}
/// <summary>
/// Removes a prefab from the prefab list. Performing this here will apply the operation to all
/// <see cref="NetworkManager"/>s that reference this list.
/// </summary>
/// <param name="prefab"></param>
public void Remove(NetworkPrefab prefab)
{
List.Remove(prefab);
OnRemove?.Invoke(prefab);
}
/// <summary>
/// Check if the given GameObject is present as a prefab within the list
/// </summary>
/// <param name="prefab">The prefab to check</param>
/// <returns>Whether or not the prefab exists</returns>
public bool Contains(GameObject prefab)
{
for (int i = 0; i < List.Count; i++)
{
if (List[i].Prefab == prefab)
{
return true;
}
}
return false;
}
/// <summary>
/// Check if the given NetworkPrefab is present within the list
/// </summary>
/// <param name="prefab">The prefab to check</param>
/// <returns>Whether or not the prefab exists</returns>
public bool Contains(NetworkPrefab prefab)
{
for (int i = 0; i < List.Count; i++)
{
if (List[i].Equals(prefab))
{
return true;
}
}
return false;
}
}
}

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Reflection;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
@@ -21,6 +21,7 @@ namespace Unity.Netcode
Client = 2
}
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
@@ -81,7 +82,7 @@ namespace Unity.Netcode
var context = new NetworkContext
{
SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup,
Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
@@ -218,7 +219,7 @@ namespace Unity.Netcode
var context = new NetworkContext
{
SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup,
Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
@@ -235,14 +236,42 @@ namespace Unity.Netcode
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{
foreach (var client in NetworkManager.ConnectedClients)
if (clientRpcParams.Send.TargetClientIds != null)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
client.Key,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
targetClientId,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
{
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
{
NetworkManager.NetworkMetrics.TrackRpcSent(
targetClientId,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
else
{
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
while (observerEnumerator.MoveNext())
{
NetworkManager.NetworkMetrics.TrackRpcSent(
observerEnumerator.Current,
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcWriteSize);
}
}
}
#endif
@@ -258,7 +287,18 @@ namespace Unity.Netcode
/// Gets the NetworkManager that owns this NetworkBehaviour instance
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
/// </summary>
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
public NetworkManager NetworkManager
{
get
{
if (NetworkObject?.NetworkManager != null)
{
return NetworkObject?.NetworkManager;
}
return NetworkManager.Singleton;
}
}
/// <summary>
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
@@ -274,18 +314,18 @@ namespace Unity.Netcode
/// <summary>
/// Gets if we are executing as server
/// </summary>
protected bool IsServer { get; private set; }
public bool IsServer { get; private set; }
/// <summary>
/// Gets if we are executing as client
/// </summary>
protected bool IsClient { get; private set; }
public bool IsClient { get; private set; }
/// <summary>
/// Gets if we are executing as Host, I.E Server and Client
/// </summary>
protected bool IsHost { get; private set; }
public bool IsHost { get; private set; }
/// <summary>
/// Gets Whether or not the object has a owner
@@ -307,23 +347,29 @@ namespace Unity.Netcode
m_NetworkObject.NetworkManager.IsServer;
}
/// <summary>
/// Gets the NetworkObject that owns this NetworkBehaviour instance
/// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
/// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
/// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
/// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
/// how NetworkObject works but it was close to the release and too risky to change
///
/// <summary>
/// Gets the NetworkObject that owns this NetworkBehaviour instance
/// </summary>
public NetworkObject NetworkObject
{
get
{
if (m_NetworkObject == null)
try
{
m_NetworkObject = GetComponentInParent<NetworkObject>();
if (m_NetworkObject == null)
{
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
}
catch (Exception)
{
return null;
}
// ShutdownInProgress check:
@@ -331,7 +377,8 @@ namespace Unity.Netcode
// 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
// 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)
{
@@ -435,14 +482,41 @@ namespace Unity.Netcode
IsSpawned = true;
InitializeVariables();
UpdateNetworkProperties();
OnNetworkSpawn();
}
internal void VisibleOnNetworkSpawn()
{
try
{
OnNetworkSpawn();
}
catch (Exception e)
{
Debug.LogException(e);
}
InitializeVariables();
if (IsServer)
{
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
// NetworkList, we need to mark the object as free of updates.
// This should happen for all objects on the machine triggering the spawn.
PostNetworkVariableWrite(true);
}
}
internal void InternalOnNetworkDespawn()
{
IsSpawned = false;
UpdateNetworkProperties();
OnNetworkDespawn();
try
{
OnNetworkDespawn();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
/// <summary>
@@ -470,6 +544,7 @@ namespace Unity.Netcode
/// <summary>
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
/// </summary>
/// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param>
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
private bool m_VarInit = false;
@@ -495,13 +570,10 @@ namespace Unity.Netcode
if (list == null)
{
list = new List<FieldInfo>();
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
}
else
{
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
}
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
{
return GetFieldInfoForTypeRecursive(type.BaseType, list);
@@ -525,13 +597,7 @@ namespace Unity.Netcode
var fieldType = sortedFields[i].FieldType;
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
{
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
if (instance == null)
{
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
}
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this) ?? throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
instance.Initialize(this);
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
@@ -574,16 +640,30 @@ namespace Unity.Netcode
NetworkVariableIndexesToResetSet.Clear();
}
internal void PostNetworkVariableWrite()
internal void PostNetworkVariableWrite(bool forced = false)
{
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
if (forced)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
// Mark every variable as no longer dirty. We just spawned the object and whatever the game code did
// during OnNetworkSpawn has been sent and needs to be cleared
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].ResetDirty();
}
}
else
{
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
}
}
MarkVariablesDirty(false);
}
internal void VariableUpdate(ulong targetClientId)
internal void PreVariableUpdate()
{
if (!m_VarInit)
{
@@ -591,6 +671,10 @@ namespace Unity.Netcode
}
PreNetworkVariableWrite();
}
internal void VariableUpdate(ulong targetClientId)
{
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
}
@@ -637,7 +721,7 @@ namespace Unity.Netcode
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
using (tmpWriter)
{
message.Serialize(tmpWriter);
message.Serialize(tmpWriter, message.Version);
}
}
else
@@ -662,14 +746,22 @@ namespace Unity.Netcode
return false;
}
internal void MarkVariablesDirty()
internal void MarkVariablesDirty(bool dirty)
{
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)
{
if (NetworkVariableFields.Count == 0)
@@ -679,27 +771,47 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
if (canClientRead)
if (NetworkVariableFields[j].CanClientRead(targetClientId))
{
var writePos = writer.Position;
writer.WriteValueSafe((ushort)0);
var startPos = writer.Position;
NetworkVariableFields[j].WriteField(writer);
var size = writer.Position - startPos;
writer.Seek(writePos);
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
var writePos = writer.Position;
// Note: This value can't be packed because we don't know how large it will be in advance
// we reserve space for it, then write the data, then come back and fill in the space
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
writer.WriteValueSafe((ushort)0);
var startPos = writer.Position;
NetworkVariableFields[j].WriteField(writer);
var size = writer.Position - startPos;
writer.Seek(writePos);
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
}
else
{
NetworkVariableFields[j].WriteField(writer);
}
}
else
else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
writer.WriteValueSafe((ushort)0);
}
}
}
internal void SetNetworkVariableData(FastBufferReader reader)
/// <summary>
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
/// </summary>
/// <remarks>
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
/// by the number of bytes written for that specific field.
/// </remarks>
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
{
if (NetworkVariableFields.Count == 0)
{
@@ -708,13 +820,23 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
reader.ReadValueSafe(out ushort varSize);
if (varSize == 0)
var varSize = (ushort)0;
var readStartPos = 0;
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
reader.ReadValueSafe(out varSize);
if (varSize == 0)
{
continue;
}
readStartPos = reader.Position;
}
else // If the client cannot read this field, then skip it
if (!NetworkVariableFields[j].CanClientRead(clientId))
{
continue;
}
var readStartPos = reader.Position;
NetworkVariableFields[j].ReadField(reader);
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
@@ -751,8 +873,171 @@ namespace Unity.Netcode
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
}
/// <summary>
/// Override this method if your derived NetworkBehaviour requires custom synchronization data.
/// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours
/// and will increase the payload size for client synchronization and dynamically spawned
/// <see cref="NetworkObject"/>s.
/// </summary>
/// <remarks>
/// When serializing (writing) this will be invoked during the client synchronization period and
/// when spawning new NetworkObjects.
/// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated
/// NetworkObject being spawned.
/// </remarks>
/// <param name="serializer">The serializer to use to read and write the data.</param>
/// <typeparam name="T">
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
/// is in read mode or write mode.
/// </typeparam>
/// <param name="targetClientId">the relative client identifier being synchronized</param>
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
}
/// <summary>
/// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance.
/// </summary>
/// <remarks>
/// This value will be set prior to <see cref="OnSynchronize{T}(ref BufferSerializer{T})"/> being invoked.
/// For writing (server-side), this is useful to know which client will receive the serialized data.
/// For reading (client-side), this will be the <see cref="NetworkManager.LocalClientId"/>.
/// When synchronization of this instance is complete, this value will be reset to 0
/// </remarks>
protected ulong m_TargetIdBeingSynchronized { get; private set; }
/// <summary>
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
/// </summary>
/// <remarks>
/// This includes try-catch blocks to recover from exceptions that might occur and continue to
/// synchronize any remaining NetworkBehaviours.
/// </remarks>
/// <returns>true if it wrote synchronization data and false if it did not</returns>
internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetClientId = 0) where T : IReaderWriter
{
m_TargetIdBeingSynchronized = targetClientId;
if (serializer.IsWriter)
{
// Get the writer to handle seeking and determining how many bytes were written
var writer = serializer.GetFastBufferWriter();
// Save our position before we attempt to write anything so we can seek back to it (i.e. error occurs)
var positionBeforeWrite = writer.Position;
writer.WriteValueSafe(NetworkBehaviourId);
// Save our position where we will write the final size being written so we can skip over it in the
// event an exception occurs when deserializing.
var sizePosition = writer.Position;
writer.WriteValueSafe((ushort)0);
// Save our position before synchronizing to determine how much was written
var positionBeforeSynchronize = writer.Position;
var threwException = false;
try
{
OnSynchronize(ref serializer);
}
catch (Exception ex)
{
threwException = true;
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} threw an exception during synchronization serialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
}
}
}
var finalPosition = writer.Position;
// Reset before exiting
m_TargetIdBeingSynchronized = default;
// If we wrote nothing then skip writing anything for this NetworkBehaviour
if (finalPosition == positionBeforeSynchronize || threwException)
{
writer.Seek(positionBeforeWrite);
return false;
}
else
{
// Write the number of bytes serialized to handle exceptions on the deserialization side
var bytesWritten = finalPosition - positionBeforeSynchronize;
writer.Seek(sizePosition);
writer.WriteValueSafe((ushort)bytesWritten);
writer.Seek(finalPosition);
}
return true;
}
else
{
var reader = serializer.GetFastBufferReader();
// We will always read the expected byte count
reader.ReadValueSafe(out ushort expectedBytesToRead);
// Save our position before we begin synchronization deserialization
var positionBeforeSynchronize = reader.Position;
var synchronizationError = false;
try
{
// Invoke synchronization
OnSynchronize(ref serializer);
}
catch (Exception ex)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} threw an exception during synchronization deserialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
}
}
synchronizationError = true;
}
var totalBytesRead = reader.Position - positionBeforeSynchronize;
if (totalBytesRead != expectedBytesToRead)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
}
synchronizationError = true;
}
// Reset before exiting
m_TargetIdBeingSynchronized = default;
// Skip over the entry if deserialization fails
if (synchronizationError)
{
var skipToPosition = positionBeforeSynchronize + expectedBytesToRead;
reader.Seek(skipToPosition);
return false;
}
return true;
}
}
/// <summary>
/// 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()
{
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
// here without having called InitializedVariables, which causes problems if any
// of those variables use native containers (e.g. NetworkList) as they won't be
@@ -764,6 +1049,7 @@ namespace Unity.Netcode
InitializeVariables();
}
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].Dispose();

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,25 +7,55 @@ using UnityEngine.PlayerLoop;
namespace Unity.Netcode
{
/// <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>
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);
}
/// <summary>
/// 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>
public enum NetworkUpdateStage : byte
{
Unset = 0, // Default
/// <summary>
/// Default value
/// </summary>
Unset = 0,
/// <summary>
/// Very first initialization update
/// </summary>
Initialization = 1,
/// <summary>
/// Invoked before Fixed update
/// </summary>
EarlyUpdate = 2,
/// <summary>
/// Fixed Update (i.e. state machine, physics, animations, etc)
/// </summary>
FixedUpdate = 3,
/// <summary>
/// Updated before the Monobehaviour.Update for all components is invoked
/// </summary>
PreUpdate = 4,
/// <summary>
/// Updated when the Monobehaviour.Update for all components is invoked
/// </summary>
Update = 5,
/// <summary>
/// Updated before the Monobehaviour.LateUpdate for all components is invoked
/// </summary>
PreLateUpdate = 6,
/// <summary>
/// Updated after the Monobehaviour.LateUpdate for all components is invoked
/// </summary>
PostLateUpdate = 7
}
@@ -53,6 +83,7 @@ namespace Unity.Netcode
/// <summary>
/// Registers a network update system to be executed in all network update stages.
/// </summary>
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to register for all network updates</param>
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
{
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
@@ -64,6 +95,8 @@ namespace Unity.Netcode
/// <summary>
/// Registers a network update system to be executed in a specific network update stage.
/// </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)
{
var sysSet = s_UpdateSystem_Sets[updateStage];
@@ -94,6 +127,7 @@ namespace Unity.Netcode
/// <summary>
/// Unregisters a network update system from all network update stages.
/// </summary>
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to deregister from all network updates</param>
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
{
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
@@ -105,6 +139,8 @@ namespace Unity.Netcode
/// <summary>
/// Unregisters a network update system from a specific network update stage.
/// </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)
{
var sysSet = s_UpdateSystem_Sets[updateStage];
@@ -131,7 +167,7 @@ namespace Unity.Netcode
/// </summary>
public static NetworkUpdateStage UpdateStage;
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
internal static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
{
UpdateStage = updateStage;

View File

@@ -7,8 +7,18 @@ namespace Unity.Netcode
/// </summary>
public class InvalidParentException : Exception
{
/// <summary>
/// Constructor for <see cref="InvalidParentException"/>
/// </summary>
public InvalidParentException() { }
/// <inheritdoc/>
/// <param name="message"></param>
public InvalidParentException(string message) : base(message) { }
/// <inheritdoc/>
/// <param name="message"></param>
/// <param name="innerException"></param>
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) { }
}
/// <summary>
/// Exception thrown when a specified network channel is invalid
/// </summary>
public class InvalidChannelException : Exception
{
/// <summary>
/// Constructs an InvalidChannelException with a message
/// </summary>
/// <param name="message">the message</param>
public InvalidChannelException(string message) : base(message) { }
}
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,8 +14,23 @@ namespace Unity.Netcode
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
// 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}");
/// <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}");
/// <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}");
/// <summary>

View File

@@ -5,9 +5,26 @@ namespace Unity.Netcode
/// </summary>
internal struct BatchHeader : INetworkSerializeByMemcpy
{
internal const ushort MagicValue = 0x1160;
/// <summary>
/// A magic number to detect corrupt messages.
/// Always set to k_MagicValue
/// </summary>
public ushort Magic;
/// <summary>
/// Total number of bytes in the batch.
/// </summary>
public int BatchSize;
/// <summary>
/// Hash of the message to detect corrupt messages.
/// </summary>
public ulong BatchHash;
/// <summary>
/// Total number of messages in the batch.
/// </summary>
public ushort BatchSize;
public ushort BatchCount;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
@@ -68,9 +69,23 @@ namespace Unity.Netcode
if (clientIds == null)
{
throw new ArgumentNullException("You must pass in a valid clientId List");
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
}
if (m_NetworkManager.IsHost)
{
for (var i = 0; i < clientIds.Count; ++i)
{
if (clientIds[i] == m_NetworkManager.LocalClientId)
{
InvokeUnnamedMessage(
m_NetworkManager.LocalClientId,
new FastBufferReader(messageBuffer, Allocator.None),
0
);
}
}
}
var message = new UnnamedMessage
{
SendData = messageBuffer
@@ -92,6 +107,18 @@ namespace Unity.Netcode
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
if (m_NetworkManager.IsHost)
{
if (clientId == m_NetworkManager.LocalClientId)
{
InvokeUnnamedMessage(
m_NetworkManager.LocalClientId,
new FastBufferReader(messageBuffer, Allocator.None),
0
);
return;
}
}
var message = new UnnamedMessage
{
SendData = messageBuffer
@@ -124,14 +151,18 @@ namespace Unity.Netcode
// We dont know what size to use. Try every (more collision prone)
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup32[hash];
messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup64[hash];
messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
}
else
@@ -142,15 +173,19 @@ namespace Unity.Netcode
case HashSize.VarIntFourBytes:
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup32[hash];
messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
break;
case HashSize.VarIntEightBytes:
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup64[hash];
messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
break;
}
@@ -193,6 +228,7 @@ namespace Unity.Netcode
/// <summary>
/// Sends a named message to all clients
/// </summary>
/// <param name="messageName">The message name to send</param>
/// <param name="messageStream">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendNamedMessageToAll(string messageName, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
@@ -219,6 +255,20 @@ namespace Unity.Netcode
hash = XXHash.Hash64(messageName);
break;
}
if (m_NetworkManager.IsHost)
{
if (clientId == m_NetworkManager.LocalClientId)
{
InvokeNamedMessage(
hash,
m_NetworkManager.LocalClientId,
new FastBufferReader(messageStream, Allocator.None),
0
);
return;
}
}
var message = new NamedMessage
{
@@ -238,7 +288,7 @@ namespace Unity.Netcode
/// Sends the named message
/// </summary>
/// <param name="messageName">The message name to send</param>
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
/// <param name="clientIds">The clients to send to</param>
/// <param name="messageStream">The message stream containing the data</param>
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
@@ -250,7 +300,7 @@ namespace Unity.Netcode
if (clientIds == null)
{
throw new ArgumentNullException("You must pass in a valid clientId List");
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
}
ulong hash = 0;
@@ -263,6 +313,21 @@ namespace Unity.Netcode
hash = XXHash.Hash64(messageName);
break;
}
if (m_NetworkManager.IsHost)
{
for (var i = 0; i < clientIds.Count; ++i)
{
if (clientIds[i] == m_NetworkManager.LocalClientId)
{
InvokeNamedMessage(
hash,
m_NetworkManager.LocalClientId,
new FastBufferReader(messageStream, Allocator.None),
0
);
}
}
}
var message = new NamedMessage
{
Hash = hash,

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,22 +2,26 @@ namespace Unity.Netcode
{
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public ulong NetworkObjectId;
public ulong OwnerClientId;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(this);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
reader.ReadValueSafe(out this);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
internal struct ConnectionApprovedMessage : INetworkMessage
{
public int Version => 0;
public ulong OwnerClientId;
public int NetworkTick;
@@ -13,14 +15,26 @@ namespace Unity.Netcode
private FastBufferReader m_ReceivedSceneObjectData;
public void Serialize(FastBufferWriter writer)
public NativeArray<MessageVersionData> MessageVersions;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int)))
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
foreach (var messageVersion in MessageVersions)
{
throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
messageVersion.Serialize(writer);
}
writer.WriteValue(OwnerClientId);
writer.WriteValue(NetworkTick);
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
BytePacker.WriteValueBitPacked(writer, NetworkTick);
uint sceneObjectCount = 0;
if (SpawnedObjectsList != null)
@@ -39,17 +53,19 @@ namespace Unity.Netcode
++sceneObjectCount;
}
}
writer.Seek(pos);
writer.WriteValue(sceneObjectCount);
// Can't pack this value because its space is reserved, so it needs to always use all the reserved space.
writer.WriteValueSafe(sceneObjectCount);
writer.Seek(writer.Length);
}
else
{
writer.WriteValue(sceneObjectCount);
writer.WriteValueSafe(sceneObjectCount);
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -57,13 +73,36 @@ namespace Unity.Netcode
return false;
}
if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int)))
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
ByteUnpacker.ReadValueBitPacked(reader, out int length);
var messageHashesInOrder = new NativeArray<uint>(length, Allocator.Temp);
for (var i = 0; i < length; ++i)
{
throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
}
var messageVersion = new MessageVersionData();
messageVersion.Deserialize(reader);
networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
messageHashesInOrder[i] = messageVersion.Hash;
reader.ReadValue(out OwnerClientId);
reader.ReadValue(out NetworkTick);
// Update the received version since this message will always be passed version 0, due to the map not
// being initialized until just now.
var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
if (messageType == typeof(ConnectionApprovedMessage))
{
receivedMessageVersion = messageVersion.Version;
}
}
networkManager.MessagingSystem.SetServerMessageOrder(messageHashesInOrder);
messageHashesInOrder.Dispose();
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
m_ReceivedSceneObjectData = reader;
return true;
}
@@ -79,12 +118,13 @@ namespace Unity.Netcode
networkManager.NetworkTickSystem.Reset(networkManager.NetworkTimeSystem.LocalTime, networkManager.NetworkTimeSystem.ServerTime);
networkManager.LocalClient = new NetworkClient() { ClientId = networkManager.LocalClientId };
networkManager.IsApproved = true;
// Only if scene management is disabled do we handle NetworkObject synchronization at this point
if (!networkManager.NetworkConfig.EnableSceneManagement)
{
networkManager.SpawnManager.DestroySceneObjects();
m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount);
m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount);
// Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing
// to create a list to hold the data. This is a breach of convention for performance reasons.

View File

@@ -1,15 +1,35 @@
using Unity.Collections;
namespace Unity.Netcode
{
internal struct ConnectionRequestMessage : INetworkMessage
{
public int Version => 0;
public ulong ConfigHash;
public byte[] ConnectionData;
public bool ShouldSendConnectionData;
public void Serialize(FastBufferWriter writer)
public NativeArray<MessageVersionData> MessageVersions;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
foreach (var messageVersion in MessageVersions)
{
messageVersion.Serialize(writer);
}
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
if (ShouldSendConnectionData)
{
writer.WriteValueSafe(ConfigHash);
@@ -21,7 +41,7 @@ namespace Unity.Netcode
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsServer)
@@ -29,6 +49,30 @@ namespace Unity.Netcode
return false;
}
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
ByteUnpacker.ReadValueBitPacked(reader, out int length);
for (var i = 0; i < length; ++i)
{
var messageVersion = new MessageVersionData();
messageVersion.Deserialize(reader);
networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
// Update the received version since this message will always be passed version 0, due to the map not
// being initialized until just now.
var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
if (messageType == typeof(ConnectionRequestMessage))
{
receivedMessageVersion = messageVersion.Version;
}
}
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
if (networkManager.NetworkConfig.ConnectionApproval)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>()))
@@ -101,16 +145,24 @@ namespace Unity.Netcode
{
// Note: Delegate creation allocates.
// Note: ToArray() also allocates. :(
networkManager.InvokeConnectionApproval(ConnectionData, senderId,
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
var response = new NetworkManager.ConnectionApprovalResponse();
networkManager.ClientsToApprove[senderId] = response;
networkManager.ConnectionApprovalCallback(
new NetworkManager.ConnectionApprovalRequest
{
var localCreatePlayerObject = createPlayerObject;
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation);
});
Payload = ConnectionData,
ClientNetworkId = senderId
}, response);
}
else
{
networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
var response = new NetworkManager.ConnectionApprovalResponse
{
Approved = true,
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
};
networkManager.HandleConnectionApproval(senderId, response);
}
}
}

View File

@@ -2,15 +2,17 @@ namespace Unity.Netcode
{
internal struct CreateObjectMessage : INetworkMessage
{
public int Version => 0;
public NetworkObject.SceneObject ObjectInfo;
private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
ObjectInfo.Serialize(writer);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -19,9 +21,9 @@ namespace Unity.Netcode
}
ObjectInfo.Deserialize(reader);
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo.Header.IsSceneObject, ObjectInfo.Header.Hash))
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context);
return false;
}
m_ReceivedNetworkVariableData = reader;

View File

@@ -2,14 +2,18 @@ namespace Unity.Netcode
{
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public ulong NetworkObjectId;
public int Version => 0;
public void Serialize(FastBufferWriter writer)
public ulong NetworkObjectId;
public bool DestroyGameObject;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(this);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValueSafe(DestroyGameObject);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -17,7 +21,8 @@ namespace Unity.Netcode
return false;
}
reader.ReadValueSafe(out this);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out DestroyGameObject);
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
@@ -37,7 +42,7 @@ namespace Unity.Netcode
}
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
}
}
}

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
{
public int Version => 0;
public ulong Hash;
public FastBufferWriter SendData;
private FastBufferReader m_ReceiveData;
public unsafe void Serialize(FastBufferWriter writer)
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(Hash);
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
reader.ReadValueSafe(out Hash);
m_ReceiveData = reader;

View File

@@ -12,6 +12,8 @@ namespace Unity.Netcode
/// </summary>
internal struct NetworkVariableDeltaMessage : INetworkMessage
{
public int Version => 0;
public ulong NetworkObjectId;
public ushort NetworkBehaviourIndex;
@@ -21,15 +23,15 @@ namespace Unity.Netcode
private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
writer.WriteValue(NetworkObjectId);
writer.WriteValue(NetworkBehaviourIndex);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
{
@@ -38,7 +40,7 @@ namespace Unity.Netcode
// This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
writer.WriteValueSafe((ushort)0);
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
else
{
@@ -62,11 +64,21 @@ namespace Unity.Netcode
shouldWrite = false;
}
// The object containing the behaviour we're about to process is about to be shown to this client
// As a result, the client will get the fully serialized NetworkVariable and would be confused by
// an extraneous delta
if (NetworkBehaviour.NetworkManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
NetworkBehaviour.NetworkManager.ObjectsToShowToClient[TargetClientId]
.Contains(NetworkBehaviour.NetworkObject))
{
shouldWrite = false;
}
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (!shouldWrite)
{
BytePacker.WriteValueBitPacked(writer, 0);
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
}
else
@@ -94,12 +106,6 @@ namespace Unity.Netcode
networkVariable.WriteDelta(writer);
}
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
{
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
}
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
TargetClientId,
NetworkBehaviour.NetworkObject,
@@ -110,15 +116,10 @@ namespace Unity.Netcode
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
}
reader.ReadValue(out NetworkObjectId);
reader.ReadValue(out NetworkBehaviourIndex);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
m_ReceivedNetworkVariableData = reader;

View File

@@ -1,32 +1,65 @@
using UnityEngine;
namespace Unity.Netcode
{
internal struct ParentSyncMessage : INetworkMessage
{
public int Version => 0;
public ulong NetworkObjectId;
public bool IsReparented;
private byte m_BitField;
public bool WorldPositionStays
{
get => ByteUtility.GetBit(m_BitField, 0);
set => ByteUtility.SetBit(ref m_BitField, 0, value);
}
//If(Metadata.IsReparented)
public bool IsLatestParentSet;
public bool IsLatestParentSet
{
get => ByteUtility.GetBit(m_BitField, 1);
set => ByteUtility.SetBit(ref m_BitField, 1, value);
}
//If(IsLatestParentSet)
public ulong? LatestParent;
public void Serialize(FastBufferWriter writer)
// Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
public bool RemoveParent
{
writer.WriteValueSafe(NetworkObjectId);
writer.WriteValueSafe(IsReparented);
if (IsReparented)
{
writer.WriteValueSafe(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValueSafe((ulong)LatestParent);
}
}
get => ByteUtility.GetBit(m_BitField, 2);
set => ByteUtility.SetBit(ref m_BitField, 2, value);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
// These additional properties are used to synchronize clients with the current position,
// rotation, and scale after parenting/de-parenting (world/local space relative). This
// allows users to control the final child's transform values without having to have a
// NetworkTransform component on the child. (i.e. picking something up)
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValueSafe(m_BitField);
if (!RemoveParent)
{
if (IsLatestParentSet)
{
BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
}
}
// Whether parenting or removing a parent, we always update the position, rotation, and scale
writer.WriteValueSafe(Position);
writer.WriteValueSafe(Rotation);
writer.WriteValueSafe(Scale);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -34,24 +67,27 @@ namespace Unity.Netcode
return false;
}
reader.ReadValueSafe(out NetworkObjectId);
reader.ReadValueSafe(out IsReparented);
if (IsReparented)
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out m_BitField);
if (!RemoveParent)
{
reader.ReadValueSafe(out IsLatestParentSet);
if (IsLatestParentSet)
{
reader.ReadValueSafe(out ulong latestParent);
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
LatestParent = latestParent;
}
}
// Whether parenting or removing a parent, we always update the position, rotation, and scale
reader.ReadValueSafe(out Position);
reader.ReadValueSafe(out Rotation);
reader.ReadValueSafe(out Scale);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
return false;
}
return true;
}
@@ -59,8 +95,22 @@ namespace Unity.Netcode
{
var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
networkObject.SetNetworkParenting(IsReparented, LatestParent);
networkObject.ApplyNetworkParenting();
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
networkObject.ApplyNetworkParenting(RemoveParent);
// We set all of the transform values after parenting as they are
// the values of the server-side post-parenting transform values
if (!WorldPositionStays)
{
networkObject.transform.localPosition = Position;
networkObject.transform.localRotation = Rotation;
}
else
{
networkObject.transform.position = Position;
networkObject.transform.rotation = Rotation;
}
networkObject.transform.localScale = Scale;
}
}
}

View File

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

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.
internal struct SceneEventMessage : INetworkMessage
{
public int Version => 0;
public SceneEventData EventData;
private FastBufferReader m_ReceivedData;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
EventData.Serialize(writer);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
m_ReceivedData = reader;
return true;

View File

@@ -2,6 +2,8 @@ namespace Unity.Netcode
{
internal struct ServerLogMessage : INetworkMessage
{
public int Version => 0;
public NetworkLog.LogType LogType;
// It'd be lovely to be able to replace this with FixedString or NativeArray...
// But it's not really practical. On the sending side, the user is likely to want
@@ -11,13 +13,13 @@ namespace Unity.Netcode
public string Message;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(LogType);
BytePacker.WriteValuePacked(writer, Message);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)

View File

@@ -2,21 +2,23 @@ namespace Unity.Netcode
{
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public int Tick;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(this);
BytePacker.WriteValueBitPacked(writer, Tick);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
reader.ReadValueSafe(out this);
ByteUnpacker.ReadValueBitPacked(reader, out Tick);
return true;
}

View File

@@ -2,15 +2,17 @@ namespace Unity.Netcode
{
internal struct UnnamedMessage : INetworkMessage
{
public int Version => 0;
public FastBufferWriter SendData;
private FastBufferReader m_ReceivedData;
public unsafe void Serialize(FastBufferWriter writer)
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
m_ReceivedData = reader;
return true;

View File

@@ -2,12 +2,18 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace Unity.Netcode
{
internal class HandlerNotRegisteredException : SystemException
{
public HandlerNotRegisteredException() { }
public HandlerNotRegisteredException(string issue) : base(issue) { }
}
internal class InvalidMessageStructureException : SystemException
{
@@ -36,20 +42,27 @@ namespace Unity.Netcode
{
Writer = new FastBufferWriter(writerSize, writerAllocator, maxWriterSize);
NetworkDelivery = delivery;
BatchHeader = default;
BatchHeader = new BatchHeader { Magic = BatchHeader.MagicValue };
}
}
internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system);
internal delegate int VersionGetter();
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
private Type[] m_ReverseTypeMap = new Type[255];
// These array will grow as we need more message handlers. 4 is just a starting size.
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<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
// This is m_PerClientMessageVersion[clientId][messageType] = version
private Dictionary<ulong, Dictionary<Type, int>> m_PerClientMessageVersions = new Dictionary<ulong, Dictionary<Type, int>>();
private Dictionary<uint, Type> m_MessagesByHash = new Dictionary<uint, Type>();
private Dictionary<Type, int> m_LocalVersions = new Dictionary<Type, int>();
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
private uint m_HighMessageType;
@@ -59,6 +72,7 @@ namespace Unity.Netcode
internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal uint MessageHandlerCount => m_HighMessageType;
internal uint GetMessageType(Type t)
@@ -67,12 +81,40 @@ namespace Unity.Netcode
}
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
internal struct MessageWithHandler
{
public Type MessageType;
public MessageHandler Handler;
public VersionGetter GetVersion;
}
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
{
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)
@@ -89,6 +131,7 @@ namespace Unity.Netcode
var allowedTypes = provider.GetMessages();
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
allowedTypes = PrioritizeMessageOrder(allowedTypes);
foreach (var type in allowedTypes)
{
RegisterMessageType(type);
@@ -143,9 +186,34 @@ namespace Unity.Netcode
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_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
m_MessagesByHash[XXHash.Hash32(messageWithHandler.MessageType.FullName)] = messageWithHandler.MessageType;
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
m_LocalVersions[messageWithHandler.MessageType] = messageWithHandler.GetVersion();
}
public int GetLocalVersion(Type messageType)
{
return m_LocalVersions[messageType];
}
internal static string ByteArrayToString(byte[] ba, int offset, int count)
{
var hex = new StringBuilder(ba.Length * 2);
for (int i = offset; i < offset + count; ++i)
{
hex.AppendFormat("{0:x2} ", ba[i]);
}
return hex.ToString();
}
internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float receiveTime)
@@ -158,18 +226,38 @@ namespace Unity.Netcode
new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
{
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
NetworkLog.LogError("Received a packet too small to contain a BatchHeader. Ignoring it.");
return;
}
batchReader.ReadValue(out BatchHeader batchHeader);
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
if (batchHeader.Magic != BatchHeader.MagicValue)
{
m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length);
NetworkLog.LogError($"Received a packet with an invalid Magic Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
return;
}
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
if (batchHeader.BatchSize != data.Count)
{
NetworkLog.LogError($"Received a packet with an invalid Batch Size Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Offset: {data.Offset}, Size: {data.Count}, Expected Size: {batchHeader.BatchSize}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
return;
}
var hash = XXHash.Hash64(batchReader.GetUnsafePtrAtCurrentPosition(), batchReader.Length - batchReader.Position);
if (hash != batchHeader.BatchHash)
{
NetworkLog.LogError($"Received a packet with an invalid Hash Value. Please report this to the Netcode for GameObjects team at https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues and include the following data: Received Hash: {batchHeader.BatchHash}, Calculated Hash: {hash}, Offset: {data.Offset}, Size: {data.Count}, Full receive array: {ByteArrayToString(data.Array, 0, data.Array.Length)}");
return;
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
}
for (var messageIdx = 0; messageIdx < batchHeader.BatchCount; ++messageIdx)
{
var messageHeader = new MessageHeader();
@@ -181,7 +269,7 @@ namespace Unity.Netcode
}
catch (OverflowException)
{
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
NetworkLog.LogError("Received a batch that didn't have enough data for all of its batches, ending early!");
throw;
}
@@ -189,7 +277,7 @@ namespace Unity.Netcode
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
{
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
NetworkLog.LogError("Received a message that claimed a size larger than the packet, ending early!");
return;
}
m_IncomingMessageQueue.Add(new ReceiveQueueItem
@@ -207,7 +295,7 @@ namespace Unity.Netcode
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchSize, batchReader.Length);
m_Hooks[hookIdx].OnAfterReceiveBatch(clientId, batchHeader.BatchCount, batchReader.Length);
}
}
}
@@ -226,56 +314,114 @@ namespace Unity.Netcode
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)
{
if (header.MessageType >= m_HighMessageType)
{
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
reader.Dispose();
return;
}
var context = new NetworkContext
{
SystemOwner = m_Owner,
SenderId = senderId,
Timestamp = timestamp,
Header = header,
SerializedHeaderSize = serializedHeaderSize,
MessageSize = header.MessageSize,
};
var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type, reader, ref context))
{
reader.Dispose();
return;
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
// No user-land message handler exceptions should escape the receive loop.
// If an exception is throw, the message is ignored.
// Example use case: A bad message is received that can't be deserialized and throws
// an OverflowException because it specifies a length greater than the number of bytes in it
// for some dynamic-length value.
try
if (header.MessageType >= m_HighMessageType)
{
handler.Invoke(reader, ref context, this);
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
return;
}
catch (Exception e)
var context = new NetworkContext
{
Debug.LogException(e);
SystemOwner = m_Owner,
SenderId = senderId,
Timestamp = timestamp,
Header = header,
SerializedHeaderSize = serializedHeaderSize,
MessageSize = header.MessageSize,
};
var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type, reader, ref context))
{
return;
}
var handler = m_MessageHandlers[header.MessageType];
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
// 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.
// If an exception is throw, the message is ignored.
// Example use case: A bad message is received that can't be deserialized and throws
// an OverflowException because it specifies a length greater than the number of bytes in it
// for some dynamic-length value.
try
{
handler.Invoke(reader, ref context, this);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
}
@@ -314,7 +460,7 @@ namespace Unity.Netcode
m_SendQueues.Remove(clientId);
}
private unsafe void CleanupDisconnectedClient(ulong clientId)
private void CleanupDisconnectedClient(ulong clientId)
{
var queue = m_SendQueues[clientId];
for (var i = 0; i < queue.Length; ++i)
@@ -325,10 +471,67 @@ namespace Unity.Netcode
queue.Dispose();
}
internal void CleanupDisconnectedClients()
{
var removeList = new NativeList<ulong>(Allocator.Temp);
foreach (var clientId in m_PerClientMessageVersions.Keys)
{
if (!m_SendQueues.ContainsKey(clientId))
{
removeList.Add(clientId);
}
}
foreach (var clientId in removeList)
{
m_PerClientMessageVersions.Remove(clientId);
}
}
public static int CreateMessageAndGetVersion<T>() where T : INetworkMessage, new()
{
return new T().Version;
}
internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = false)
{
if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap))
{
if (forReceive)
{
Debug.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state.");
}
else
{
Debug.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state.");
}
return -1;
}
if (!versionMap.TryGetValue(type, out var messageVersion))
{
return -1;
}
return messageVersion;
}
public static void ReceiveMessage<T>(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new()
{
var message = new T();
if (message.Deserialize(reader, ref context))
var messageVersion = 0;
// Special cases because these are the messages that carry the version info - thus the version info isn't
// populated yet when we get these. The first part of these messages always has to be the version data
// and can't change.
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage))
{
messageVersion = system.GetMessageVersion(typeof(T), context.SenderId, true);
if (messageVersion < 0)
{
return;
}
}
if (message.Deserialize(reader, ref context, messageVersion))
{
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
{
@@ -366,16 +569,47 @@ namespace Unity.Netcode
return 0;
}
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
var largestSerializedSize = 0;
var sentMessageVersions = new NativeHashSet<int>(clientIds.Count, Allocator.Temp);
for (var i = 0; i < clientIds.Count; ++i)
{
var messageVersion = 0;
// Special case because this is the message that carries the version info - thus the version info isn't
// populated yet when we get this. The first part of this message always has to be the version data
// and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
continue;
}
}
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
if (sentMessageVersions.Contains(messageVersion))
{
continue;
}
message.Serialize(tmpSerializer);
sentMessageVersions.Add(messageVersion);
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds);
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
message.Serialize(tmpSerializer, messageVersion);
var size = SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds, messageVersion);
largestSerializedSize = size > largestSerializedSize ? size : largestSerializedSize;
}
sentMessageVersions.Dispose();
return largestSerializedSize;
}
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds)
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds, int messageVersionFilter)
where TMessageType : INetworkMessage
{
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
@@ -390,6 +624,25 @@ namespace Unity.Netcode
for (var i = 0; i < clientIds.Count; ++i)
{
var messageVersion = 0;
// Special case because this is the message that carries the version info - thus the version info isn't
// populated yet when we get this. The first part of this message always has to be the version data
// and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
continue;
}
if (messageVersion != messageVersionFilter)
{
continue;
}
}
var clientId = clientIds[i];
if (!CanSend(clientId, typeof(TMessageType), delivery))
@@ -427,7 +680,7 @@ namespace Unity.Netcode
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++;
writeQueueItem.BatchHeader.BatchCount++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length);
@@ -440,8 +693,22 @@ namespace Unity.Netcode
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId)
where TMessageType : INetworkMessage
{
var messageVersion = 0;
// Special case because this is the message that carries the version info - thus the version info isn't
// populated yet when we get this. The first part of this message always has to be the version data
// and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientId);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
return 0;
}
}
ulong* clientIds = stackalloc ulong[] { clientId };
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1), messageVersion);
}
private struct PointerListWrapper<T> : IReadOnlyList<T>
@@ -508,7 +775,7 @@ namespace Unity.Netcode
for (var i = 0; i < sendQueueItem.Length; ++i)
{
ref var queueItem = ref sendQueueItem.ElementAt(i);
if (queueItem.BatchHeader.BatchSize == 0)
if (queueItem.BatchHeader.BatchCount == 0)
{
queueItem.Writer.Dispose();
continue;
@@ -516,23 +783,28 @@ namespace Unity.Netcode
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
m_Hooks[hookIdx].OnBeforeSendBatch(clientId, queueItem.BatchHeader.BatchCount, queueItem.Writer.Length, queueItem.NetworkDelivery);
}
queueItem.Writer.Seek(0);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// Skipping the Verify and sneaking the write mark in because we know it's fine.
queueItem.Writer.Handle->AllowedWriteMark = 2;
queueItem.Writer.Handle->AllowedWriteMark = sizeof(BatchHeader);
#endif
queueItem.BatchHeader.BatchHash = XXHash.Hash64(queueItem.Writer.GetUnsafePtr() + sizeof(BatchHeader), queueItem.Writer.Length - sizeof(BatchHeader));
queueItem.BatchHeader.BatchSize = queueItem.Writer.Length;
queueItem.Writer.WriteValue(queueItem.BatchHeader);
try
{
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchCount, queueItem.Writer.Length, queueItem.NetworkDelivery);
}
}
finally

View File

@@ -3,21 +3,52 @@ using Unity.Collections;
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
{
}
/// <summary>
/// The receive parameters for server-side remote procedure calls
/// </summary>
public struct ServerRpcReceiveParams
{
/// <summary>
/// Server-Side RPC
/// The client identifier of the sender
/// </summary>
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
{
/// <summary>
/// The server RPC send parameters (currently a place holder)
/// </summary>
public ServerRpcSendParams Send;
/// <summary>
/// The client RPC receive parameters provides you with the sender's identifier
/// </summary>
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
{
/// <summary>
@@ -34,13 +65,32 @@ namespace Unity.Netcode
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
{
}
/// <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
{
/// <summary>
/// The client RPC send parameters provides you with the ability to send to a specific list of clients
/// </summary>
public ClientRpcSendParams Send;
/// <summary>
/// The client RPC receive parameters (currently a place holder)
/// </summary>
public ClientRpcReceiveParams Receive;
}

View File

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

View File

@@ -4,7 +4,7 @@ using UnityEngine;
namespace Unity.Netcode
{
class NetworkObjectProvider : INetworkObjectProvider
internal class NetworkObjectProvider : INetworkObjectProvider
{
private readonly NetworkManager m_NetworkManager;
@@ -15,7 +15,7 @@ namespace Unity.Netcode
public Object GetNetworkObject(ulong networkObjectId)
{
if(m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
if (m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
{
return value;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
@@ -8,7 +9,7 @@ namespace Unity.Netcode
/// Event based NetworkVariable container for syncing Lists
/// </summary>
/// <typeparam name="T">The type for the list</typeparam>
public class NetworkList<T> : NetworkVariableSerialization<T> 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<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
@@ -24,16 +25,27 @@ namespace Unity.Netcode
/// </summary>
public event OnListChangedDelegate OnListChanged;
/// <summary>
/// Constructor method for <see cref="NetworkList"/>
/// </summary>
public NetworkList() { }
/// <inheritdoc/>
/// <param name="values"></param>
/// <param name="readPerm"></param>
/// <param name="writePerm"></param>
public NetworkList(IEnumerable<T> values = default,
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
{
foreach (var value in values)
// allow null IEnumerable<T> to mean "no values"
if (values != null)
{
m_List.Add(value);
foreach (var value in values)
{
m_List.Add(value);
}
}
}
@@ -41,7 +53,10 @@ namespace Unity.Netcode
public override void ResetDirty()
{
base.ResetDirty();
m_DirtyEvents.Clear();
if (m_DirtyEvents.Length > 0)
{
m_DirtyEvents.Clear();
}
}
/// <inheritdoc />
@@ -51,6 +66,18 @@ namespace Unity.Netcode
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 />
public override void WriteDelta(FastBufferWriter writer)
{
@@ -67,34 +94,35 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
for (int i = 0; i < m_DirtyEvents.Length; i++)
{
writer.WriteValueSafe(m_DirtyEvents[i].Type);
switch (m_DirtyEvents[i].Type)
var element = m_DirtyEvents.ElementAt(i);
writer.WriteValueSafe(element.Type);
switch (element.Type)
{
case NetworkListEvent<T>.EventType.Add:
{
Write(writer, m_DirtyEvents[i].Value);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.Insert:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
Write(writer, m_DirtyEvents[i].Value);
writer.WriteValueSafe(element.Index);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.Remove:
{
Write(writer, m_DirtyEvents[i].Value);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.RemoveAt:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
writer.WriteValueSafe(element.Index);
}
break;
case NetworkListEvent<T>.EventType.Value:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
Write(writer, m_DirtyEvents[i].Value);
writer.WriteValueSafe(element.Index);
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
}
break;
case NetworkListEvent<T>.EventType.Clear:
@@ -112,7 +140,7 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
Write(writer, m_List[i]);
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
}
}
@@ -123,7 +151,8 @@ namespace Unity.Netcode
reader.ReadValueSafe(out ushort count);
for (int i = 0; i < count; i++)
{
Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
m_List.Add(value);
}
}
@@ -139,7 +168,8 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
m_List.Add(value);
if (OnListChanged != null)
@@ -160,15 +190,25 @@ namespace Unity.Netcode
Index = m_List.Length - 1,
Value = m_List[m_List.Length - 1]
});
MarkNetworkObjectDirty();
}
}
break;
case NetworkListEvent<T>.EventType.Insert:
{
reader.ReadValueSafe(out int index);
Read(reader, out T value);
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
if (index < m_List.Length)
{
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
}
else
{
m_List.Add(value);
}
if (OnListChanged != null)
{
@@ -188,12 +228,14 @@ namespace Unity.Netcode
Index = index,
Value = m_List[index]
});
MarkNetworkObjectDirty();
}
}
break;
case NetworkListEvent<T>.EventType.Remove:
{
Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
int index = m_List.IndexOf(value);
if (index == -1)
{
@@ -220,6 +262,7 @@ namespace Unity.Netcode
Index = index,
Value = value
});
MarkNetworkObjectDirty();
}
}
break;
@@ -247,13 +290,15 @@ namespace Unity.Netcode
Index = index,
Value = value
});
MarkNetworkObjectDirty();
}
}
break;
case NetworkListEvent<T>.EventType.Value:
{
reader.ReadValueSafe(out int index);
Read(reader, out T value);
var value = new T();
NetworkVariableSerialization<T>.Read(reader, ref value);
if (index >= m_List.Length)
{
throw new Exception("Shouldn't be here, index is higher than list length");
@@ -282,6 +327,7 @@ namespace Unity.Netcode
Value = value,
PreviousValue = previousValue
});
MarkNetworkObjectDirty();
}
}
break;
@@ -304,6 +350,7 @@ namespace Unity.Netcode
{
Type = eventType
});
MarkNetworkObjectDirty();
}
}
break;
@@ -326,6 +373,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Add(T item)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.Add(item);
var listEvent = new NetworkListEvent<T>()
@@ -341,6 +394,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Clear()
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.Clear();
var listEvent = new NetworkListEvent<T>()
@@ -354,14 +413,20 @@ namespace Unity.Netcode
/// <inheritdoc />
public bool Contains(T item)
{
int index = NativeArrayExtensions.IndexOf(m_List, item);
int index = m_List.IndexOf(item);
return index != -1;
}
/// <inheritdoc />
public bool Remove(T item)
{
int index = NativeArrayExtensions.IndexOf(m_List, item);
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
int index = m_List.IndexOf(item);
if (index == -1)
{
return false;
@@ -390,8 +455,21 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Insert(int index, T item)
{
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = 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[index] = item;
}
else
{
m_List.Add(item);
}
var listEvent = new NetworkListEvent<T>()
{
@@ -406,6 +484,12 @@ namespace Unity.Netcode
/// <inheritdoc />
public void RemoveAt(int index)
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
m_List.RemoveAt(index);
var listEvent = new NetworkListEvent<T>()
@@ -423,13 +507,21 @@ namespace Unity.Netcode
get => m_List[index];
set
{
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
var previousValue = m_List[index];
m_List[index] = value;
var listEvent = new NetworkListEvent<T>()
{
Type = NetworkListEvent<T>.EventType.Value,
Index = index,
Value = value
Value = value,
PreviousValue = previousValue
};
HandleAddListEvent(listEvent);
@@ -439,9 +531,13 @@ namespace Unity.Netcode
private void HandleAddListEvent(NetworkListEvent<T> listEvent)
{
m_DirtyEvents.Add(listEvent);
MarkNetworkObjectDirty();
OnListChanged?.Invoke(listEvent);
}
/// <summary>
/// This is actually unused left-over from a previous interface
/// </summary>
public int LastModifiedTick
{
get
@@ -451,6 +547,11 @@ 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()
{
m_List.Dispose();

View File

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

View File

@@ -1,4 +1,5 @@
using System;
using UnityEngine;
namespace Unity.Netcode
{
@@ -12,16 +13,41 @@ namespace Unity.Netcode
/// </summary>
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced;
/// <summary>
/// Maintains a link to the associated NetworkBehaviour
/// </summary>
private protected NetworkBehaviour m_NetworkBehaviour;
public NetworkBehaviour GetBehaviour()
{
return m_NetworkBehaviour;
}
/// <summary>
/// Initializes the NetworkVariable
/// </summary>
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
public void Initialize(NetworkBehaviour networkBehaviour)
{
m_NetworkBehaviour = networkBehaviour;
}
/// <summary>
/// The default read permissions
/// </summary>
public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone;
/// <summary>
/// The default write permissions
/// </summary>
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(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
@@ -30,7 +56,11 @@ namespace Unity.Netcode
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>
/// Gets or sets the name of the network variable's instance
@@ -43,14 +73,29 @@ namespace Unity.Netcode
/// </summary>
public readonly NetworkVariableReadPermission ReadPerm;
/// <summary>
/// The write permission for this var
/// </summary>
public readonly NetworkVariableWritePermission WritePerm;
/// <summary>
/// Sets whether or not the variable needs to be delta synced
/// </summary>
/// <param name="isDirty">Whether or not the var is dirty</param>
public virtual void SetDirty(bool 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>
@@ -70,6 +115,11 @@ namespace Unity.Netcode
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)
{
switch (ReadPerm)
@@ -78,10 +128,15 @@ namespace Unity.Netcode
case NetworkVariableReadPermission.Everyone:
return true;
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)
{
switch (WritePerm)
@@ -127,6 +182,9 @@ namespace Unity.Netcode
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
/// <summary>
/// Virtual <see cref="IDisposable"/> implementation
/// </summary>
public virtual void Dispose()
{
}

View File

@@ -1,77 +0,0 @@
using System;
using UnityEngine;
namespace Unity.Netcode
{
public class NetworkVariableHelper
{
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
//
// RuntimeAccessModifiersILPP will make this `public`
internal static void InitializeDelegatesNetworkSerializable<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteNetworkSerializable);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadNetworkSerializable);
}
internal static void InitializeDelegatesStruct<T>() where T : unmanaged, INetworkSerializeByMemcpy
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteStruct);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadStruct);
}
internal static void InitializeDelegatesEnum<T>() where T : unmanaged, Enum
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteEnum);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadEnum);
}
internal static void InitializeDelegatesPrimitive<T>() where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>
{
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WritePrimitive);
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadPrimitive);
}
internal static void InitializeAllBaseDelegates()
{
// Built-in C# types, serialized through a generic method
InitializeDelegatesPrimitive<bool>();
InitializeDelegatesPrimitive<byte>();
InitializeDelegatesPrimitive<sbyte>();
InitializeDelegatesPrimitive<char>();
InitializeDelegatesPrimitive<decimal>();
InitializeDelegatesPrimitive<float>();
InitializeDelegatesPrimitive<double>();
InitializeDelegatesPrimitive<short>();
InitializeDelegatesPrimitive<ushort>();
InitializeDelegatesPrimitive<int>();
InitializeDelegatesPrimitive<uint>();
InitializeDelegatesPrimitive<long>();
InitializeDelegatesPrimitive<ulong>();
// Built-in Unity types, serialized with specific overloads because they're structs without ISerializeByMemcpy attached
NetworkVariableSerialization<Vector2>.SetWriteDelegate((FastBufferWriter writer, in Vector2 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector3>.SetWriteDelegate((FastBufferWriter writer, in Vector3 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector4>.SetWriteDelegate((FastBufferWriter writer, in Vector4 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Quaternion>.SetWriteDelegate((FastBufferWriter writer, in Quaternion value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Color>.SetWriteDelegate((FastBufferWriter writer, in Color value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Color32>.SetWriteDelegate((FastBufferWriter writer, in Color32 value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Ray>.SetWriteDelegate((FastBufferWriter writer, in Ray value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Ray2D>.SetWriteDelegate((FastBufferWriter writer, in Ray2D value) => { writer.WriteValueSafe(value); });
NetworkVariableSerialization<Vector2>.SetReadDelegate((FastBufferReader reader, out Vector2 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Vector3>.SetReadDelegate((FastBufferReader reader, out Vector3 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Vector4>.SetReadDelegate((FastBufferReader reader, out Vector4 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Quaternion>.SetReadDelegate((FastBufferReader reader, out Quaternion value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Color>.SetReadDelegate((FastBufferReader reader, out Color value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Color32>.SetReadDelegate((FastBufferReader reader, out Color32 value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Ray>.SetReadDelegate((FastBufferReader reader, out Ray value) => { reader.ReadValueSafe(out value); });
NetworkVariableSerialization<Ray2D>.SetReadDelegate((FastBufferReader reader, out Ray2D value) => { reader.ReadValueSafe(out value); });
}
}
}

View File

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

View File

@@ -1,169 +1,443 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// Interface used by NetworkVariables to serialize them
/// </summary>
/// <typeparam name="T"></typeparam>
internal interface INetworkVariableSerializer<T>
{
// Write has to be taken by ref here because of INetworkSerializable
// Open Instance Delegates (pointers to methods without an instance attached to them)
// require the first parameter passed to them (the instance) to be passed by ref.
// So foo.Bar() becomes BarDelegate(ref foo);
// Taking T as an in parameter like we do in other places would require making a copy
// of it to pass it as a ref parameter.
public void Write(FastBufferWriter writer, ref T value);
public void Read(FastBufferReader reader, 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>
/// Basic serializer for unmanaged types.
/// This covers primitives, built-in unity types, and IForceSerializeByMemcpy
/// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things
/// by calling that directly - thus preventing us from having to have a specific T that meets
/// the specific constraints that the various generic WriteValue calls require.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
{
public void Write(FastBufferWriter writer, ref T value)
{
writer.WriteUnmanagedSafe(value);
}
public void Read(FastBufferReader reader, ref T value)
{
reader.ReadUnmanagedSafe(out value);
}
}
/// <summary>
/// Serializer for FixedStrings
/// </summary>
/// <typeparam name="T"></typeparam>
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
public void Write(FastBufferWriter writer, ref T value)
{
writer.WriteValueSafe(value);
}
public void Read(FastBufferReader reader, ref T value)
{
reader.ReadValueSafeInPlace(ref value);
}
}
/// <summary>
/// Serializer for unmanaged INetworkSerializable types
/// </summary>
/// <typeparam name="T"></typeparam>
internal class UnmanagedNetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged, INetworkSerializable
{
public void Write(FastBufferWriter writer, ref T value)
{
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
value.NetworkSerialize(bufferSerializer);
}
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.NetworkSerialize(bufferSerializer);
}
}
}
/// <summary>
/// This class is used to register user serialization with NetworkVariables for types
/// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter
/// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows
/// users to tell NetworkVariable about those extension methods (or simply pass in a lambda)
/// </summary>
/// <typeparam name="T"></typeparam>
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);
/// <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);
/// <summary>
/// The <see cref="WriteValueDelegate"/> delegate handler declaration
/// </summary>
public static WriteValueDelegate WriteValue;
/// <summary>
/// The <see cref="ReadValueDelegate"/> delegate handler declaration
/// </summary>
public static ReadValueDelegate ReadValue;
}
/// <summary>
/// This class is instantiated for types that we can't determine ahead of time are serializable - types
/// that don't meet any of the constraints for methods that are available on FastBufferReader and
/// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure
/// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence
/// of user serialization isn't checked until it's used, so if no serialization is provided, this
/// will throw an exception when an object containing the relevant NetworkVariable is spawned.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
{
public void Write(FastBufferWriter writer, ref T value)
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
{
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
}
public void Read(FastBufferReader reader, ref T value)
{
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
{
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
}
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
}
}
/// <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
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
#if UNITY_EDITOR
[InitializeOnLoadMethod]
#endif
internal static void InitializeIntegerSerialization()
{
NetworkVariableSerialization<short>.Serializer = new ShortSerializer();
NetworkVariableSerialization<short>.AreEqual = NetworkVariableSerialization<short>.ValueEquals;
NetworkVariableSerialization<ushort>.Serializer = new UshortSerializer();
NetworkVariableSerialization<ushort>.AreEqual = NetworkVariableSerialization<ushort>.ValueEquals;
NetworkVariableSerialization<int>.Serializer = new IntSerializer();
NetworkVariableSerialization<int>.AreEqual = NetworkVariableSerialization<int>.ValueEquals;
NetworkVariableSerialization<uint>.Serializer = new UintSerializer();
NetworkVariableSerialization<uint>.AreEqual = NetworkVariableSerialization<uint>.ValueEquals;
NetworkVariableSerialization<long>.Serializer = new LongSerializer();
NetworkVariableSerialization<long>.AreEqual = NetworkVariableSerialization<long>.ValueEquals;
NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer();
NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableSerialization<ulong>.ValueEquals;
}
/// <summary>
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_UnmanagedByMemcpy<T>() where T : unmanaged
{
NetworkVariableSerialization<T>.Serializer = new UnmanagedTypeSerializer<T>();
}
/// <summary>
/// Registers an unmanaged type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_UnmanagedINetworkSerializable<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariableSerialization<T>.Serializer = new UnmanagedNetworkSerializableSerializer<T>();
}
/// <summary>
/// Registers a managed type that implements INetworkSerializable and will be serialized through a call to
/// NetworkSerialize
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_ManagedINetworkSerializable<T>() where T : class, INetworkSerializable, new()
{
NetworkVariableSerialization<T>.Serializer = new ManagedNetworkSerializableSerializer<T>();
}
/// <summary>
/// Registers a FixedString type that will be serialized through FastBufferReader/FastBufferWriter's FixedString
/// serializers
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeSerializer_FixedString<T>() where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
NetworkVariableSerialization<T>.Serializer = new FixedStringSerializer<T>();
}
/// <summary>
/// Registers a managed type that will be checked for equality using T.Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_ManagedIEquatable<T>() where T : class, IEquatable<T>
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEqualsObject;
}
/// <summary>
/// Registers an unmanaged type that will be checked for equality using T.Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_UnmanagedIEquatable<T>() where T : unmanaged, IEquatable<T>
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.EqualityEquals;
}
/// <summary>
/// Registers an unmanaged type that will be checked for equality using memcmp and only considered
/// equal if they are bitwise equivalent in memory
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_UnmanagedValueEquals<T>() where T : unmanaged
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ValueEquals;
}
/// <summary>
/// Registers a managed type that will be checked for equality using the == operator
/// </summary>
/// <typeparam name="T"></typeparam>
public static void InitializeEqualityChecker_ManagedClassEquals<T>() where T : class
{
NetworkVariableSerialization<T>.AreEqual = NetworkVariableSerialization<T>.ClassEquals;
}
}
/// <summary>
/// Support methods for reading/writing NetworkVariables
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
/// but there's no way to achieve the same thing with a class, this includes various read/write delegates
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
/// which is invoked by code generated by ILPP during module load.
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
///
/// Any type that inherits from `NetworkVariableSerialization<T>` will implicitly result in any `T`
/// passed to it being picked up and initialized by ILPP.
///
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
/// 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.
/// </summary>
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
[Serializable]
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
public static class NetworkVariableSerialization<T>
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
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
{
writer.WriteNetworkSerializable(value);
// get unmanaged pointers
var aptr = UnsafeUtility.AddressOf(ref a);
var bptr = UnsafeUtility.AddressOf(ref b);
// compare addresses
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0;
}
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializable
internal static bool EqualityEqualsObject<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize structs
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
writer.WriteValueSafe(value);
}
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, INetworkSerializeByMemcpy
{
reader.ReadValueSafe(out value);
}
// Functions that serialize enums
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, Enum
{
writer.WriteValueSafe(value);
}
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, Enum
{
reader.ReadValueSafe(out value);
}
// Functions that serialize other types
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
writer.WriteValueSafe(value);
}
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
{
reader.ReadValueSafe(out value);
}
// Should never be reachable at runtime. All calls to this should be replaced with the correct
// call above by ILPP.
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : unmanaged
{
if (value is INetworkSerializable)
if (a == null)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
return b == null;
}
else if (value is INetworkSerializeByMemcpy)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (value is Enum)
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
if (b == null)
{
return false;
}
NetworkVariableSerialization<TForMethod>.Write(writer, value);
return a.Equals(b);
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
{
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
{
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
}
else
{
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
}
NetworkVariableSerialization<TForMethod>.Read(reader, out value);
return a.Equals(b);
}
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
protected internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
// These static delegates provide the right implementation for writing and reading a particular network variable type.
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
//
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
//
// In the future we may be able to use this to provide packing implementations for floats and integers to optimize bandwidth usage.
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
private static WriteDelegate<T> s_Write = WriteValue;
private static ReadDelegate<T> s_Read = ReadValue;
protected static void Write(FastBufferWriter writer, in T value)
internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
{
s_Write(writer, value);
return a == b;
}
protected static void Read(FastBufferReader reader, out T value)
internal static void Write(FastBufferWriter writer, ref T value)
{
s_Read(reader, out value);
Serializer.Write(writer, ref value);
}
internal static void SetWriteDelegate(WriteDelegate<T> write)
{
s_Write = write;
}
internal static void SetReadDelegate(ReadDelegate<T> read)
{
s_Read = read;
}
protected NetworkVariableSerialization(
NetworkVariableReadPermission readPerm = DefaultReadPerm,
NetworkVariableWritePermission writePerm = DefaultWritePerm)
: base(readPerm, writePerm)
internal static void Read(FastBufferReader reader, ref T value)
{
Serializer.Read(reader, ref value);
}
}
}

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -10,19 +10,27 @@ namespace Unity.Netcode
/// </summary>
internal interface ISceneManagerHandler
{
// Generic action to call when a scene is finished loading/unloading
struct SceneEventAction
{
internal uint SceneEventId;
internal Action<uint> EventAction;
internal void Invoke()
{
EventAction.Invoke(SceneEventId);
}
}
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction);
void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager = null);
Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager = null);
bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager = null);
void StopTrackingScene(int handle, string name, NetworkManager networkManager = null);
void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager = null);
void ClearSceneTracking(NetworkManager networkManager = null);
void UnloadUnassignedScenes(NetworkManager networkManager = null);
void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene);
void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode);
bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using UnityEngine.SceneManagement;
@@ -80,6 +80,16 @@ namespace Unity.Netcode
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
SynchronizeComplete,
/// <summary>
/// Synchronizes clients when the active scene has changed
/// See: <see cref="NetworkObject.ActiveSceneSynchronization"/>
/// </summary>
ActiveSceneChanged,
/// <summary>
/// Synchronizes clients when one or more NetworkObjects are migrated into a new scene
/// See: <see cref="NetworkObject.SceneMigrationSynchronization"/>
/// </summary>
ObjectSceneChanged,
}
/// <summary>
@@ -94,13 +104,13 @@ namespace Unity.Netcode
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
internal uint SceneEventId;
internal uint ActiveSceneHash;
internal uint SceneHash;
internal int SceneHandle;
// Used by the client during synchronization
internal uint ClientSceneHash;
internal int ClientSceneHandle;
internal int NetworkSceneHandle;
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
/// NetworkVariable information. If that process changes, then we need to update this
@@ -118,6 +128,9 @@ namespace Unity.Netcode
/// </summary>
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
private List<NetworkObject> m_DespawnedInSceneObjectsSync = new List<NetworkObject>();
private Dictionary<int, List<uint>> m_DespawnedInSceneObjects = new Dictionary<int, List<uint>>();
/// <summary>
/// Server Side Re-Synchronization:
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
@@ -136,6 +149,8 @@ namespace Unity.Netcode
internal Queue<uint> ScenesToSynchronize;
internal Queue<uint> SceneHandlesToSynchronize;
internal LoadSceneMode ClientSynchronizationMode;
/// <summary>
/// Server Side:
@@ -240,7 +255,45 @@ namespace Unity.Netcode
m_NetworkObjectsSync.Add(sobj);
}
}
// Sort by parents before children
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
// Sort by INetworkPrefabInstanceHandler implementation before the
// NetworkObjects spawned by the implementation
m_NetworkObjectsSync.Sort(SortNetworkObjects);
// This is useful to know what NetworkObjects a client is going to be synchronized with
// as well as the order in which they will be deserialized
if (m_NetworkManager.LogLevel == LogLevel.Developer)
{
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
foreach (var networkObject in m_NetworkObjectsSync)
{
messageBuilder.Append($"{networkObject.name}");
}
NetworkLog.LogInfo(messageBuilder.ToString());
}
}
internal void AddDespawnedInSceneNetworkObjects()
{
m_DespawnedInSceneObjectsSync.Clear();
// Find all active and non-active in-scene placed NetworkObjects
#if UNITY_2023_1_OR_NEWER
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager);
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
#endif
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
{
m_DespawnedInSceneObjectsSync.Add(sobj);
}
}
}
/// <summary>
@@ -274,6 +327,8 @@ namespace Unity.Netcode
case SceneEventType.ReSynchronize:
case SceneEventType.LoadEventCompleted:
case SceneEventType.UnloadEventCompleted:
case SceneEventType.ActiveSceneChanged:
case SceneEventType.ObjectSceneChanged:
{
return true;
}
@@ -307,6 +362,32 @@ namespace Unity.Netcode
return 0;
}
/// <summary>
/// Sorts the synchronization order of the NetworkObjects to be serialized
/// by parents before children.
/// </summary>
/// <remarks>
/// This only handles late joining players. Spawning and nesting several children
/// dynamically is still handled by the orphaned child list when deserialized out of
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
/// dropped and re-sent but child object is received and processed)
/// </remarks>
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
{
// If the first has a parent, move the first down
if (first.transform.parent != null)
{
return 1;
}
else // If the second has a parent and the first does not, then move the first up
if (second.transform.parent != null)
{
return -1;
}
return 0;
}
/// <summary>
/// Client and Server Side:
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
@@ -317,14 +398,30 @@ namespace Unity.Netcode
// Write the scene event type
writer.WriteValueSafe(SceneEventType);
if (SceneEventType == SceneEventType.ActiveSceneChanged)
{
writer.WriteValueSafe(ActiveSceneHash);
return;
}
if (SceneEventType == SceneEventType.ObjectSceneChanged)
{
SerializeObjectsMovedIntoNewScene(writer);
return;
}
// Write the scene loading mode
writer.WriteValueSafe(LoadSceneMode);
writer.WriteValueSafe((byte)LoadSceneMode);
// Write the scene event progress Guid
if (SceneEventType != SceneEventType.Synchronize)
{
writer.WriteValueSafe(SceneEventProgressId);
}
else
{
writer.WriteValueSafe(ClientSynchronizationMode);
}
// Write the scene index and handle
writer.WriteValueSafe(SceneHash);
@@ -334,6 +431,7 @@ namespace Unity.Netcode
{
case SceneEventType.Synchronize:
{
writer.WriteValueSafe(ActiveSceneHash);
WriteSceneSynchronizationData(writer);
break;
}
@@ -372,26 +470,38 @@ namespace Unity.Netcode
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
// Store our current position in the stream to come back and say how much data we have written
var positionStart = writer.Position;
// Size Place Holder -- Start
// !!NOTE!!: Since this is a placeholder to be set after we know how much we have written,
// for stream offset purposes this MUST not be a packed value!
writer.WriteValueSafe((int)0);
writer.WriteValueSafe(0);
int totalBytes = 0;
// Write the number of NetworkObjects we are serializing
writer.WriteValueSafe(m_NetworkObjectsSync.Count());
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
writer.WriteValueSafe(m_NetworkObjectsSync.Count);
// Serialize all NetworkObjects that are spawned
for (var i = 0; i < m_NetworkObjectsSync.Count; ++i)
{
var noStart = writer.Position;
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle);
sceneObject.Serialize(writer);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
totalBytes += noStop - noStart;
}
// 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)
{
var noStart = writer.Position;
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position;
totalBytes += noStop - noStart;
}
// Size Place Holder -- End
@@ -423,8 +533,6 @@ namespace Unity.Netcode
{
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
{
// Write our server relative scene handle for the NetworkObject being serialized
writer.WriteValueSafe(keyValuePairBySceneHandle.Key);
// Serialize the NetworkObject
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId);
sceneObject.Serialize(writer);
@@ -433,6 +541,15 @@ namespace Unity.Netcode
}
}
// Write the number of despawned in-scene placed NetworkObjects
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
// Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
}
var tailPosition = writer.Position;
// Reposition to our count position to the head before we wrote our object count
writer.Seek(headPosition);
@@ -450,12 +567,37 @@ namespace Unity.Netcode
internal void Deserialize(FastBufferReader reader)
{
reader.ReadValueSafe(out SceneEventType);
reader.ReadValueSafe(out LoadSceneMode);
if (SceneEventType == SceneEventType.ActiveSceneChanged)
{
reader.ReadValueSafe(out ActiveSceneHash);
return;
}
if (SceneEventType == SceneEventType.ObjectSceneChanged)
{
// Defer these scene event types if a client hasn't finished synchronizing
if (!m_NetworkManager.IsConnectedClient)
{
DeferObjectsMovedIntoNewScene(reader);
}
else
{
DeserializeObjectsMovedIntoNewScene(reader);
}
return;
}
reader.ReadValueSafe(out byte loadSceneMode);
LoadSceneMode = (LoadSceneMode)loadSceneMode;
if (SceneEventType != SceneEventType.Synchronize)
{
reader.ReadValueSafe(out SceneEventProgressId);
}
else
{
reader.ReadValueSafe(out ClientSynchronizationMode);
}
reader.ReadValueSafe(out SceneHash);
reader.ReadValueSafe(out SceneHandle);
@@ -464,6 +606,7 @@ namespace Unity.Netcode
{
case SceneEventType.Synchronize:
{
reader.ReadValueSafe(out ActiveSceneHash);
CopySceneSynchronizationData(reader);
break;
}
@@ -541,15 +684,19 @@ namespace Unity.Netcode
for (ushort i = 0; i < newObjectsCount; i++)
{
InternalBuffer.ReadValueSafe(out int sceneHandle);
// Set our relative scene to the NetworkObject
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneHandle);
// Deserialize the NetworkObject
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer);
if (sceneObject.IsSceneObject)
{
// Set our relative scene to the NetworkObject
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
}
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
}
finally
{
@@ -571,7 +718,11 @@ namespace Unity.Netcode
if (networkObjectsToRemove.Length > 0)
{
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
var networkObjectIdToNetworkObject = new Dictionary<ulong, NetworkObject>();
foreach (var networkObject in networkObjects)
{
@@ -672,6 +823,90 @@ namespace Unity.Netcode
}
}
/// <summary>
/// For synchronizing any despawned in-scene placed NetworkObjects that were
/// despawned by the server during synchronization or scene loading
/// </summary>
private void DeserializeDespawnedInScenePlacedNetworkObjects()
{
// Process all de-spawned in-scene NetworkObjects for this network session
m_DespawnedInSceneObjects.Clear();
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
for (int i = 0; i < despawnedObjectsCount; i++)
{
// We just need to get the scene
InternalBuffer.ReadValueSafe(out int networkSceneHandle);
InternalBuffer.ReadValueSafe(out uint globalObjectIdHash);
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
if (!sceneCache.ContainsKey(networkSceneHandle))
{
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
{
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
{
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
// Find all active and non-active in-scene placed NetworkObjects
#if UNITY_2023_1_OR_NEWER
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#endif
foreach (var inSceneObject in inSceneNetworkObjects)
{
if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash))
{
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
}
}
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
}
}
else // Use the cached NetworkObjects if they exist
{
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
}
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
{
// Since this is a NetworkObject that was never spawned, we just need to send a notification
// out that it was despawned so users can make adjustments
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
}
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
{
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
}
}
else
{
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
}
}
}
/// <summary>
/// Client Side:
/// During the processing of a server sent Event_Sync, this method will be called for each scene once
@@ -683,26 +918,33 @@ namespace Unity.Netcode
{
try
{
// Process all NetworkObjects for this scene
// Process all spawned NetworkObjects for this network session
InternalBuffer.ReadValueSafe(out int newObjectsCount);
for (int i = 0; i < newObjectsCount; i++)
{
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
InternalBuffer.ReadValueSafe(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))
// If the sceneObject is in-scene placed, then set the scene being synchronized
if (sceneObject.IsSceneObject)
{
m_NetworkObjectsSync.Add(spawnedNetworkObject);
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
{
@@ -753,6 +995,143 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Serialize scene handles and associated NetworkObjects that were migrated
/// into a new scene.
/// </summary>
private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer)
{
var sceneManager = m_NetworkManager.SceneManager;
// Write the number of scene handles
writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count);
foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene)
{
// Write the scene handle
writer.WriteValueSafe(sceneHandleObjects.Key);
// Write the number of NetworkObjectIds to expect
writer.WriteValueSafe(sceneHandleObjects.Value.Count);
foreach (var networkObject in sceneHandleObjects.Value)
{
writer.WriteValueSafe(networkObject.NetworkObjectId);
}
}
// Once we are done, clear the table
sceneManager.ObjectsMigratedIntoNewScene.Clear();
}
/// <summary>
/// Deserialize scene handles and associated NetworkObjects that need to
/// be migrated into a new scene.
/// </summary>
private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader)
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
// Just always assure this has no entries
sceneManager.ObjectsMigratedIntoNewScene.Clear();
var numberOfScenes = 0;
var sceneHandle = 0;
var objectCount = 0;
var networkObjectId = (ulong)0;
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, new List<NetworkObject>());
reader.ReadValueSafe(out objectCount);
for (int j = 0; j < objectCount; j++)
{
reader.ReadValueSafe(out networkObjectId);
if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId))
{
NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!");
continue;
}
// Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed
//
sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(spawnManager.SpawnedObjects[networkObjectId]);
}
}
}
/// <summary>
/// While a client is synchronizing ObjectSceneChanged messages could be received.
/// This defers any ObjectSceneChanged message processing to occur after the client
/// has completed synchronization to assure the associated NetworkObjects being
/// migrated to a new scene are instantiated and spawned.
/// </summary>
private void DeferObjectsMovedIntoNewScene(FastBufferReader reader)
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
var numberOfScenes = 0;
var sceneHandle = 0;
var objectCount = 0;
var networkObjectId = (ulong)0;
var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent()
{
ObjectsMigratedTable = new Dictionary<int, List<ulong>>()
};
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List<ulong>());
reader.ReadValueSafe(out objectCount);
for (int j = 0; j < objectCount; j++)
{
reader.ReadValueSafe(out networkObjectId);
deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId);
}
}
sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent);
}
internal void ProcessDeferredObjectSceneChangedEvents()
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
if (sceneManager.DeferredObjectsMovedEvents.Count == 0)
{
return;
}
foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents)
{
foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable)
{
if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key))
{
sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new List<NetworkObject>());
}
foreach (var objectId in keyEntry.Value)
{
if (!spawnManager.SpawnedObjects.ContainsKey(objectId))
{
NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!");
continue;
}
var networkObject = spawnManager.SpawnedObjects[objectId];
if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Contains(networkObject))
{
sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(networkObject);
}
}
}
objectsMovedEvent.ObjectsMigratedTable.Clear();
}
sceneManager.DeferredObjectsMovedEvents.Clear();
// If there are any pending objects to migrate, then migrate them
if (sceneManager.ObjectsMigratedIntoNewScene.Count > 0)
{
sceneManager.MigrateNetworkObjectsIntoScenes();
}
}
/// <summary>
/// Used to release the pooled network buffer
/// </summary>

View File

@@ -58,12 +58,13 @@ namespace Unity.Netcode
/// <summary>
/// List of clientIds of those clients that is done loading the scene.
/// </summary>
internal List<ulong> DoneClients { get; } = new List<ulong>();
internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
internal List<ulong> ClientsThatDisconnected = new List<ulong>();
/// <summary>
/// The NetworkTime at the moment the scene switch was initiated by the server.
/// This is when the current scene event will have timed out
/// </summary>
internal NetworkTime TimeAtInitiation { get; }
internal float WhenSceneEventHasTimedOut;
/// <summary>
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
@@ -75,17 +76,15 @@ namespace Unity.Netcode
/// </summary>
internal OnCompletedDelegate OnComplete;
/// <summary>
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
/// </summary>
internal bool IsCompleted { get; private set; }
internal bool TimedOut { get; private set; }
internal Action<uint> OnSceneEventCompleted;
/// <summary>
/// If all clients are done loading the scene, at the moment of completed.
/// This will make sure that we only have timed out if we never completed
/// </summary>
internal bool AreAllClientsDoneLoading { get; private set; }
internal bool HasTimedOut()
{
return WhenSceneEventHasTimedOut <= m_NetworkManager.RealTimeProvider.RealTimeSinceStartup;
}
/// <summary>
/// The hash value generated from the full scene path
@@ -93,9 +92,10 @@ namespace Unity.Netcode
internal uint SceneHash { get; set; }
internal Guid Guid { get; } = Guid.NewGuid();
internal uint SceneEventId;
private Coroutine m_TimeOutCoroutine;
private AsyncOperation m_SceneLoadOperation;
private AsyncOperation m_AsyncOperation;
private NetworkManager m_NetworkManager { get; }
@@ -105,55 +105,195 @@ namespace Unity.Netcode
internal LoadSceneMode LoadSceneMode;
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
{
var clients = new List<ulong>();
if (completedSceneEvent)
{
// If we are the host, then add the host-client to the list
// of clients that completed if the AsyncOperation is done.
if (m_NetworkManager.IsHost && m_AsyncOperation.isDone)
{
clients.Add(m_NetworkManager.LocalClientId);
}
// Add all clients that completed the scene event
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (clientStatus.Value == completedSceneEvent)
{
clients.Add(clientStatus.Key);
}
}
}
else
{
// If we are the host, then add the host-client to the list
// of clients that did not complete if the AsyncOperation is
// not done.
if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone)
{
clients.Add(m_NetworkManager.LocalClientId);
}
// If we are getting the list of clients that have not completed the
// scene event, then add any clients that disconnected during this
// scene event.
clients.AddRange(ClientsThatDisconnected);
}
return clients;
}
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
{
if (status == SceneEventProgressStatus.Started)
{
m_NetworkManager = networkManager;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
TimeAtInitiation = networkManager.LocalTime;
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 = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
}
}
Status = status;
}
/// <summary>
/// Remove the client from the clients processing the current scene event
/// Add this client to the clients that disconnected list
/// </summary>
private void OnClientDisconnectCallback(ulong clientId)
{
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsThatDisconnected.Add(clientId);
ClientsProcessingSceneEvent.Remove(clientId);
}
}
/// <summary>
/// Coroutine that checks to see if the scene event is complete every network tick period.
/// This will handle completing the scene event when one or more client(s) disconnect(s)
/// during a scene event and if it does not complete within the scene loading time out period
/// it will time out the scene event.
/// </summary>
internal IEnumerator TimeOutSceneEventProgress()
{
yield return new WaitForSecondsRealtime(m_NetworkManager.NetworkConfig.LoadSceneTimeOut);
TimedOut = true;
CheckCompletion();
}
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();
}
internal void CheckCompletion()
{
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && m_SceneLoadOperation.isDone) || (!IsCompleted && TimedOut))
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
while (!HasTimedOut())
{
IsCompleted = true;
AreAllClientsDoneLoading = true;
yield return waitForNetworkTick;
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
TryFinishingSceneEventProgress();
}
}
/// <summary>
/// Sets the client's scene event progress to finished/true
/// </summary>
internal void ClientFinishedSceneEvent(ulong clientId)
{
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
{
ClientsProcessingSceneEvent[clientId] = true;
TryFinishingSceneEventProgress();
}
}
/// <summary>
/// Determines if the scene event has finished for both
/// client(s) and server.
/// </summary>
/// <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()
{
// If the network session is terminated/terminating then finish tracking
// this scene event
if (!IsNetworkSessionActive())
{
return true;
}
// Clients skip over this
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (!clientStatus.Value)
{
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
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.OnClientDisconnectCallback -= OnClientDisconnectCallback;
}
if (m_TimeOutCoroutine != null)
{
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
}
}
}

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