Compare commits
11 Commits
1.0.0-pre.
...
1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e7078c160 | ||
|
|
a6969670f5 | ||
|
|
e15bd056c5 | ||
|
|
18ffd5fdc8 | ||
|
|
0f7a30d285 | ||
|
|
5b1fc203ed | ||
|
|
add668dfd2 | ||
|
|
60e2dabef4 | ||
|
|
5b4aaa8b59 | ||
|
|
4818405514 | ||
|
|
36d07fad5e |
302
CHANGELOG.md
302
CHANGELOG.md
@@ -1,3 +1,4 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
@@ -6,13 +7,290 @@ 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.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) (#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)
|
||||
- `UnityTransport` settings can now be set programmatically. (#1845)
|
||||
- `FastBufferWriter` and Reader IsInitialized property. (#1859)
|
||||
- Prefabs can now be added to the network at **runtime** (i.e., from an addressable asset). If `ForceSamePrefabs` is false, this can happen after a connection has been formed. (#1882)
|
||||
- When `ForceSamePrefabs` is false, a configurable delay (default 1 second, configurable via `NetworkConfig.SpawnTimeout`) has been introduced to gracefully handle race conditions where a spawn call has been received for an object whose prefab is still being loaded. (#1882)
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed `NetcodeIntegrationTestHelpers` to use `UnityTransport` (#1870)
|
||||
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `SnapshotSystem` (#1852)
|
||||
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)
|
||||
- Removed `com.unity.collections` dependency from the package (#1849)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850)
|
||||
- Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847)
|
||||
- Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
|
||||
- Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838)
|
||||
- Fixed ClientRpcs would always send to all connected clients by default as opposed to only sending to the NetworkObject's Observers list by default. (#1836)
|
||||
- Fixed clarity for NetworkSceneManager client side notification when it receives a scene hash value that does not exist in its local hash table. (#1828)
|
||||
- Fixed client throws a key not found exception when it times out using UNet or UTP. (#1821)
|
||||
- Fixed network variable updates are no longer limited to 32,768 bytes when NetworkConfig.EnsureNetworkVariableLengthSafety is enabled. The limits are now determined by what the transport can send in a message. (#1811)
|
||||
- Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809)
|
||||
- Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808)
|
||||
- Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801)
|
||||
- Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780)
|
||||
- Fixed issue when spawning new player if an already existing player exists it does not remove IsPlayer from the previous player (#1779)
|
||||
- Fixed lack of notification that NetworkManager and NetworkObject cannot be added to the same GameObject with in-editor notifications (#1777)
|
||||
- Fixed parenting warning printing for false positives (#1855)
|
||||
|
||||
## [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)
|
||||
- Disallowed async keyword in RPCs (#1681)
|
||||
- Fixed an issue where Alpha release versions of Unity (version 2022.2.0a5 and later) will not compile due to the UNet Transport no longer existing (#1678)
|
||||
- Fixed messages larger than 64k being written with incorrectly truncated message size in header (#1686) (credit: @kaen)
|
||||
- Fixed overloading RPC methods causing collisions and failing on IL2CPP targets. (#1694)
|
||||
- Fixed spawn flow to propagate `IsSceneObject` down to children NetworkObjects, decouple implicit relationship between object spawning & `IsSceneObject` flag (#1685)
|
||||
- Fixed error when serializing ConnectionApprovalMessage with scene management disabled when one or more objects is hidden via the CheckObjectVisibility delegate (#1720)
|
||||
- Fixed CheckObjectVisibility delegate not being properly invoked for connecting clients when Scene Management is enabled. (#1680)
|
||||
- Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682)
|
||||
- Fixed NetworkVariables containing more than 1300 bytes of data (such as large NetworkLists) no longer cause an OverflowException (the limit on data size is now whatever limit the chosen transport imposes on fragmented NetworkDelivery mechanisms) (#1725)
|
||||
- Fixed ServerRpcParams and ClientRpcParams must be the last parameter of an RPC in order to function properly. Added a compile-time check to ensure this is the case and trigger an error if they're placed elsewhere (#1721)
|
||||
- Fixed FastBufferReader being created with a length of 1 if provided an input of length 0 (#1724)
|
||||
- Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728)
|
||||
- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731)
|
||||
- Improved performance in NetworkAnimator (#1735)
|
||||
- Removed the "always sync" network animator (aka "autosend") parameters (#1746)
|
||||
- Fixed in-scene placed NetworkObjects not respawning after shutting down the NetworkManager and then starting it back up again (#1769)
|
||||
|
||||
## [1.0.0-pre.5] - 2022-01-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added `PreviousValue` in `NetworkListEvent`, when `Value` has changed (#1528)
|
||||
|
||||
### Changed
|
||||
|
||||
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
|
||||
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)'
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
|
||||
- Fixed The ClientNetworkTransform sample script to allow for owner changes at runtime. (#1606)
|
||||
- Fixed When the LogLevel is set to developer NetworkBehaviour generates warning messages when it should not (#1631)
|
||||
- Fixed NetworkTransport Initialize now can receive the associated NetworkManager instance to avoid using NetworkManager.Singleton in transport layer (#1677)
|
||||
- Fixed a bug where NetworkList.Contains value was inverted (#1363)
|
||||
|
||||
## [1.0.0-pre.4] - 2021-01-04
|
||||
|
||||
### Added
|
||||
|
||||
- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `com.unity.modules.ai` package dependency (#1565)
|
||||
- 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)
|
||||
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
|
||||
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
|
||||
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
|
||||
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
|
||||
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
|
||||
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
|
||||
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
|
||||
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)
|
||||
- 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)
|
||||
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)
|
||||
|
||||
## [1.0.0-pre.3] - 2021-10-22
|
||||
|
||||
### Added
|
||||
|
||||
- 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)
|
||||
@@ -37,7 +315,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)
|
||||
@@ -94,7 +372,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)
|
||||
@@ -166,14 +444,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).
|
||||
@@ -206,7 +484,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.
|
||||
@@ -221,10 +499,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.
|
||||
@@ -237,7 +515,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.
|
||||
|
||||
@@ -4,14 +4,14 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Solves for incoming values that are jittered
|
||||
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal abstract class BufferedLinearInterpolator<T> where T : struct
|
||||
/// <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;
|
||||
@@ -24,6 +24,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
|
||||
|
||||
@@ -69,6 +73,21 @@ namespace Unity.Netcode
|
||||
|
||||
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Resets interpolator to initial state
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Buffer.Clear();
|
||||
m_EndTimeConsumed = 0.0d;
|
||||
m_StartTimeConsumed = 0.0d;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -82,7 +101,6 @@ namespace Unity.Netcode
|
||||
Update(0, serverTime, serverTime);
|
||||
}
|
||||
|
||||
|
||||
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
|
||||
private void TryConsumeFromBuffer(double renderTime, double serverTime)
|
||||
{
|
||||
@@ -144,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);
|
||||
@@ -155,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);
|
||||
@@ -179,26 +199,36 @@ namespace Unity.Netcode
|
||||
|
||||
if (t < 0.0f)
|
||||
{
|
||||
throw new OverflowException($"t = {t} but must be >= 0. range {range}, RenderTime {renderTime}, Start time {m_StartTimeConsumed}, end time {m_EndTimeConsumed}");
|
||||
// There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed
|
||||
// This clamps t to a minimum of 0 and fixes issues with longer frames and pauses
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
|
||||
float maxInterpTime = 0.1f;
|
||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / maxInterpTime); // second interpolate to smooth out extrapolation jumps
|
||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps
|
||||
}
|
||||
|
||||
m_NbItemsReceivedThisFrame = 0;
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
/// <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++;
|
||||
@@ -211,11 +241,15 @@ namespace Unity.Netcode
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
ResetTo(newMeasurement, sentTime);
|
||||
// Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues
|
||||
m_Buffer.Add(m_LastBufferedItemReceived);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -223,39 +257,75 @@ 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;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
|
||||
internal class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="Quaternion"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
return Quaternion.SlerpUnclamped(start, end, time);
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Quaternion.Slerp(start, end, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
return Quaternion.SlerpUnclamped(start, end, time);
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Quaternion.Slerp(start, end, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +1,103 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
|
||||
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
|
||||
/// mode of the <see cref="Rigidbody"/> and disabling it on all peers but the authoritative one.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody")]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if we are server (true) or owner (false) authoritative
|
||||
/// </summary>
|
||||
private bool m_IsServerAuthoritative;
|
||||
|
||||
private Rigidbody m_Rigidbody;
|
||||
private NetworkTransform m_NetworkTransform;
|
||||
|
||||
private bool m_OriginalKinematic;
|
||||
private RigidbodyInterpolation m_OriginalInterpolation;
|
||||
|
||||
// Used to cache the authority state of this rigidbody during the last frame
|
||||
// Used to cache the authority state of this Rigidbody during the last frame
|
||||
private bool m_IsAuthority;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody"/> on this peer currently holds authority.
|
||||
/// </summary>
|
||||
private bool HasAuthority => m_NetworkTransform.CanCommitToTransform;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_NetworkTransform = GetComponent<NetworkTransform>();
|
||||
m_IsServerAuthoritative = m_NetworkTransform.IsServerAuthoritative();
|
||||
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
|
||||
// Set interpolation to none if NetworkTransform is handling interpolation, otherwise it sets it to the original value
|
||||
m_Rigidbody.interpolation = m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation;
|
||||
|
||||
// Turn off physics for the rigid body until spawned, otherwise
|
||||
// clients can run fixed update before the first full
|
||||
// NetworkTransform update
|
||||
m_Rigidbody.isKinematic = true;
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
/// <summary>
|
||||
/// For owner authoritative (i.e. ClientNetworkTransform)
|
||||
/// we adjust our authority when we gain ownership
|
||||
/// </summary>
|
||||
public override void OnGainedOwnership()
|
||||
{
|
||||
if (NetworkManager.IsListening)
|
||||
{
|
||||
if (HasAuthority != m_IsAuthority)
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
|
||||
private void UpdateRigidbodyKinematicMode()
|
||||
/// <summary>
|
||||
/// For owner authoritative(i.e. ClientNetworkTransform)
|
||||
/// we adjust our authority when we have lost ownership
|
||||
/// </summary>
|
||||
public override void OnLostOwnership()
|
||||
{
|
||||
if (m_IsAuthority == false)
|
||||
{
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation.None;
|
||||
/// <summary>
|
||||
/// Sets the authority differently depending upon
|
||||
/// whether it is server or owner authoritative
|
||||
/// </summary>
|
||||
private void UpdateOwnershipAuthority()
|
||||
{
|
||||
if (m_IsServerAuthoritative)
|
||||
{
|
||||
m_IsAuthority = NetworkManager.IsServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
|
||||
m_Rigidbody.isKinematic = m_OriginalKinematic;
|
||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||
m_IsAuthority = IsOwner;
|
||||
}
|
||||
|
||||
// If you have authority then you are not kinematic
|
||||
m_Rigidbody.isKinematic = !m_IsAuthority;
|
||||
|
||||
// Set interpolation of the Rigidbody based on authority
|
||||
// With authority: let local transform handle interpolation
|
||||
// Without authority: let the NetworkTransform handle interpolation
|
||||
m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : RigidbodyInterpolation.None;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
UpdateRigidbodyKinematicMode();
|
||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||
// Turn off physics for the rigid body until spawned, otherwise
|
||||
// non-owners can run fixed updates before the first full
|
||||
// NetworkTransform update and physics will be applied (i.e. gravity, etc)
|
||||
m_Rigidbody.isKinematic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
@@ -8,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;
|
||||
@@ -32,7 +34,7 @@ namespace Unity.Netcode.Components
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (NetworkManager.IsListening)
|
||||
if (IsSpawned)
|
||||
{
|
||||
if (HasAuthority != m_IsAuthority)
|
||||
{
|
||||
@@ -74,8 +76,8 @@ namespace Unity.Netcode.Components
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
m_IsAuthority = false;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS2D
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,13 +5,22 @@
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
"versionDefines": [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# About Netcode for GameObjects
|
||||
|
||||
Unity Netcode for GameObjects is a high-level networking library built to abstract networking. This allows developers to focus on the game rather than low level protocols and networking frameworks.
|
||||
|
||||
## Guides
|
||||
|
||||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
||||
|
||||
* [Documentation](https://docs-multiplayer.unity3d.com/docs/getting-started/about-mlapi)
|
||||
* [Installation](https://docs-multiplayer.unity3d.com/docs/migration/install)
|
||||
* [First Steps](https://docs-multiplayer.unity3d.com/docs/tutorials/helloworld/helloworldintro)
|
||||
* [API Reference](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
# Technical details
|
||||
|
||||
## Requirements
|
||||
|
||||
This version of Netcode for GameObjects is compatible with the following Unity versions and platforms:
|
||||
|
||||
* 2020.3 and later
|
||||
* Windows, Mac, Linux platforms
|
||||
|
||||
## Document revision history
|
||||
|
||||
|Date|Reason|
|
||||
|---|---|
|
||||
|March 10, 2021|Document created. Matches package version 0.1.0|
|
||||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
||||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
||||
|August 5, 2021|Update product/package name|
|
||||
|September 9,2021|Updated the links and name of the file.|
|
||||
35
Documentation~/index.md
Normal file
35
Documentation~/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# About Netcode for GameObjects
|
||||
|
||||
Netcode for GameObjects is a Unity package that provides networking capabilities to GameObject & MonoBehaviour workflows.
|
||||
|
||||
## Guides
|
||||
|
||||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
||||
|
||||
- [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about)
|
||||
- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/migration/install)
|
||||
- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro)
|
||||
- [API Reference](https://docs-multiplayer.unity3d.com/netcode/current/api/introduction)
|
||||
|
||||
# Technical details
|
||||
|
||||
## Requirements
|
||||
|
||||
Netcode for GameObjects targets the following Unity versions:
|
||||
- Unity 2020.3, 2021.1, 2021.2 and 2021.3
|
||||
|
||||
On the following runtime platforms:
|
||||
- Windows, MacOS, and Linux
|
||||
- iOS and Android
|
||||
- Most closed platforms, such as consoles. Contact us for more information about specific closed platforms.
|
||||
|
||||
## Document revision history
|
||||
|
||||
|Date|Reason|
|
||||
|---|---|
|
||||
|March 10, 2021|Document created. Matches package version 0.1.0|
|
||||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
||||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
||||
|August 5, 2021|Update product/package name|
|
||||
|September 9,2021|Updated the links and name of the file.|
|
||||
|April 20, 2022|Updated links|
|
||||
@@ -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;
|
||||
@@ -22,7 +27,13 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
|
||||
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
|
||||
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
|
||||
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
|
||||
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
|
||||
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
|
||||
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;
|
||||
@@ -73,6 +84,35 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string FullNameWithGenericParameters(this TypeReference typeReference, GenericParameter[] contextGenericParameters, TypeReference[] contextGenericParameterTypes)
|
||||
{
|
||||
var name = typeReference.FullName;
|
||||
if (typeReference.HasGenericParameters)
|
||||
{
|
||||
name += "<";
|
||||
for (var i = 0; i < typeReference.Resolve().GenericParameters.Count; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
name += ", ";
|
||||
}
|
||||
|
||||
for (var j = 0; j < contextGenericParameters.Length; ++j)
|
||||
{
|
||||
if (typeReference.GenericParameters[i].FullName == contextGenericParameters[i].FullName)
|
||||
{
|
||||
name += contextGenericParameterTypes[i].FullName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name += ">";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
|
||||
{
|
||||
if (typeReference.IsArray)
|
||||
@@ -83,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);
|
||||
}
|
||||
@@ -264,6 +317,28 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddWarning(this List<DiagnosticMessage> diagnostics, string message)
|
||||
{
|
||||
diagnostics.AddWarning((SequencePoint)null, message);
|
||||
}
|
||||
|
||||
public static void AddWarning(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message)
|
||||
{
|
||||
diagnostics.AddWarning(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
|
||||
}
|
||||
|
||||
public static void AddWarning(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message)
|
||||
{
|
||||
diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Warning,
|
||||
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
|
||||
Line = sequencePoint?.StartLine ?? 0,
|
||||
Column = sequencePoint?.StartColumn ?? 0,
|
||||
MessageData = $" - {message}"
|
||||
});
|
||||
}
|
||||
|
||||
public static void RemoveRecursiveReferences(this ModuleDefinition moduleDefinition)
|
||||
{
|
||||
// Weird behavior from Cecil: When importing a reference to a specific implementation of a generic
|
||||
@@ -322,5 +397,74 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
|
||||
private static void SearchForBaseModulesRecursive(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver, ref ModuleDefinition unityModule, ref ModuleDefinition netcodeModule, HashSet<string> visited)
|
||||
{
|
||||
|
||||
foreach (var module in assemblyDefinition.Modules)
|
||||
{
|
||||
if (module == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (unityModule == null && module.Name == UnityModuleName)
|
||||
{
|
||||
unityModule = module;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (netcodeModule == null && module.Name == NetcodeModuleName)
|
||||
{
|
||||
netcodeModule = module;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var assemblyNameReference in assemblyDefinition.MainModule.AssemblyReferences)
|
||||
{
|
||||
if (assemblyNameReference == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (visited.Contains(assemblyNameReference.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.Add(assemblyNameReference.Name);
|
||||
|
||||
var assembly = assemblyResolver.Resolve(assemblyNameReference);
|
||||
if (assembly == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SearchForBaseModulesRecursive(assembly, assemblyResolver, ref unityModule, ref netcodeModule, visited);
|
||||
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static (ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver)
|
||||
{
|
||||
ModuleDefinition unityModule = null;
|
||||
ModuleDefinition netcodeModule = null;
|
||||
var visited = new HashSet<string>();
|
||||
SearchForBaseModulesRecursive(assemblyDefinition, assemblyResolver, ref unityModule, ref netcodeModule, visited);
|
||||
|
||||
return (unityModule, netcodeModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
@@ -13,14 +12,11 @@ using MethodAttributes = Mono.Cecil.MethodAttributes;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
|
||||
internal sealed class INetworkMessageILPP : ILPPInterface
|
||||
{
|
||||
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>();
|
||||
|
||||
@@ -31,17 +27,25 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
@@ -63,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
|
||||
@@ -94,108 +98,125 @@ 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 TypeReference m_FastBufferReader_TypeRef;
|
||||
private TypeReference m_NetworkContext_TypeRef;
|
||||
private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef;
|
||||
private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
|
||||
private MethodReference m_MessagingSystem_MessageHandler_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 MethodReference m_Type_GetTypeFromHandle_MethodRef;
|
||||
|
||||
private MethodReference m_List_Add_MethodRef;
|
||||
|
||||
private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage);
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(typeof(FastBufferReader));
|
||||
m_NetworkContext_TypeRef = moduleDefinition.ImportReference(typeof(NetworkContext));
|
||||
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 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 (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_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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodDef in messagingSystemTypeDef.Methods)
|
||||
{
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_ReceiveMessageName:
|
||||
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodReference GetNetworkMessageRecieveHandler(TypeDefinition typeDefinition)
|
||||
{
|
||||
SequencePoint typeSequence = null;
|
||||
foreach (var method in typeDefinition.Methods)
|
||||
{
|
||||
var resolved = method.Resolve();
|
||||
var methodSequence = resolved.DebugInformation.SequencePoints.FirstOrDefault();
|
||||
if (typeSequence == null || methodSequence.StartLine < typeSequence.StartLine)
|
||||
{
|
||||
typeSequence = methodSequence;
|
||||
}
|
||||
|
||||
if (resolved.IsStatic && resolved.IsPublic && resolved.Name == "Receive" && resolved.Parameters.Count == 2
|
||||
&& !resolved.Parameters[0].IsIn
|
||||
&& !resolved.Parameters[0].ParameterType.IsByReference
|
||||
&& resolved.Parameters[0].ParameterType.Resolve() ==
|
||||
m_FastBufferReader_TypeRef.Resolve()
|
||||
&& resolved.Parameters[1].IsIn
|
||||
&& resolved.Parameters[1].ParameterType.IsByReference
|
||||
&& resolved.Parameters[1].ParameterType.GetElementType().Resolve() == m_NetworkContext_TypeRef.Resolve()
|
||||
&& resolved.ReturnType == resolved.Module.TypeSystem.Void)
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
m_Diagnostics.AddError(typeSequence, $"Class {typeDefinition.FullName} does not implement required method: `public static void Receive(FastBufferReader, in NetworkContext)`");
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
@@ -244,10 +265,8 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
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)
|
||||
@@ -264,11 +283,8 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
foreach (var type in networkMessageTypes)
|
||||
{
|
||||
var receiveMethod = GetNetworkMessageRecieveHandler(type);
|
||||
if (receiveMethod == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef);
|
||||
receiveMethod.GenericArguments.Add(type);
|
||||
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod);
|
||||
}
|
||||
|
||||
|
||||
120
Editor/CodeGen/INetworkSerializableILPP.cs
Normal file
120
Editor/CodeGen/INetworkSerializableILPP.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal sealed class INetworkSerializableILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
private TypeReference ResolveGenericType(TypeReference type, List<TypeReference> typeStack)
|
||||
{
|
||||
var genericName = type.Name;
|
||||
var lastType = (GenericInstanceType)typeStack[typeStack.Count - 1];
|
||||
var resolvedType = lastType.Resolve();
|
||||
typeStack.RemoveAt(typeStack.Count - 1);
|
||||
for (var i = 0; i < resolvedType.GenericParameters.Count; ++i)
|
||||
{
|
||||
var parameter = resolvedType.GenericParameters[i];
|
||||
if (parameter.Name == genericName)
|
||||
{
|
||||
var underlyingType = lastType.GenericArguments[i];
|
||||
if (underlyingType.Resolve() == null)
|
||||
{
|
||||
return ResolveGenericType(underlyingType, typeStack);
|
||||
}
|
||||
|
||||
return underlyingType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}");
|
||||
}
|
||||
|
||||
mainModule.RemoveRecursiveReferences();
|
||||
|
||||
// write
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
|
||||
var writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolStream = pdb,
|
||||
WriteSymbols = true
|
||||
};
|
||||
|
||||
assemblyDefinition.Write(pe, writerParameters);
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/CodeGen/INetworkSerializableILPP.cs.meta
Normal file
3
Editor/CodeGen/INetworkSerializableILPP.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64a0c1e708fa46a389d64e7b4708e6c7
|
||||
timeCreated: 1635535237
|
||||
File diff suppressed because it is too large
Load Diff
@@ -120,8 +120,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
foreach (var methodDefinition in typeDefinition.Methods)
|
||||
{
|
||||
if (methodDefinition.Name == nameof(NetworkBehaviour.__sendServerRpc)
|
||||
|| methodDefinition.Name == nameof(NetworkBehaviour.__sendClientRpc))
|
||||
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
|
||||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
|
||||
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
|
||||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc))
|
||||
{
|
||||
methodDefinition.IsFamily = true;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
public class DontShowInTransportDropdownAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f097067d4254dc7ad018d7ad90df7c3
|
||||
timeCreated: 1620386886
|
||||
81
Editor/HiddenScriptEditor.cs
Normal file
81
Editor/HiddenScriptEditor.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Unity.Netcode.Components;
|
||||
#if UNITY_UNET_PRESENT
|
||||
using Unity.Netcode.Transports.UNET;
|
||||
#endif
|
||||
using Unity.Netcode.Transports.UTP;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for the given component.
|
||||
/// </summary>
|
||||
public class HiddenScriptEditor : UnityEditor.Editor
|
||||
{
|
||||
private static readonly string[] k_HiddenFields = { "m_Script" };
|
||||
|
||||
/// <summary>
|
||||
/// Draws inspector properties without the script field.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
DrawPropertiesExcluding(serializedObject, k_HiddenFields);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
}
|
||||
#if UNITY_UNET_PRESENT
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for UNetTransport.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(UNetTransport), true)]
|
||||
public class UNetTransportEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for UnityTransport.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(UnityTransport), true)]
|
||||
public class UnityTransportEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#if COM_UNITY_MODULES_ANIMATION
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for NetworkAnimator.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
||||
public class NetworkAnimatorEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for NetworkRigidbody.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkRigidbody), true)]
|
||||
public class NetworkRigidbodyEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
/// <summary>
|
||||
/// Internal use. Hides the script field for NetworkRigidbody2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkRigidbody2D), true)]
|
||||
public class NetworkRigidbody2DEditor : HiddenScriptEditor
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
3
Editor/HiddenScriptEditor.cs.meta
Normal file
3
Editor/HiddenScriptEditor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebf622cc80e94f488e59caf8b7419f50
|
||||
timeCreated: 1661959406
|
||||
@@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using Unity.Netcode.Components;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
public static class TextUtility
|
||||
{
|
||||
public static GUIContent TextContent(string name, string tooltip)
|
||||
{
|
||||
var newContent = new GUIContent(name);
|
||||
newContent.tooltip = tooltip;
|
||||
return newContent;
|
||||
}
|
||||
|
||||
public static GUIContent TextContent(string name)
|
||||
{
|
||||
return new GUIContent(name);
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkAnimatorEditor : UnityEditor.Editor
|
||||
{
|
||||
private NetworkAnimator m_AnimSync;
|
||||
[NonSerialized] private bool m_Initialized;
|
||||
private SerializedProperty m_AnimatorProperty;
|
||||
private GUIContent m_AnimatorLabel;
|
||||
|
||||
private void Init()
|
||||
{
|
||||
if (m_Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Initialized = true;
|
||||
m_AnimSync = target as NetworkAnimator;
|
||||
|
||||
m_AnimatorProperty = serializedObject.FindProperty("m_Animator");
|
||||
m_AnimatorLabel = TextUtility.TextContent("Animator", "The Animator component to synchronize.");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Init();
|
||||
serializedObject.Update();
|
||||
DrawControls();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawControls()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_AnimSync.ResetParameterOptions();
|
||||
}
|
||||
|
||||
if (m_AnimSync.Animator == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controller = m_AnimSync.Animator.runtimeAnimatorController as AnimatorController;
|
||||
if (controller != null)
|
||||
{
|
||||
var showWarning = false;
|
||||
EditorGUI.indentLevel += 1;
|
||||
int i = 0;
|
||||
|
||||
foreach (var p in controller.parameters)
|
||||
{
|
||||
if (i >= NetworkAnimator.K_MaxAnimationParams)
|
||||
{
|
||||
showWarning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool oldSend = m_AnimSync.GetParameterAutoSend(i);
|
||||
bool send = EditorGUILayout.Toggle(p.name, oldSend);
|
||||
if (send != oldSend)
|
||||
{
|
||||
m_AnimSync.SetParameterAutoSend(i, send);
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (showWarning)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"NetworkAnimator can only select between the first {NetworkAnimator.K_MaxAnimationParams} parameters in a mecanim controller", MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,8 @@ namespace Unity.Netcode.Editor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
@@ -211,5 +213,134 @@ namespace Unity.Netcode.Editor
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once when a NetworkBehaviour component is
|
||||
/// displayed in the inspector view.
|
||||
/// </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)
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
return GetRootParentTransform(transform.parent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if a GameObject has one or more NetworkBehaviours but
|
||||
/// 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
|
||||
if (gameObject == null || (gameObject.GetComponent<NetworkBehaviour>() == null && gameObject.GetComponentInChildren<NetworkBehaviour>() == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (!rootTransform.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
// If we are removing a NetworkObject but there is still one or more NetworkBehaviour components
|
||||
// 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))
|
||||
{
|
||||
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.");
|
||||
}
|
||||
|
||||
// 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))
|
||||
{
|
||||
gameObject.AddComponent<NetworkObject>();
|
||||
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(activeScene);
|
||||
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ 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
|
||||
@@ -15,7 +19,6 @@ namespace Unity.Netcode.Editor
|
||||
private static GUIStyle s_HelpBoxStyle;
|
||||
|
||||
// Properties
|
||||
private SerializedProperty m_DontDestroyOnLoadProperty;
|
||||
private SerializedProperty m_RunInBackgroundProperty;
|
||||
private SerializedProperty m_LogLevelProperty;
|
||||
|
||||
@@ -58,7 +61,7 @@ namespace Unity.Netcode.Editor
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsSubclassOf(typeof(NetworkTransport)) && type.GetCustomAttributes(typeof(DontShowInTransportDropdownAttribute), true).Length == 0)
|
||||
if (type.IsSubclassOf(typeof(NetworkTransport)) && !type.IsSubclassOf(typeof(TestingNetworkTransport)) && type != typeof(TestingNetworkTransport))
|
||||
{
|
||||
m_TransportTypes.Add(type);
|
||||
}
|
||||
@@ -85,7 +88,6 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkManager = (NetworkManager)target;
|
||||
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
@@ -112,7 +114,6 @@ namespace Unity.Netcode.Editor
|
||||
private void CheckNullProperties()
|
||||
{
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
@@ -138,9 +139,13 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true);
|
||||
m_NetworkPrefabsList.elementHeightCallback = index =>
|
||||
{
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
var networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
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);
|
||||
};
|
||||
@@ -199,6 +204,7 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
@@ -208,22 +214,9 @@ 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();
|
||||
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty);
|
||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||
EditorGUILayout.Space();
|
||||
@@ -363,7 +356,7 @@ namespace Unity.Netcode.Editor
|
||||
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
||||
const string openDocsButtonText = "Open Docs";
|
||||
const string dismissButtonText = "Dismiss";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tutorials/goldenpath_series/goldenpath_foundation_module";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
|
||||
const string infoIconName = "console.infoicon";
|
||||
|
||||
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
||||
|
||||
194
Editor/NetworkManagerHelper.cs
Normal file
194
Editor/NetworkManagerHelper.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Specialized editor specific NetworkManager code
|
||||
/// </summary>
|
||||
public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper
|
||||
{
|
||||
internal static NetworkManagerHelper Singleton;
|
||||
|
||||
// This is primarily to handle IntegrationTest scenarios where more than 1 NetworkManager could exist
|
||||
private static Dictionary<NetworkManager, Transform> s_LastKnownNetworkManagerParents = new Dictionary<NetworkManager, Transform>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton instance and registers for:
|
||||
/// Hierarchy changed notification: to notify the user when they nest a NetworkManager
|
||||
/// Play mode state change notification: to capture when entering or exiting play mode (currently only exiting)
|
||||
/// </summary>
|
||||
[InitializeOnLoadMethod]
|
||||
private static void InitializeOnload()
|
||||
{
|
||||
Singleton = new NetworkManagerHelper();
|
||||
NetworkManager.NetworkManagerHelper = Singleton;
|
||||
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
|
||||
|
||||
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
|
||||
}
|
||||
|
||||
private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
|
||||
{
|
||||
switch (playModeStateChange)
|
||||
{
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Clear();
|
||||
ScenesInBuildActiveSceneCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects if a user is trying to enter into play mode when both conditions are true:
|
||||
/// - the currently active and open scene is not added to the scenes in build list
|
||||
/// - an instance of a NetworkManager with scene management enabled can be found
|
||||
/// If both conditions are met then the user is presented with a dialog box that
|
||||
/// provides the user with the option to add the scene to the scenes in build list
|
||||
/// before entering into play mode or the user can continue under those conditions.
|
||||
///
|
||||
/// ** When scene management is enabled the user should treat all scenes that need to
|
||||
/// be synchronized using network scene management as if they were preparing for a build.
|
||||
/// Any scene that needs to be loaded at run time has to be included in the scenes in
|
||||
/// build list. **
|
||||
/// </summary>
|
||||
private static void ScenesInBuildActiveSceneCheck()
|
||||
{
|
||||
var scenesList = EditorBuildSettings.scenes.ToList();
|
||||
var activeScene = SceneManager.GetActiveScene();
|
||||
var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1;
|
||||
var networkManager = Object.FindObjectOfType<NetworkManager>();
|
||||
if (!isSceneInBuildSettings && networkManager != null)
|
||||
{
|
||||
if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Add Scene to Scenes in Build", $"The current scene was not found in the scenes" +
|
||||
$" in build and a {nameof(NetworkManager)} instance was found with scene management enabled! Clients will not be able " +
|
||||
$"to synchronize to this scene unless it is added to the scenes in build list.\n\nWould you like to add it now?",
|
||||
"Yes", "No - Continue"))
|
||||
{
|
||||
scenesList.Add(new EditorBuildSettingsScene(activeScene.path, true));
|
||||
EditorBuildSettings.scenes = scenesList.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked only when the hierarchy changes
|
||||
/// </summary>
|
||||
private static void EditorApplication_hierarchyChanged()
|
||||
{
|
||||
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
|
||||
foreach (var networkManager in allNetworkManagers)
|
||||
{
|
||||
if (!networkManager.NetworkManagerCheckForParent())
|
||||
{
|
||||
Singleton.CheckAndNotifyUserNetworkObjectRemoved(networkManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles notifying users that they cannot add a NetworkObject component
|
||||
/// to a GameObject that also has a NetworkManager component. The NetworkObject
|
||||
/// will always be removed.
|
||||
/// GameObject + NetworkObject then NetworkManager = NetworkObject removed
|
||||
/// GameObject + NetworkManager then NetworkObject = NetworkObject removed
|
||||
/// Note: Since this is always invoked after <see cref="NetworkManagerCheckForParent"/>
|
||||
/// we do not need to check for parent when searching for a NetworkObject component
|
||||
/// </summary>
|
||||
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
|
||||
{
|
||||
// Check for any NetworkObject at the same gameObject relative layer
|
||||
|
||||
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>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!EditorApplication.isUpdating)
|
||||
{
|
||||
Object.DestroyImmediate(networkObject);
|
||||
|
||||
if (!EditorApplication.isPlaying && !editorTest)
|
||||
{
|
||||
EditorUtility.DisplayDialog($"Removing {nameof(NetworkObject)}", NetworkManagerAndNetworkObjectNotAllowedMessage(), "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(NetworkManagerAndNetworkObjectNotAllowedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string NetworkManagerAndNetworkObjectNotAllowedMessage()
|
||||
{
|
||||
return $"A {nameof(GameObject)} cannot have both a {nameof(NetworkManager)} and {nameof(NetworkObject)} assigned to it or any children under it.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
|
||||
/// When in edit mode it provides the option to automatically fix the issue
|
||||
/// When in play mode it just notifies the user when entering play mode as well as when the user
|
||||
/// tries to start a network session while a NetworkManager is still nested.
|
||||
/// </summary>
|
||||
public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false)
|
||||
{
|
||||
var gameObject = networkManager.gameObject;
|
||||
var transform = networkManager.transform;
|
||||
var isParented = transform.root != transform;
|
||||
|
||||
var message = NetworkManager.GenerateNestedNetworkManagerMessage(transform);
|
||||
if (s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && !ignoreNetworkManagerCache)
|
||||
{
|
||||
// If we have already notified the user, then don't notify them again
|
||||
if (s_LastKnownNetworkManagerParents[networkManager] == transform.root)
|
||||
{
|
||||
return isParented;
|
||||
}
|
||||
else // If we are no longer a child, then we can remove ourself from this list
|
||||
if (transform.root == gameObject.transform)
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Remove(networkManager);
|
||||
}
|
||||
}
|
||||
if (!EditorApplication.isUpdating && isParented)
|
||||
{
|
||||
if (!EditorApplication.isPlaying && !editorTest)
|
||||
{
|
||||
message += $"Click 'Auto-Fix' to automatically remove it from {transform.root.gameObject.name} or 'Manual-Fix' to fix it yourself in the hierarchy view.";
|
||||
if (EditorUtility.DisplayDialog("Invalid Nested NetworkManager", message, "Auto-Fix", "Manual-Fix"))
|
||||
{
|
||||
transform.parent = null;
|
||||
isParented = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(message);
|
||||
}
|
||||
|
||||
if (!s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && isParented)
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Add(networkManager, networkManager.transform.root);
|
||||
}
|
||||
}
|
||||
return isParented;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a32aeecf69a2542469927066f5b88005
|
||||
guid: b26b53dc28ae1b5488bbbecc3e499bbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -4,6 +4,9 @@ using UnityEditor;
|
||||
|
||||
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;
|
||||
@@ -100,5 +110,32 @@ namespace Unity.Netcode.Editor
|
||||
GUI.enabled = guiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
// Saved for use in OnDestroy
|
||||
private GameObject m_GameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once when a NetworkObject component is
|
||||
/// displayed in the inspector view.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
// We set the GameObject upon being enabled because when the
|
||||
// NetworkObject component is removed (i.e. when OnDestroy is invoked)
|
||||
// it is no longer valid/available.
|
||||
m_GameObject = (target as NetworkObject).gameObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once when a NetworkObject component is
|
||||
/// no longer displayed in the inspector view.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Since this is also invoked when a NetworkObject component is removed
|
||||
// from a GameObject, we go ahead and check for a NetworkObject when
|
||||
// this custom editor is destroyed.
|
||||
NetworkBehaviourEditor.CheckForNetworkObject(m_GameObject, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ using Unity.Netcode.Components;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkTransform))]
|
||||
/// <summary>
|
||||
/// The <see cref="CustomEditor"/> for <see cref="NetworkTransform"/>
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkTransform), true)]
|
||||
public class NetworkTransformEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty m_SyncPositionXProperty;
|
||||
@@ -28,6 +31,7 @@ namespace Unity.Netcode.Editor
|
||||
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));
|
||||
@@ -46,6 +50,7 @@ namespace Unity.Netcode.Editor
|
||||
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel);
|
||||
@@ -112,6 +117,7 @@ namespace Unity.Netcode.Editor
|
||||
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
||||
EditorGUILayout.PropertyField(m_InterpolateProperty);
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
// if rigidbody is present but network rigidbody is not present
|
||||
var go = ((NetworkTransform)target).gameObject;
|
||||
if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false)
|
||||
@@ -119,12 +125,15 @@ namespace Unity.Netcode.Editor
|
||||
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" +
|
||||
"Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning);
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
if (go.TryGetComponent<Rigidbody2D>(out _) && go.TryGetComponent<NetworkRigidbody2D>(out _) == false)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" +
|
||||
"Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning);
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS2D
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af81f9951b096ff4cb8e4f8a4106104a
|
||||
guid: a325130169714440ba1b4878082e8956
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
53
Editor/PackageChecker/UTPAdapterChecker.cs
Normal file
53
Editor/PackageChecker/UTPAdapterChecker.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
#if COM_UNITY_NETCODE_ADAPTER_UTP
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
|
||||
namespace Unity.Netcode.Editor.PackageChecker
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
internal class UTPAdapterChecker
|
||||
{
|
||||
private const string k_UTPAdapterPackageName = "com.unity.netcode.adapter.utp";
|
||||
|
||||
private static ListRequest s_ListRequest = null;
|
||||
|
||||
static UTPAdapterChecker()
|
||||
{
|
||||
if (s_ListRequest == null)
|
||||
{
|
||||
s_ListRequest = Client.List();
|
||||
EditorApplication.update += EditorUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EditorUpdate()
|
||||
{
|
||||
if (!s_ListRequest.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorApplication.update -= EditorUpdate;
|
||||
|
||||
if (s_ListRequest.Status == StatusCode.Success)
|
||||
{
|
||||
if (s_ListRequest.Result.Any(p => p.name == k_UTPAdapterPackageName))
|
||||
{
|
||||
Debug.Log($"({nameof(UTPAdapterChecker)}) Found UTP Adapter package, it is no longer needed, `UnityTransport` is now directly integrated into the SDK therefore removing it from the project.");
|
||||
Client.Remove(k_UTPAdapterPackageName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = s_ListRequest.Error;
|
||||
Debug.LogError($"({nameof(UTPAdapterChecker)}) Cannot check the list of packages -> error #{error.errorCode}: {error.message}");
|
||||
}
|
||||
|
||||
s_ListRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_NETCODE_ADAPTER_UTP
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
|
||||
guid: df5ed97df956b4aad91a221ba59fa304
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Unity.Netcode.Editor.PackageChecker",
|
||||
"rootNamespace": "Unity.Netcode.Editor.PackageChecker",
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.netcode.adapter.utp",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_NETCODE_ADAPTER_UTP"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78ac2a8d1365141f68da5d0a9e10dbc6
|
||||
guid: de64d7f9ca85d4bf59c8c24738bc1057
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
@@ -8,18 +8,31 @@
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
README.md
12
README.md
@@ -1,10 +1,16 @@
|
||||
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
||||
[](https://docs-multiplayer.unity3d.com/) [](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
# Netcode for GameObjects
|
||||
|
||||
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs.unity3d.com/Packages/com.unity.transport@1.0/manual/index.html).
|
||||
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
||||
[](https://docs-multiplayer.unity3d.com/netcode/current/about) [](https://docs-multiplayer.unity3d.com/netcode/current/api/introduction)
|
||||
|
||||
Netcode for GameObjects is a Unity package that provides networking capabilities to GameObject & MonoBehaviour workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs-multiplayer.unity3d.com/transport/current/about).
|
||||
|
||||
### Getting Started
|
||||
|
||||
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) guide for a taste of how to use the framework for basic networked tasks.
|
||||
|
||||
### Community and Feedback
|
||||
|
||||
For general questions, networking advice or discussions about Netcode for GameObjects, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/).
|
||||
|
||||
@@ -5,8 +5,11 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
#endif
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")]
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue with a fixed size
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queue</typeparam>
|
||||
public sealed class FixedQueue<T>
|
||||
{
|
||||
private readonly T[] m_Queue;
|
||||
private int m_QueueCount = 0;
|
||||
private int m_QueueStart;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of enqueued objects
|
||||
/// </summary>
|
||||
public int Count => m_QueueCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FixedQueue with a given size
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The size of the queue</param>
|
||||
public FixedQueue(int maxSize)
|
||||
{
|
||||
m_Queue = new T[maxSize];
|
||||
m_QueueStart = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an object
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
public bool Enqueue(T t)
|
||||
{
|
||||
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
|
||||
if (++m_QueueCount > m_Queue.Length)
|
||||
{
|
||||
--m_QueueCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (--m_QueueCount == -1)
|
||||
{
|
||||
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
|
||||
}
|
||||
|
||||
T res = m_Queue[m_QueueStart];
|
||||
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
}
|
||||
}
|
||||
@@ -53,9 +53,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>
|
||||
@@ -128,10 +140,10 @@ namespace Unity.Netcode
|
||||
public int LoadSceneTimeOut = 120;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
|
||||
/// The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
|
||||
public float MessageBufferTimeout = 20f;
|
||||
[Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")]
|
||||
public float SpawnTimeout = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable network logs.
|
||||
@@ -139,20 +151,15 @@ namespace Unity.Netcode
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
|
||||
/// The number of RTT samples that is kept as an average for calculations
|
||||
/// </summary>
|
||||
public bool UseSnapshotDelta { get; } = false;
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
|
||||
/// </summary>
|
||||
public bool UseSnapshotSpawn { get; } = false;
|
||||
/// <summary>
|
||||
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
|
||||
/// </summary>
|
||||
public int SnapshotMaxSpawnUsage { get; } = 1200;
|
||||
|
||||
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>
|
||||
@@ -224,7 +231,7 @@ namespace Unity.Netcode
|
||||
return m_ConfigHash.Value;
|
||||
}
|
||||
|
||||
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, int.MaxValue);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteValueSafe(ProtocolVersion);
|
||||
@@ -239,6 +246,8 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(sortedEntry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteValueSafe(TickRate);
|
||||
writer.WriteValueSafe(ConnectionApproval);
|
||||
writer.WriteValueSafe(ForceSamePrefabs);
|
||||
writer.WriteValueSafe(EnableSceneManagement);
|
||||
|
||||
@@ -5,6 +5,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal static class NetworkConstants
|
||||
{
|
||||
internal const string PROTOCOL_VERSION = "14.0.0";
|
||||
internal const string PROTOCOL_VERSION = "15.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,17 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// The NetworkObject's owned by this Client
|
||||
/// </summary>
|
||||
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
|
||||
public List<NetworkObject> OwnedObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PlayerObject != null && PlayerObject.NetworkManager != null && PlayerObject.NetworkManager.IsListening)
|
||||
{
|
||||
return PlayerObject.NetworkManager.SpawnManager.GetClientOwnedObjects(ClientId);
|
||||
}
|
||||
|
||||
return new List<NetworkObject>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
Runtime/Core/ComponentFactory.cs
Normal file
65
Runtime/Core/ComponentFactory.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to support testable code by allowing any supported component used by NetworkManager to be replaced
|
||||
/// with a mock component or a test version that overloads certain methods to change or record their behavior.
|
||||
/// Components currently supported by ComponentFactory:
|
||||
/// - IDeferredMessageManager
|
||||
/// </summary>
|
||||
internal static class ComponentFactory
|
||||
{
|
||||
internal delegate object CreateObjectDelegate(NetworkManager networkManager);
|
||||
|
||||
private static Dictionary<Type, CreateObjectDelegate> s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates an instance of a given interface
|
||||
/// </summary>
|
||||
/// <param name="networkManager">The network manager</param>
|
||||
/// <typeparam name="T">The interface to instantiate it with</typeparam>
|
||||
/// <returns></returns>
|
||||
public static T Create<T>(NetworkManager networkManager)
|
||||
{
|
||||
return (T)s_Delegates[typeof(T)](networkManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the default creation logic for a given interface type
|
||||
/// </summary>
|
||||
/// <param name="creator">The factory delegate to create the instance</param>
|
||||
/// <typeparam name="T">The interface type to override</typeparam>
|
||||
public static void Register<T>(CreateObjectDelegate creator)
|
||||
{
|
||||
s_Delegates[typeof(T)] = creator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts the creation logic for a given interface type to the default logic
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The interface type to revert</typeparam>
|
||||
public static void Deregister<T>()
|
||||
{
|
||||
s_Delegates.Remove(typeof(T));
|
||||
SetDefaults();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the default creation logic for all supported component types
|
||||
/// </summary>
|
||||
public static void SetDefaults()
|
||||
{
|
||||
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
|
||||
}
|
||||
|
||||
private static void SetDefault<T>(CreateObjectDelegate creator)
|
||||
{
|
||||
if (!s_Delegates.ContainsKey(typeof(T)))
|
||||
{
|
||||
s_Delegates[typeof(T)] = creator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Core/ComponentFactory.cs.meta
Normal file
3
Runtime/Core/ComponentFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fda4c0eb89644fcea5416bbf98ea0ba0
|
||||
timeCreated: 1649966562
|
||||
@@ -1,388 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct IndexAllocatorEntry
|
||||
{
|
||||
internal int Pos; // Position where the memory of this slot is
|
||||
internal int Length; // Length of the memory allocated to this slot
|
||||
internal int Next; // Next and Prev define the order of the slots in the buffer
|
||||
internal int Prev;
|
||||
internal bool Free; // Whether this is a free slot
|
||||
}
|
||||
|
||||
internal class IndexAllocator
|
||||
{
|
||||
private const int k_NotSet = -1;
|
||||
private readonly int m_MaxSlot; // Maximum number of sections (free or not) in the buffer
|
||||
private readonly int m_BufferSize; // Size of the buffer we allocated into
|
||||
private int m_LastSlot = 0; // Last allocated slot
|
||||
private IndexAllocatorEntry[] m_Slots; // Array of slots
|
||||
private int[] m_IndexToSlot; // Mapping from the client's index to the slot index
|
||||
|
||||
internal IndexAllocator(int bufferSize, int maxSlot)
|
||||
{
|
||||
m_MaxSlot = maxSlot;
|
||||
m_BufferSize = bufferSize;
|
||||
m_Slots = new IndexAllocatorEntry[m_MaxSlot];
|
||||
m_IndexToSlot = new int[m_MaxSlot];
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset this IndexAllocator to an empty one, with the same sized buffer and slots
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
// todo: could be made faster, for example by having a last index
|
||||
// and not needing valid stuff past it
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
m_Slots[i].Free = true;
|
||||
m_Slots[i].Next = i + 1;
|
||||
m_Slots[i].Prev = i - 1;
|
||||
m_Slots[i].Pos = m_BufferSize;
|
||||
m_Slots[i].Length = 0;
|
||||
|
||||
m_IndexToSlot[i] = k_NotSet;
|
||||
}
|
||||
|
||||
m_Slots[0].Pos = 0;
|
||||
m_Slots[0].Length = m_BufferSize;
|
||||
m_Slots[0].Prev = k_NotSet;
|
||||
m_Slots[m_MaxSlot - 1].Next = k_NotSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of memory used
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the amount of memory used, starting at 0, ending after the last used slot
|
||||
/// </returns>
|
||||
internal int Range
|
||||
{
|
||||
get
|
||||
{
|
||||
// when the whole buffer is free, m_LastSlot points to an empty slot
|
||||
if (m_Slots[m_LastSlot].Free)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// otherwise return the end of the last slot used
|
||||
return m_Slots[m_LastSlot].Pos + m_Slots[m_LastSlot].Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a slot with "size" position, for index "index"
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Used in Deallocate to identify which slot</param>
|
||||
/// <param name="size">The size required. </param>
|
||||
/// <param name="pos">Returns the position to use in the buffer </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't enough memory available or no slots are large enough
|
||||
/// </returns>
|
||||
internal bool Allocate(int index, int size, out int pos)
|
||||
{
|
||||
pos = 0;
|
||||
// size must be positive, index must be within range
|
||||
if (size < 0 || index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// refuse allocation if the index is already in use
|
||||
if (m_IndexToSlot[index] != k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: this is the slowest part
|
||||
// improvement 1: list of free blocks (minor)
|
||||
// improvement 2: heap of free blocks
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
if (m_Slots[i].Free && m_Slots[i].Length >= size)
|
||||
{
|
||||
m_IndexToSlot[index] = i;
|
||||
|
||||
int leftOver = m_Slots[i].Length - size;
|
||||
int next = m_Slots[i].Next;
|
||||
if (m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[next].Pos -= leftOver;
|
||||
m_Slots[next].Length += leftOver;
|
||||
}
|
||||
else
|
||||
{
|
||||
int add = MoveSlotAfter(i);
|
||||
|
||||
m_Slots[add].Pos = m_Slots[i].Pos + size;
|
||||
m_Slots[add].Length = m_Slots[i].Length - size;
|
||||
}
|
||||
|
||||
m_Slots[i].Free = false;
|
||||
m_Slots[i].Length = size;
|
||||
|
||||
pos = m_Slots[i].Pos;
|
||||
|
||||
// if we allocate past the current range, we are the last slot
|
||||
if (m_Slots[i].Pos + m_Slots[i].Length > Range)
|
||||
{
|
||||
m_LastSlot = i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deallocate a slot
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Same index used in Allocate </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't an allocated slot at this index
|
||||
/// </returns>
|
||||
internal bool Deallocate(int index)
|
||||
{
|
||||
// size must be positive, index must be within range
|
||||
if (index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int slot = m_IndexToSlot[index];
|
||||
|
||||
if (slot == k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[slot].Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Slots[slot].Free = true;
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
// if previous slot was free, merge and grow
|
||||
if (prev != k_NotSet && m_Slots[prev].Free)
|
||||
{
|
||||
m_Slots[prev].Length += m_Slots[slot].Length;
|
||||
m_Slots[slot].Length = 0;
|
||||
|
||||
// if the slot we're merging was the last one, the last one is now the one we merged with
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = prev;
|
||||
}
|
||||
|
||||
// todo: verify what this does on full or nearly full cases
|
||||
MoveSlotToEnd(slot);
|
||||
slot = prev;
|
||||
}
|
||||
|
||||
next = m_Slots[slot].Next;
|
||||
|
||||
// merge with next slot if it is free
|
||||
if (next != k_NotSet && m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[slot].Length += m_Slots[next].Length;
|
||||
m_Slots[next].Length = 0;
|
||||
MoveSlotToEnd(next);
|
||||
}
|
||||
|
||||
// if we just deallocate the last one, we need to move last back
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = m_Slots[m_LastSlot].Prev;
|
||||
// if there's nothing allocated anymore, use 0
|
||||
if (m_LastSlot == k_NotSet)
|
||||
{
|
||||
m_LastSlot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// mark the index as available
|
||||
m_IndexToSlot[index] = k_NotSet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Take a slot at the end and link it to go just after "slot".
|
||||
// Used when allocating part of a slot and we need an entry for the rest
|
||||
// Returns the slot that was picked
|
||||
private int MoveSlotAfter(int slot)
|
||||
{
|
||||
int ret = m_Slots[m_MaxSlot - 1].Prev;
|
||||
int p0 = m_Slots[ret].Prev;
|
||||
|
||||
m_Slots[p0].Next = m_MaxSlot - 1;
|
||||
m_Slots[m_MaxSlot - 1].Prev = p0;
|
||||
|
||||
int p1 = m_Slots[slot].Next;
|
||||
m_Slots[slot].Next = ret;
|
||||
m_Slots[p1].Prev = ret;
|
||||
|
||||
m_Slots[ret].Prev = slot;
|
||||
m_Slots[ret].Next = p1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Move the slot "slot" to the end of the list.
|
||||
// Used when merging two slots, that gives us an extra entry at the end
|
||||
private void MoveSlotToEnd(int slot)
|
||||
{
|
||||
// if we're already there
|
||||
if (m_Slots[slot].Next == k_NotSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
m_Slots[prev].Next = next;
|
||||
if (next != k_NotSet)
|
||||
{
|
||||
m_Slots[next].Prev = prev;
|
||||
}
|
||||
|
||||
int p0 = m_Slots[m_MaxSlot - 1].Prev;
|
||||
|
||||
m_Slots[p0].Next = slot;
|
||||
m_Slots[slot].Next = m_MaxSlot - 1;
|
||||
|
||||
m_Slots[m_MaxSlot - 1].Prev = slot;
|
||||
m_Slots[slot].Prev = p0;
|
||||
|
||||
m_Slots[slot].Pos = m_BufferSize;
|
||||
}
|
||||
|
||||
// runs a bunch of consistency check on the Allocator
|
||||
internal bool Verify()
|
||||
{
|
||||
int pos = k_NotSet;
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
int endPos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int prev = pos;
|
||||
if (pos != k_NotSet)
|
||||
{
|
||||
pos = m_Slots[pos].Next;
|
||||
if (pos == k_NotSet)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Prev != prev)
|
||||
{
|
||||
// the previous is not correct
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Length < 0)
|
||||
{
|
||||
// Length should be positive
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev != k_NotSet && m_Slots[prev].Free && m_Slots[pos].Free && m_Slots[pos].Length > 0)
|
||||
{
|
||||
// should not have two consecutive free slots
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Pos != total)
|
||||
{
|
||||
// slots should all line up nicely
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Slots[pos].Free)
|
||||
{
|
||||
endPos = m_Slots[pos].Pos + m_Slots[pos].Length;
|
||||
}
|
||||
|
||||
total += m_Slots[pos].Length;
|
||||
count++;
|
||||
|
||||
} while (pos != k_NotSet);
|
||||
|
||||
if (count != m_MaxSlot)
|
||||
{
|
||||
// some slots were lost
|
||||
return false;
|
||||
}
|
||||
|
||||
if (total != m_BufferSize)
|
||||
{
|
||||
// total buffer should be accounted for
|
||||
return false;
|
||||
}
|
||||
|
||||
if (endPos != Range)
|
||||
{
|
||||
// end position should match reported end position
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Debug display the allocator structure
|
||||
internal void DebugDisplay()
|
||||
{
|
||||
string logMessage = "IndexAllocator structure\n";
|
||||
|
||||
bool[] seen = new bool[m_MaxSlot];
|
||||
|
||||
int pos = 0;
|
||||
int count = 0;
|
||||
bool prevEmpty = false;
|
||||
do
|
||||
{
|
||||
seen[pos] = true;
|
||||
count++;
|
||||
if (m_Slots[pos].Length == 0 && prevEmpty)
|
||||
{
|
||||
// don't display repetitive empty slots
|
||||
}
|
||||
else
|
||||
{
|
||||
logMessage += string.Format("{0}:{1}, {2} ({3}) \n", m_Slots[pos].Pos, m_Slots[pos].Length,
|
||||
m_Slots[pos].Free ? "Free" : "Used", pos);
|
||||
if (m_Slots[pos].Length == 0)
|
||||
{
|
||||
prevEmpty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
pos = m_Slots[pos].Next;
|
||||
} while (pos != k_NotSet && !seen[pos]);
|
||||
|
||||
logMessage += string.Format("{0} Total entries\n", count);
|
||||
|
||||
Debug.Log(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,41 +15,56 @@ namespace Unity.Netcode
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal enum __RpcExecStage
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Client = 2
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
||||
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
[NonSerialized]
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
|
||||
#pragma warning restore 414 // restore assigned but its value is never used
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
private const int k_RpcMessageDefaultSize = 1024; // 1k
|
||||
private const int k_RpcMessageMaximumSize = 1024 * 64; // 64k
|
||||
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerRpcParams rpcParams, RpcDelivery delivery)
|
||||
#pragma warning restore 414 // restore assigned but its value is never used
|
||||
internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
|
||||
switch (delivery)
|
||||
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
var serverRpcMessage = new ServerRpcMessage
|
||||
{
|
||||
Metadata = new RpcMetadata
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourId = NetworkBehaviourId,
|
||||
NetworkRpcMethodId = rpcMethodId,
|
||||
},
|
||||
WriteBuffer = bufferWriter
|
||||
};
|
||||
|
||||
NetworkDelivery networkDelivery;
|
||||
switch (rpcDelivery)
|
||||
{
|
||||
default:
|
||||
case RpcDelivery.Reliable:
|
||||
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
break;
|
||||
case RpcDelivery.Unreliable:
|
||||
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
|
||||
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
|
||||
{
|
||||
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
||||
}
|
||||
@@ -57,41 +72,33 @@ namespace Unity.Netcode
|
||||
break;
|
||||
}
|
||||
|
||||
var message = new RpcMessage
|
||||
{
|
||||
Header = new RpcMessage.HeaderData
|
||||
{
|
||||
Type = RpcMessage.RpcType.Server,
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourId = NetworkBehaviourId,
|
||||
NetworkMethodId = rpcMethodId
|
||||
},
|
||||
RpcData = writer
|
||||
};
|
||||
|
||||
var rpcMessageSize = 0;
|
||||
var rpcWriteSize = 0;
|
||||
|
||||
// If we are a server/host then we just no op and send to ourself
|
||||
if (IsHost || IsServer)
|
||||
{
|
||||
using var tempBuffer = new FastBufferReader(writer, Allocator.Temp);
|
||||
using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp);
|
||||
var context = new NetworkContext
|
||||
{
|
||||
SenderId = NetworkManager.ServerClientId,
|
||||
Timestamp = Time.realtimeSinceStartup,
|
||||
SystemOwner = NetworkManager,
|
||||
// header information isn't valid since it's not a real message.
|
||||
// Passing false to canDefer prevents it being accessed.
|
||||
Header = new MessageHeader()
|
||||
// RpcMessage doesn't access this stuff so it's just left empty.
|
||||
Header = new MessageHeader(),
|
||||
SerializedHeaderSize = 0,
|
||||
MessageSize = 0
|
||||
};
|
||||
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
|
||||
rpcMessageSize = tempBuffer.Length;
|
||||
serverRpcMessage.ReadBuffer = tempBuffer;
|
||||
serverRpcMessage.Handle(ref context);
|
||||
rpcWriteSize = tempBuffer.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
rpcMessageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ServerClientId);
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId);
|
||||
}
|
||||
|
||||
bufferWriter.Dispose();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||
@@ -101,26 +108,44 @@ namespace Unity.Netcode
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcMessageSize);
|
||||
rpcWriteSize);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId, ClientRpcParams rpcParams, RpcDelivery delivery)
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
|
||||
switch (delivery)
|
||||
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
var clientRpcMessage = new ClientRpcMessage
|
||||
{
|
||||
Metadata = new RpcMetadata
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourId = NetworkBehaviourId,
|
||||
NetworkRpcMethodId = rpcMethodId,
|
||||
},
|
||||
WriteBuffer = bufferWriter
|
||||
};
|
||||
|
||||
NetworkDelivery networkDelivery;
|
||||
switch (rpcDelivery)
|
||||
{
|
||||
default:
|
||||
case RpcDelivery.Reliable:
|
||||
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
break;
|
||||
case RpcDelivery.Unreliable:
|
||||
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
|
||||
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
|
||||
{
|
||||
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
||||
}
|
||||
@@ -128,88 +153,135 @@ namespace Unity.Netcode
|
||||
break;
|
||||
}
|
||||
|
||||
var message = new RpcMessage
|
||||
{
|
||||
Header = new RpcMessage.HeaderData
|
||||
{
|
||||
Type = RpcMessage.RpcType.Client,
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourId = NetworkBehaviourId,
|
||||
NetworkMethodId = rpcMethodId
|
||||
},
|
||||
RpcData = writer
|
||||
};
|
||||
int messageSize;
|
||||
var rpcWriteSize = 0;
|
||||
|
||||
// We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC
|
||||
// to ourself. Sadly we have to figure that out from the list of clientIds :(
|
||||
bool shouldSendToHost = false;
|
||||
|
||||
if (rpcParams.Send.TargetClientIds != null)
|
||||
if (clientRpcParams.Send.TargetClientIds != null)
|
||||
{
|
||||
foreach (var clientId in rpcParams.Send.TargetClientIds)
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
if (targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to make sure we are sending to only observers, if not log an error.
|
||||
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
|
||||
{
|
||||
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
|
||||
}
|
||||
}
|
||||
|
||||
messageSize = NetworkManager.SendMessage(message, networkDelivery, in rpcParams.Send.TargetClientIds);
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
|
||||
}
|
||||
else if (rpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
{
|
||||
foreach (var clientId in rpcParams.Send.TargetClientIdsNativeArray)
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
if (targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to make sure we are sending to only observers, if not log an error.
|
||||
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
|
||||
{
|
||||
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
|
||||
}
|
||||
}
|
||||
|
||||
messageSize = NetworkManager.SendMessage(message, networkDelivery, rpcParams.Send.TargetClientIdsNativeArray.Value);
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldSendToHost = IsHost;
|
||||
messageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ConnectedClientsIds);
|
||||
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
|
||||
while (observerEnumerator.MoveNext())
|
||||
{
|
||||
// Skip over the host
|
||||
if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
continue;
|
||||
}
|
||||
rpcWriteSize = NetworkManager.MessagingSystem.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are a server/host then we just no op and send to ourself
|
||||
if (shouldSendToHost)
|
||||
{
|
||||
using var tempBuffer = new FastBufferReader(writer, Allocator.Temp);
|
||||
using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp);
|
||||
var context = new NetworkContext
|
||||
{
|
||||
SenderId = NetworkManager.ServerClientId,
|
||||
Timestamp = Time.realtimeSinceStartup,
|
||||
SystemOwner = NetworkManager,
|
||||
// header information isn't valid since it's not a real message.
|
||||
// Passing false to canDefer prevents it being accessed.
|
||||
Header = new MessageHeader()
|
||||
// RpcMessage doesn't access this stuff so it's just left empty.
|
||||
Header = new MessageHeader(),
|
||||
SerializedHeaderSize = 0,
|
||||
MessageSize = 0
|
||||
};
|
||||
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
|
||||
messageSize = tempBuffer.Length;
|
||||
clientRpcMessage.ReadBuffer = tempBuffer;
|
||||
clientRpcMessage.Handle(ref context);
|
||||
}
|
||||
|
||||
bufferWriter.Dispose();
|
||||
|
||||
#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(),
|
||||
messageSize);
|
||||
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
|
||||
}
|
||||
|
||||
internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId)
|
||||
{
|
||||
var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray);
|
||||
return $"Sending ClientRpc to non-observer! {containerNameHoldingId} contains clientId {targetClientId} that is not an observer!";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
||||
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
|
||||
@@ -217,50 +289,50 @@ namespace Unity.Netcode
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
||||
/// is the local player object. If no NetworkObject is assigned it will always return false.
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
||||
public bool IsLocalPlayer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkObject.IsOwner;
|
||||
public bool IsOwner { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected bool IsServer => IsRunning && NetworkManager.IsServer;
|
||||
protected bool IsServer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient => IsRunning && NetworkManager.IsClient;
|
||||
protected bool IsClient { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost => IsRunning && NetworkManager.IsHost;
|
||||
|
||||
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
|
||||
protected bool IsHost { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
||||
public bool IsOwnedByServer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
|
||||
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
|
||||
/// </summary>
|
||||
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false;
|
||||
public bool IsSpawned { get; internal set; }
|
||||
|
||||
internal bool IsBehaviourEditable()
|
||||
{
|
||||
// Only server can MODIFY. So allow modification if network is either not running or we are server
|
||||
return !m_NetworkObject ||
|
||||
(m_NetworkObject.NetworkManager == null ||
|
||||
!m_NetworkObject.NetworkManager.IsListening ||
|
||||
m_NetworkObject.NetworkManager.IsServer);
|
||||
m_NetworkObject.NetworkManager == null ||
|
||||
m_NetworkObject.NetworkManager.IsListening == false ||
|
||||
m_NetworkObject.NetworkManager.IsServer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -282,9 +354,18 @@ namespace Unity.Netcode
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
// ShutdownInProgress check:
|
||||
// This prevents an edge case scenario where the NetworkManager is shutting down but user code
|
||||
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
|
||||
// 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.
|
||||
// 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))
|
||||
{
|
||||
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
||||
}
|
||||
}
|
||||
|
||||
return m_NetworkObject;
|
||||
@@ -301,12 +382,12 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
||||
public ulong NetworkObjectId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
||||
/// </summary>
|
||||
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
public ushort NetworkBehaviourId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
|
||||
@@ -326,7 +407,47 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the ClientId that owns the NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
||||
public ulong OwnerClientId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates properties with network session related
|
||||
/// dependencies such as a NetworkObject's spawned
|
||||
/// state or NetworkManager's session state.
|
||||
/// </summary>
|
||||
internal void UpdateNetworkProperties()
|
||||
{
|
||||
// Set NetworkObject dependent properties
|
||||
if (NetworkObject != null)
|
||||
{
|
||||
// Set identification related properties
|
||||
NetworkObjectId = NetworkObject.NetworkObjectId;
|
||||
IsLocalPlayer = NetworkObject.IsLocalPlayer;
|
||||
|
||||
// This is "OK" because GetNetworkBehaviourOrderIndex uses the order of
|
||||
// NetworkObject.ChildNetworkBehaviours which is set once when first
|
||||
// accessed.
|
||||
NetworkBehaviourId = NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
|
||||
// Set ownership related properties
|
||||
IsOwnedByServer = NetworkObject.IsOwnedByServer;
|
||||
IsOwner = NetworkObject.IsOwner;
|
||||
OwnerClientId = NetworkObject.OwnerClientId;
|
||||
|
||||
// Set NetworkManager dependent properties
|
||||
if (NetworkManager != null)
|
||||
{
|
||||
IsHost = NetworkManager.IsListening && NetworkManager.IsHost;
|
||||
IsClient = NetworkManager.IsListening && NetworkManager.IsClient;
|
||||
IsServer = NetworkManager.IsListening && NetworkManager.IsServer;
|
||||
}
|
||||
}
|
||||
else // Shouldn't happen, but if so then set the properties to their default value;
|
||||
{
|
||||
OwnerClientId = NetworkObjectId = default;
|
||||
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = default;
|
||||
NetworkBehaviourId = default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
|
||||
@@ -340,12 +461,44 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InternalOnNetworkSpawn()
|
||||
{
|
||||
IsSpawned = true;
|
||||
InitializeVariables();
|
||||
UpdateNetworkProperties();
|
||||
}
|
||||
|
||||
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();
|
||||
try
|
||||
{
|
||||
OnNetworkDespawn();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -353,14 +506,27 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public virtual void OnGainedOwnership() { }
|
||||
|
||||
internal void InternalOnGainedOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
OnGainedOwnership();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when we loose ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnLostOwnership() { }
|
||||
|
||||
internal void InternalOnLostOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
OnLostOwnership();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -410,20 +576,17 @@ namespace Unity.Netcode
|
||||
|
||||
m_VarInit = true;
|
||||
|
||||
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
||||
|
||||
var sortedFields = GetFieldInfoForType(GetType());
|
||||
for (int i = 0; i < sortedFields.Length; i++)
|
||||
{
|
||||
Type fieldType = sortedFields[i].FieldType;
|
||||
|
||||
var fieldType = sortedFields[i].FieldType;
|
||||
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
||||
{
|
||||
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = (NetworkVariableBase)Activator.CreateInstance(fieldType, true);
|
||||
sortedFields[i].SetValue(this, instance);
|
||||
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
|
||||
}
|
||||
|
||||
instance.Initialize(this);
|
||||
@@ -468,16 +631,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 clientId)
|
||||
internal void PreVariableUpdate()
|
||||
{
|
||||
if (!m_VarInit)
|
||||
{
|
||||
@@ -485,67 +662,62 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
NetworkVariableUpdate(clientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong targetClientId)
|
||||
{
|
||||
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
||||
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||
|
||||
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)
|
||||
private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
|
||||
{
|
||||
if (!CouldHaveDirtyNetworkVariables())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
{
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
var networkVariable = NetworkVariableFields[k];
|
||||
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
|
||||
{
|
||||
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer))
|
||||
shouldSend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
TargetClientId = targetClientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
shouldSend = true;
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
else
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
ClientId = clientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
using (tmpWriter)
|
||||
{
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
|
||||
}
|
||||
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,15 +737,15 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
@@ -582,7 +754,7 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId);
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
|
||||
|
||||
if (canClientRead)
|
||||
{
|
||||
@@ -654,8 +826,21 @@ namespace Unity.Netcode
|
||||
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
|
||||
}
|
||||
|
||||
/// <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
|
||||
@@ -667,6 +852,7 @@ namespace Unity.Netcode
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[i].Dispose();
|
||||
|
||||
@@ -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,58 @@ 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].VariableUpdate(networkManager.ServerClientId);
|
||||
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)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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
@@ -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];
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class ConnectionRtt
|
||||
{
|
||||
private double[] m_RttSendTimes; // times at which packet were sent for RTT computations
|
||||
private int[] m_SendSequence; // tick, or other key, at which packets were sent (to allow matching)
|
||||
private double[] m_MeasuredLatencies; // measured latencies (ring buffer)
|
||||
private int m_LatenciesBegin = 0; // ring buffer begin
|
||||
private int m_LatenciesEnd = 0; // ring buffer end
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip-time data
|
||||
/// </summary>
|
||||
public struct Rtt
|
||||
{
|
||||
public double BestSec; // best RTT
|
||||
public double AverageSec; // average RTT
|
||||
public double WorstSec; // worst RTT
|
||||
public double LastSec; // latest ack'ed RTT
|
||||
public int SampleCount; // number of contributing samples
|
||||
}
|
||||
|
||||
public ConnectionRtt()
|
||||
{
|
||||
m_RttSendTimes = new double[NetworkConfig.RttWindowSize];
|
||||
m_SendSequence = new int[NetworkConfig.RttWindowSize];
|
||||
m_MeasuredLatencies = new double[NetworkConfig.RttWindowSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Round-trip-time computation for this client
|
||||
/// </summary>
|
||||
public Rtt GetRtt()
|
||||
{
|
||||
var ret = new Rtt();
|
||||
var index = m_LatenciesBegin;
|
||||
double total = 0.0;
|
||||
ret.BestSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
ret.WorstSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
|
||||
while (index != m_LatenciesEnd)
|
||||
{
|
||||
total += m_MeasuredLatencies[index];
|
||||
ret.SampleCount++;
|
||||
ret.BestSec = Math.Min(ret.BestSec, m_MeasuredLatencies[index]);
|
||||
ret.WorstSec = Math.Max(ret.WorstSec, m_MeasuredLatencies[index]);
|
||||
index = (index + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
|
||||
if (ret.SampleCount != 0)
|
||||
{
|
||||
ret.AverageSec = total / ret.SampleCount;
|
||||
// the latest RTT is one before m_LatenciesEnd
|
||||
ret.LastSec = m_MeasuredLatencies[(m_LatenciesEnd + (NetworkConfig.RttWindowSize - 1)) % NetworkConfig.RttWindowSize];
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.AverageSec = 0;
|
||||
ret.BestSec = 0;
|
||||
ret.WorstSec = 0;
|
||||
ret.SampleCount = 0;
|
||||
ret.LastSec = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal void NotifySend(int sequence, double timeSec)
|
||||
{
|
||||
m_RttSendTimes[sequence % NetworkConfig.RttWindowSize] = timeSec;
|
||||
m_SendSequence[sequence % NetworkConfig.RttWindowSize] = sequence;
|
||||
}
|
||||
|
||||
internal void NotifyAck(int sequence, double timeSec)
|
||||
{
|
||||
// if the same slot was not used by a later send
|
||||
if (m_SendSequence[sequence % NetworkConfig.RttWindowSize] == sequence)
|
||||
{
|
||||
double latency = timeSec - m_RttSendTimes[sequence % NetworkConfig.RttWindowSize];
|
||||
|
||||
m_MeasuredLatencies[m_LatenciesEnd] = latency;
|
||||
m_LatenciesEnd = (m_LatenciesEnd + 1) % NetworkConfig.RttAverageSamples;
|
||||
|
||||
if (m_LatenciesEnd == m_LatenciesBegin)
|
||||
{
|
||||
m_LatenciesBegin = (m_LatenciesBegin + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c275febadb27c4d18b41218e3353b84b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,24 @@ namespace Unity.Netcode
|
||||
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
|
||||
|
||||
// internal logging
|
||||
internal static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
|
||||
internal static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
|
||||
internal static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
|
||||
|
||||
/// <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>
|
||||
/// Logs an info log locally and on the server if possible.
|
||||
@@ -62,10 +77,9 @@ namespace Unity.Netcode
|
||||
LogType = logType,
|
||||
Message = message
|
||||
};
|
||||
var size = NetworkManager.Singleton.SendMessage(networkMessage, NetworkDelivery.ReliableFragmentedSequenced,
|
||||
NetworkManager.Singleton.ServerClientId);
|
||||
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId);
|
||||
|
||||
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size);
|
||||
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Header placed at the start of each message batch
|
||||
/// </summary>
|
||||
internal struct BatchHeader
|
||||
internal struct BatchHeader : INetworkSerializeByMemcpy
|
||||
{
|
||||
/// <summary>
|
||||
/// Total number of messages in the batch.
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public event UnnamedMessageDelegate OnUnnamedMessage;
|
||||
|
||||
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader)
|
||||
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize)
|
||||
{
|
||||
if (OnUnnamedMessage != null)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace Unity.Netcode
|
||||
((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
|
||||
}
|
||||
}
|
||||
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,9 +73,9 @@ namespace Unity.Netcode
|
||||
|
||||
var message = new UnnamedMessage
|
||||
{
|
||||
Data = messageBuffer
|
||||
SendData = messageBuffer
|
||||
};
|
||||
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
|
||||
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
|
||||
|
||||
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
|
||||
if (size != 0)
|
||||
@@ -94,9 +94,9 @@ namespace Unity.Netcode
|
||||
{
|
||||
var message = new UnnamedMessage
|
||||
{
|
||||
Data = messageBuffer
|
||||
SendData = messageBuffer
|
||||
};
|
||||
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId);
|
||||
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId);
|
||||
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
|
||||
if (size != 0)
|
||||
{
|
||||
@@ -115,9 +115,9 @@ namespace Unity.Netcode
|
||||
private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>();
|
||||
private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>();
|
||||
|
||||
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader)
|
||||
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader, int serializedHeaderSize)
|
||||
{
|
||||
var bytesCount = reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
var bytesCount = reader.Length + serializedHeaderSize;
|
||||
|
||||
if (m_NetworkManager == null)
|
||||
{
|
||||
@@ -193,6 +193,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)
|
||||
@@ -223,9 +224,9 @@ namespace Unity.Netcode
|
||||
var message = new NamedMessage
|
||||
{
|
||||
Hash = hash,
|
||||
Data = messageStream
|
||||
SendData = messageStream,
|
||||
};
|
||||
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId);
|
||||
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId);
|
||||
|
||||
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
|
||||
if (size != 0)
|
||||
@@ -238,7 +239,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)
|
||||
@@ -266,9 +267,9 @@ namespace Unity.Netcode
|
||||
var message = new NamedMessage
|
||||
{
|
||||
Hash = hash,
|
||||
Data = messageStream
|
||||
SendData = messageStream
|
||||
};
|
||||
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
|
||||
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
|
||||
|
||||
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
|
||||
if (size != 0)
|
||||
|
||||
149
Runtime/Messaging/DeferredMessageManager.cs
Normal file
149
Runtime/Messaging/DeferredMessageManager.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Time = UnityEngine.Time;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class DeferredMessageManager : IDeferredMessageManager
|
||||
{
|
||||
protected struct TriggerData
|
||||
{
|
||||
public FastBufferReader Reader;
|
||||
public MessageHeader Header;
|
||||
public ulong SenderId;
|
||||
public float Timestamp;
|
||||
public int SerializedHeaderSize;
|
||||
}
|
||||
protected struct TriggerInfo
|
||||
{
|
||||
public float Expiry;
|
||||
public NativeList<TriggerData> TriggerData;
|
||||
}
|
||||
|
||||
protected readonly Dictionary<IDeferredMessageManager.TriggerType, Dictionary<ulong, TriggerInfo>> m_Triggers = new Dictionary<IDeferredMessageManager.TriggerType, Dictionary<ulong, TriggerInfo>>();
|
||||
|
||||
private readonly NetworkManager m_NetworkManager;
|
||||
|
||||
internal DeferredMessageManager(NetworkManager networkManager)
|
||||
{
|
||||
m_NetworkManager = networkManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||
///
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
public virtual unsafe void DeferMessage(IDeferredMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!m_Triggers.TryGetValue(trigger, out var triggers))
|
||||
{
|
||||
triggers = new Dictionary<ulong, TriggerInfo>();
|
||||
m_Triggers[trigger] = triggers;
|
||||
}
|
||||
|
||||
if (!triggers.TryGetValue(key, out var triggerInfo))
|
||||
{
|
||||
triggerInfo = new TriggerInfo
|
||||
{
|
||||
Expiry = Time.realtimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
|
||||
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
||||
};
|
||||
triggers[key] = triggerInfo;
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Add(new TriggerData
|
||||
{
|
||||
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
||||
Header = context.Header,
|
||||
Timestamp = context.Timestamp,
|
||||
SenderId = context.SenderId,
|
||||
SerializedHeaderSize = context.SerializedHeaderSize
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
public virtual unsafe void CleanupStaleTriggers()
|
||||
{
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
ulong* staleKeys = stackalloc ulong[kvp.Value.Count];
|
||||
int index = 0;
|
||||
foreach (var kvp2 in kvp.Value)
|
||||
{
|
||||
if (kvp2.Value.Expiry < Time.realtimeSinceStartup)
|
||||
{
|
||||
staleKeys[index++] = kvp2.Key;
|
||||
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < index; ++i)
|
||||
{
|
||||
kvp.Value.Remove(staleKeys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void PurgeTrigger(IDeferredMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Deferred messages were received for a trigger of type {triggerType} with key {key}, but that trigger was not received within within {m_NetworkManager.NetworkConfig.SpawnTimeout} second(s).");
|
||||
}
|
||||
|
||||
foreach (var data in triggerInfo.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Dispose();
|
||||
}
|
||||
|
||||
public virtual void ProcessTriggers(IDeferredMessageManager.TriggerType trigger, ulong key)
|
||||
{
|
||||
if (m_Triggers.TryGetValue(trigger, out var triggers))
|
||||
{
|
||||
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
|
||||
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
||||
if (triggers.TryGetValue(key, out var triggerInfo))
|
||||
{
|
||||
foreach (var deferredMessage in triggerInfo.TriggerData)
|
||||
{
|
||||
// Reader will be disposed within HandleMessage
|
||||
m_NetworkManager.MessagingSystem.HandleMessage(deferredMessage.Header, deferredMessage.Reader, deferredMessage.SenderId, deferredMessage.Timestamp, deferredMessage.SerializedHeaderSize);
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Dispose();
|
||||
triggers.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
public virtual void CleanupAllTriggers()
|
||||
{
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
foreach (var kvp2 in kvp.Value)
|
||||
{
|
||||
foreach (var data in kvp2.Value.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
kvp2.Value.TriggerData.Dispose();
|
||||
}
|
||||
}
|
||||
m_Triggers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Messaging/DeferredMessageManager.cs.meta
Normal file
3
Runtime/Messaging/DeferredMessageManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac7f57f7d16a46e2aba65558e873727f
|
||||
timeCreated: 1649799187
|
||||
35
Runtime/Messaging/IDeferredMessageManager.cs
Normal file
35
Runtime/Messaging/IDeferredMessageManager.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal interface IDeferredMessageManager
|
||||
{
|
||||
internal enum TriggerType
|
||||
{
|
||||
OnSpawn,
|
||||
OnAddPrefab,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||
///
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
void CleanupStaleTriggers();
|
||||
|
||||
void ProcessTriggers(TriggerType trigger, ulong key);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
void CleanupAllTriggers();
|
||||
}
|
||||
}
|
||||
3
Runtime/Messaging/IDeferredMessageManager.cs.meta
Normal file
3
Runtime/Messaging/IDeferredMessageManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fb73a029c314763a04ebb015a07664d
|
||||
timeCreated: 1649966331
|
||||
@@ -13,18 +13,18 @@ namespace Unity.Netcode
|
||||
/// Called before an individual message is sent.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The destination clientId</param>
|
||||
/// <param name="messageType">The type of the message being sent</param>
|
||||
/// <param name="message">The message being sent</param>
|
||||
/// <param name="delivery"></param>
|
||||
void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery);
|
||||
void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Called after an individual message is sent.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The destination clientId</param>
|
||||
/// <param name="messageType">The type of the message being sent</param>
|
||||
/// <param name="message">The message being sent</param>
|
||||
/// <param name="delivery"></param>
|
||||
/// <param name="messageSizeBytes">Number of bytes in the message, not including the message header</param>
|
||||
void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes);
|
||||
void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Called before an individual message is received.
|
||||
@@ -91,7 +91,27 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="senderId">The source clientId</param>
|
||||
/// <param name="messageType">The type of the message</param>
|
||||
/// <param name="messageContent">The FastBufferReader containing the message</param>
|
||||
/// <param name="context">The NetworkContext the message is being processed in</param>
|
||||
/// <returns></returns>
|
||||
bool OnVerifyCanReceive(ulong senderId, Type messageType);
|
||||
bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called after a message is serialized, but before it's handled.
|
||||
/// Differs from OnBeforeReceiveMessage in that the actual message object is passed and can be inspected.
|
||||
/// </summary>
|
||||
/// <param name="message">The message object</param>
|
||||
/// <param name="context">The network context the message is being ahandled in</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Called after a message is serialized and handled.
|
||||
/// Differs from OnAfterReceiveMessage in that the actual message object is passed and can be inspected.
|
||||
/// </summary>
|
||||
/// <param name="message">The message object</param>
|
||||
/// <param name="context">The network context the message is being ahandled in</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Unity.Netcode
|
||||
/// static message handler for receiving messages of the following name and signature:
|
||||
///
|
||||
/// <code>
|
||||
/// public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
/// public static void Receive(FastBufferReader reader, ref NetworkContext context)
|
||||
/// </code>
|
||||
///
|
||||
/// It is the responsibility of the Serialize and Receive methods to ensure there is enough buffer space
|
||||
@@ -40,10 +40,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal interface INetworkMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to serialize the message.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
void Serialize(FastBufferWriter writer);
|
||||
bool Deserialize(FastBufferReader reader, ref NetworkContext context);
|
||||
void Handle(ref NetworkContext context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// This is the header data that's serialized to the network when sending an <see cref="INetworkMessage"/>
|
||||
/// </summary>
|
||||
internal struct MessageHeader
|
||||
internal struct MessageHeader : INetworkSerializeByMemcpy
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte representation of the message type. This is automatically assigned to each message
|
||||
@@ -11,11 +11,12 @@ namespace Unity.Netcode
|
||||
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be
|
||||
/// calculated differently.
|
||||
/// </summary>
|
||||
public byte MessageType;
|
||||
public uint MessageType;
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the message, NOT including the header.
|
||||
/// Stored as a uint to avoid zig-zag encoding, but capped at int.MaxValue.
|
||||
/// </summary>
|
||||
public ushort MessageSize;
|
||||
public uint MessageSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ChangeOwnershipMessage : INetworkMessage
|
||||
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
@@ -10,40 +10,53 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(this);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
reader.ReadValueSafe(out ChangeOwnershipMessage message);
|
||||
message.Handle(reader, context, context.SenderId, networkManager, reader.Length);
|
||||
reader.ReadValueSafe(out this);
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(FastBufferReader reader, in NetworkContext context, ulong senderId, NetworkManager networkManager, int messageSize)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (networkObject.OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are current owner.
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
var originalOwner = networkObject.OwnerClientId;
|
||||
|
||||
networkObject.OwnerClientId = OwnerClientId;
|
||||
|
||||
// We are current owner.
|
||||
if (originalOwner == networkManager.LocalClientId)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
// We are new owner.
|
||||
if (OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are new owner.
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(senderId, networkObject, messageSize);
|
||||
// For all other clients that are neither the former or current owner, update the behaviours' properties
|
||||
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
|
||||
{
|
||||
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties();
|
||||
}
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,27 @@ namespace Unity.Netcode
|
||||
{
|
||||
public ulong OwnerClientId;
|
||||
public int NetworkTick;
|
||||
public int SceneObjectCount;
|
||||
|
||||
// Not serialized, held as references to serialize NetworkVariable data
|
||||
public HashSet<NetworkObject> SpawnedObjectsList;
|
||||
|
||||
private FastBufferReader m_ReceivedSceneObjectData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int)))
|
||||
{
|
||||
throw new OverflowException(
|
||||
$"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
|
||||
throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
|
||||
}
|
||||
writer.WriteValue(OwnerClientId);
|
||||
writer.WriteValue(NetworkTick);
|
||||
writer.WriteValue(SceneObjectCount);
|
||||
|
||||
if (SceneObjectCount != 0)
|
||||
uint sceneObjectCount = 0;
|
||||
if (SpawnedObjectsList != null)
|
||||
{
|
||||
var pos = writer.Position;
|
||||
writer.Seek(writer.Position + FastBufferWriter.GetWriteSize(sceneObjectCount));
|
||||
|
||||
// Serialize NetworkVariable data
|
||||
foreach (var sobj in SpawnedObjectsList)
|
||||
{
|
||||
@@ -33,34 +36,41 @@ namespace Unity.Netcode
|
||||
sobj.Observers.Add(OwnerClientId);
|
||||
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);
|
||||
sceneObject.Serialize(writer);
|
||||
++sceneObjectCount;
|
||||
}
|
||||
}
|
||||
writer.Seek(pos);
|
||||
writer.WriteValue(sceneObjectCount);
|
||||
writer.Seek(writer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(sceneObjectCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int)))
|
||||
{
|
||||
throw new OverflowException(
|
||||
$"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
|
||||
throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
|
||||
}
|
||||
|
||||
var message = new ConnectionApprovedMessage();
|
||||
reader.ReadValue(out message.OwnerClientId);
|
||||
reader.ReadValue(out message.NetworkTick);
|
||||
reader.ReadValue(out message.SceneObjectCount);
|
||||
message.Handle(reader, context.SenderId, networkManager);
|
||||
reader.ReadValue(out OwnerClientId);
|
||||
reader.ReadValue(out NetworkTick);
|
||||
m_ReceivedSceneObjectData = reader;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(FastBufferReader reader, ulong clientId, NetworkManager networkManager)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
networkManager.LocalClientId = OwnerClientId;
|
||||
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
|
||||
|
||||
@@ -69,25 +79,27 @@ 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);
|
||||
|
||||
// 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.
|
||||
for (ushort i = 0; i < SceneObjectCount; i++)
|
||||
for (ushort i = 0; i < sceneObjectCount; i++)
|
||||
{
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
sceneObject.Deserialize(reader);
|
||||
NetworkObject.AddSceneObject(sceneObject, reader, networkManager);
|
||||
sceneObject.Deserialize(m_ReceivedSceneObjectData);
|
||||
NetworkObject.AddSceneObject(sceneObject, m_ReceivedSceneObjectData, networkManager);
|
||||
}
|
||||
|
||||
// Mark the client being connected
|
||||
networkManager.IsConnectedClient = true;
|
||||
// When scene management is disabled we notify after everything is synchronized
|
||||
networkManager.InvokeOnClientConnectedCallback(clientId);
|
||||
networkManager.InvokeOnClientConnectedCallback(context.SenderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = new ConnectionRequestMessage();
|
||||
if (networkManager.NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash) +
|
||||
FastBufferWriter.GetWriteSize<int>()))
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>()))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -41,11 +39,12 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.DisconnectClient(context.SenderId);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
reader.ReadValue(out message.ConfigHash);
|
||||
|
||||
if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash))
|
||||
reader.ReadValue(out ConfigHash);
|
||||
|
||||
if (!networkManager.NetworkConfig.CompareConfig(ConfigHash))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -53,14 +52,14 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.DisconnectClient(context.SenderId);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out message.ConnectionData);
|
||||
reader.ReadValueSafe(out ConnectionData);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash)))
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash)))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -68,11 +67,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.DisconnectClient(context.SenderId);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
reader.ReadValue(out message.ConfigHash);
|
||||
reader.ReadValue(out ConfigHash);
|
||||
|
||||
if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash))
|
||||
if (!networkManager.NetworkConfig.CompareConfig(ConfigHash))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -80,14 +79,18 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.DisconnectClient(context.SenderId);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
message.Handle(networkManager, context.SenderId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(NetworkManager networkManager, ulong senderId)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var senderId = context.SenderId;
|
||||
|
||||
if (networkManager.PendingClients.TryGetValue(senderId, out PendingClient client))
|
||||
{
|
||||
// Set to pending approval to prevent future connection requests from being approved
|
||||
@@ -98,17 +101,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,38 @@ namespace Unity.Netcode
|
||||
internal struct CreateObjectMessage : INetworkMessage
|
||||
{
|
||||
public NetworkObject.SceneObject ObjectInfo;
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
ObjectInfo.Serialize(writer);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
var message = new CreateObjectMessage();
|
||||
message.ObjectInfo.Deserialize(reader);
|
||||
message.Handle(context.SenderId, reader, networkManager);
|
||||
|
||||
ObjectInfo.Deserialize(reader);
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, FastBufferReader reader, NetworkManager networkManager)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, reader, networkManager);
|
||||
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, reader.Length);
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, m_ReceivedNetworkVariableData, networkManager);
|
||||
|
||||
networkManager.NetworkMetrics.TrackObjectSpawnReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct DestroyObjectMessage : INetworkMessage
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe(this);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
reader.ReadValueSafe(out DestroyObjectMessage message);
|
||||
message.Handle(context.SenderId, networkManager, reader.Length);
|
||||
|
||||
reader.ReadValueSafe(out this);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
// This is the same check and log message that happens inside OnDespawnObject, but we have to do it here
|
||||
// while we still have access to the network ID, otherwise the log message will be less useful.
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Trying to destroy {nameof(NetworkObject)} #{NetworkObjectId} but it does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(senderId, networkObject, messageSize);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,26 @@ namespace Unity.Netcode
|
||||
internal struct NamedMessage : INetworkMessage
|
||||
{
|
||||
public ulong Hash;
|
||||
public FastBufferWriter Data;
|
||||
public FastBufferWriter SendData;
|
||||
|
||||
private FastBufferReader m_ReceiveData;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe(Hash);
|
||||
writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length);
|
||||
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var message = new NamedMessage();
|
||||
reader.ReadValueSafe(out message.Hash);
|
||||
reader.ReadValueSafe(out Hash);
|
||||
m_ReceiveData = reader;
|
||||
return true;
|
||||
}
|
||||
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader);
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,27 +16,29 @@ namespace Unity.Netcode
|
||||
public ushort NetworkBehaviourIndex;
|
||||
|
||||
public HashSet<int> DeliveryMappedNetworkVariableIndex;
|
||||
public ulong ClientId;
|
||||
public ulong TargetClientId;
|
||||
public NetworkBehaviour NetworkBehaviour;
|
||||
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) +
|
||||
FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
|
||||
{
|
||||
throw new OverflowException(
|
||||
$"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
|
||||
writer.WriteValue(NetworkObjectId);
|
||||
writer.WriteValue(NetworkBehaviourIndex);
|
||||
for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++)
|
||||
|
||||
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(k))
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
|
||||
{
|
||||
// This var does not belong to the currently iterating delivery group.
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
writer.WriteValueSafe((short)0);
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -46,15 +48,25 @@ namespace Unity.Netcode
|
||||
continue;
|
||||
}
|
||||
|
||||
// if I'm dirty AND a client, write (server always has all permissions)
|
||||
// if I'm dirty AND the server AND the client can read me, send.
|
||||
bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer);
|
||||
var startingSize = writer.Length;
|
||||
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
|
||||
var shouldWrite = networkVariable.IsDirty() &&
|
||||
networkVariable.CanClientRead(TargetClientId) &&
|
||||
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
|
||||
|
||||
// Prevent the server from writing to the client that owns a given NetworkVariable
|
||||
// Allowing the write would send an old value to the client and cause jitter
|
||||
if (networkVariable.WritePerm == NetworkVariableWritePermission.Owner &&
|
||||
networkVariable.OwnerClientId() == TargetClientId)
|
||||
{
|
||||
shouldWrite = false;
|
||||
}
|
||||
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
BytePacker.WriteValueBitPacked(writer, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -66,56 +78,62 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue);
|
||||
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter);
|
||||
var tempWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
|
||||
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
|
||||
|
||||
writer.WriteValueSafe((ushort)tmpWriter.Length);
|
||||
tmpWriter.CopyTo(writer);
|
||||
if (!writer.TryBeginWrite(tempWriter.Length))
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
|
||||
tempWriter.CopyTo(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(writer);
|
||||
networkVariable.WriteDelta(writer);
|
||||
}
|
||||
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k))
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(k);
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
|
||||
}
|
||||
|
||||
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
||||
ClientId,
|
||||
TargetClientId,
|
||||
NetworkBehaviour.NetworkObject,
|
||||
NetworkBehaviour.NetworkVariableFields[k].Name,
|
||||
networkVariable.Name,
|
||||
NetworkBehaviour.__getTypeName(),
|
||||
writer.Length);
|
||||
writer.Length - startingSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
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);
|
||||
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
|
||||
var message = new NetworkVariableDeltaMessage();
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.NetworkObjectId) +
|
||||
FastBufferWriter.GetWriteSize(message.NetworkBehaviourIndex)))
|
||||
{
|
||||
throw new OverflowException(
|
||||
$"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
reader.ReadValue(out message.NetworkObjectId);
|
||||
reader.ReadValue(out message.NetworkBehaviourIndex);
|
||||
message.Handle(context.SenderId, reader, context, networkManager);
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
|
||||
{
|
||||
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
|
||||
{
|
||||
NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||
|
||||
if (behaviour == null)
|
||||
if (networkBehaviour == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -124,13 +142,12 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
|
||||
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
int varSize = 0;
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
reader.ReadValueSafe(out varSize);
|
||||
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
|
||||
|
||||
if (varSize == 0)
|
||||
{
|
||||
@@ -139,25 +156,27 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.ReadValueSafe(out bool deltaExists);
|
||||
m_ReceivedNetworkVariableData.ReadValueSafe(out bool deltaExists);
|
||||
if (!deltaExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkManager.IsServer)
|
||||
var networkVariable = networkBehaviour.NetworkVariableFields[i];
|
||||
|
||||
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
|
||||
{
|
||||
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
|
||||
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||
}
|
||||
|
||||
reader.Seek(reader.Position + varSize);
|
||||
m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -168,47 +187,45 @@ namespace Unity.Netcode
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
|
||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
int readStartPos = reader.Position;
|
||||
int readStartPos = m_ReceivedNetworkVariableData.Position;
|
||||
|
||||
behaviour.NetworkVariableFields[i].ReadDelta(reader, networkManager.IsServer);
|
||||
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
||||
|
||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
|
||||
senderId,
|
||||
context.SenderId,
|
||||
networkObject,
|
||||
behaviour.NetworkVariableFields[i].Name,
|
||||
behaviour.__getTypeName(),
|
||||
reader.Length);
|
||||
networkVariable.Name,
|
||||
networkBehaviour.__getTypeName(),
|
||||
context.MessageSize);
|
||||
|
||||
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (reader.Position > (readStartPos + varSize))
|
||||
if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
$"Var delta read too far. {reader.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
}
|
||||
|
||||
reader.Seek(readStartPos + varSize);
|
||||
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
|
||||
}
|
||||
else if (reader.Position < (readStartPos + varSize))
|
||||
else if (m_ReceivedNetworkVariableData.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
$"Var delta read too little. {(readStartPos + varSize) - reader.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogWarning($"Var delta read too little. {readStartPos + varSize - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
}
|
||||
|
||||
reader.Seek(readStartPos + varSize);
|
||||
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,7 +233,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
Runtime/Messaging/Messages/OrderingMessage.cs
Normal file
50
Runtime/Messaging/Messages/OrderingMessage.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides
|
||||
/// have the same message types in the same positions in
|
||||
/// - MessagingSystem.m_MessageHandlers
|
||||
/// - MessagingSystem.m_ReverseTypeMap
|
||||
/// even if one side has extra messages (compilation, version, patch, or platform differences, etc...)
|
||||
///
|
||||
/// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning
|
||||
/// of the mapping, to guarantee they can be exchanged before the two sides share their ordering
|
||||
/// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time
|
||||
/// </summary>
|
||||
internal struct OrderingMessage : INetworkMessage
|
||||
{
|
||||
public int Order;
|
||||
public uint Hash;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
|
||||
}
|
||||
|
||||
writer.WriteValue(Order);
|
||||
writer.WriteValue(Hash);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
|
||||
{
|
||||
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
|
||||
}
|
||||
|
||||
reader.ReadValue(out Order);
|
||||
reader.ReadValue(out Hash);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8514b4eca0c7044d9b92faf9407ec93
|
||||
guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,10 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ParentSyncMessage : INetworkMessage
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
|
||||
public bool IsReparented;
|
||||
public bool WorldPositionStays;
|
||||
|
||||
//If(Metadata.IsReparented)
|
||||
public bool IsLatestParentSet;
|
||||
@@ -12,56 +14,93 @@ namespace Unity.Netcode
|
||||
//If(IsLatestParentSet)
|
||||
public ulong? LatestParent;
|
||||
|
||||
// Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
|
||||
public bool RemoveParent;
|
||||
|
||||
// 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)
|
||||
{
|
||||
writer.WriteValueSafe(NetworkObjectId);
|
||||
writer.WriteValueSafe(IsReparented);
|
||||
if (IsReparented)
|
||||
BytePacker.WriteValuePacked(writer, NetworkObjectId);
|
||||
writer.WriteValueSafe(RemoveParent);
|
||||
writer.WriteValueSafe(WorldPositionStays);
|
||||
if (!RemoveParent)
|
||||
{
|
||||
writer.WriteValueSafe(IsLatestParentSet);
|
||||
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
writer.WriteValueSafe((ulong)LatestParent);
|
||||
BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent);
|
||||
}
|
||||
}
|
||||
|
||||
// Whether parenting or removing a parent, we always update the position, rotation, and scale
|
||||
writer.WriteValueSafe(Position);
|
||||
writer.WriteValueSafe(Rotation);
|
||||
writer.WriteValueSafe(Scale);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = new ParentSyncMessage();
|
||||
reader.ReadValueSafe(out message.NetworkObjectId);
|
||||
reader.ReadValueSafe(out message.IsReparented);
|
||||
if (message.IsReparented)
|
||||
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId);
|
||||
reader.ReadValueSafe(out RemoveParent);
|
||||
reader.ReadValueSafe(out WorldPositionStays);
|
||||
if (!RemoveParent)
|
||||
{
|
||||
reader.ReadValueSafe(out message.IsLatestParentSet);
|
||||
if (message.IsLatestParentSet)
|
||||
reader.ReadValueSafe(out IsLatestParentSet);
|
||||
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
message.LatestParent = latestParent;
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
}
|
||||
|
||||
message.Handle(reader, context, networkManager);
|
||||
// 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;
|
||||
}
|
||||
|
||||
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
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)
|
||||
{
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
networkObject.SetNetworkParenting(IsReparented, LatestParent);
|
||||
networkObject.ApplyNetworkParenting();
|
||||
networkObject.transform.localPosition = Position;
|
||||
networkObject.transform.localRotation = Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
|
||||
networkObject.transform.position = Position;
|
||||
networkObject.transform.rotation = Rotation;
|
||||
}
|
||||
networkObject.transform.localScale = Scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct RpcMessage : INetworkMessage
|
||||
{
|
||||
public enum RpcType : byte
|
||||
{
|
||||
Server,
|
||||
Client
|
||||
}
|
||||
|
||||
public struct HeaderData
|
||||
{
|
||||
public RpcType Type;
|
||||
public ulong NetworkObjectId;
|
||||
public ushort NetworkBehaviourId;
|
||||
public uint NetworkMethodId;
|
||||
}
|
||||
|
||||
public HeaderData Header;
|
||||
public FastBufferWriter RpcData;
|
||||
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Header) + RpcData.Length))
|
||||
{
|
||||
throw new OverflowException("Not enough space in the buffer to store RPC data.");
|
||||
}
|
||||
writer.WriteValue(Header);
|
||||
writer.WriteBytes(RpcData.GetUnsafePtr(), RpcData.Length);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
{
|
||||
var message = new RpcMessage();
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.Header)))
|
||||
{
|
||||
throw new OverflowException("Not enough space in the buffer to read RPC data.");
|
||||
}
|
||||
reader.ReadValue(out message.Header);
|
||||
message.Handle(reader, context, (NetworkManager)context.SystemOwner, context.SenderId, true);
|
||||
}
|
||||
|
||||
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager, ulong senderId, bool canDefer)
|
||||
{
|
||||
if (NetworkManager.__rpc_func_table.ContainsKey(Header.NetworkMethodId))
|
||||
{
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(Header.NetworkObjectId))
|
||||
{
|
||||
if (canDefer)
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(Header.NetworkObjectId, reader, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkLog.LogError($"Tried to invoke an RPC on a non-existent {nameof(NetworkObject)} with {nameof(canDefer)}=false");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[Header.NetworkObjectId];
|
||||
|
||||
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(Header.NetworkBehaviourId);
|
||||
if (networkBehaviour == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rpcParams = new __RpcParams();
|
||||
switch (Header.Type)
|
||||
{
|
||||
case RpcType.Server:
|
||||
rpcParams.Server = new ServerRpcParams
|
||||
{
|
||||
Receive = new ServerRpcReceiveParams
|
||||
{
|
||||
SenderClientId = senderId
|
||||
}
|
||||
};
|
||||
break;
|
||||
case RpcType.Client:
|
||||
rpcParams.Client = new ClientRpcParams
|
||||
{
|
||||
Receive = new ClientRpcReceiveParams
|
||||
{
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
NetworkManager.__rpc_func_table[Header.NetworkMethodId](networkBehaviour, reader, rpcParams);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(Header.NetworkMethodId, out var rpcMethodName))
|
||||
{
|
||||
networkManager.NetworkMetrics.TrackRpcReceived(
|
||||
senderId,
|
||||
networkObject,
|
||||
rpcMethodName,
|
||||
networkBehaviour.__getTypeName(),
|
||||
reader.Length);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Runtime/Messaging/Messages/RpcMessages.cs
Normal file
157
Runtime/Messaging/Messages/RpcMessages.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal static class RpcMessageHelpers
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[metadata.NetworkObjectId];
|
||||
var networkBehaviour = networkManager.SpawnManager.SpawnedObjects[metadata.NetworkObjectId].GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);
|
||||
if (networkBehaviour == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
|
||||
{
|
||||
networkManager.NetworkMetrics.TrackRpcReceived(
|
||||
context.SenderId,
|
||||
networkObject,
|
||||
rpcMethodName,
|
||||
networkBehaviour.__getTypeName(),
|
||||
reader.Length);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, ref __RpcParams rpcParams)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(metadata.NetworkObjectId, out var networkObject))
|
||||
{
|
||||
throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
|
||||
}
|
||||
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);
|
||||
|
||||
try
|
||||
{
|
||||
NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Unhandled RPC exception!", ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct RpcMetadata : INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ushort NetworkBehaviourId;
|
||||
public uint NetworkRpcMethodId;
|
||||
}
|
||||
|
||||
internal struct ServerRpcMessage : INetworkMessage
|
||||
{
|
||||
public RpcMetadata Metadata;
|
||||
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||
}
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var rpcParams = new __RpcParams
|
||||
{
|
||||
Server = new ServerRpcParams
|
||||
{
|
||||
Receive = new ServerRpcReceiveParams
|
||||
{
|
||||
SenderClientId = context.SenderId
|
||||
}
|
||||
}
|
||||
};
|
||||
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ClientRpcMessage : INetworkMessage
|
||||
{
|
||||
public RpcMetadata Metadata;
|
||||
|
||||
public FastBufferWriter WriteBuffer;
|
||||
public FastBufferReader ReadBuffer;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var rpcParams = new __RpcParams
|
||||
{
|
||||
Client = new ClientRpcParams
|
||||
{
|
||||
Receive = new ClientRpcReceiveParams
|
||||
{
|
||||
}
|
||||
}
|
||||
};
|
||||
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,22 @@ namespace Unity.Netcode
|
||||
{
|
||||
public SceneEventData EventData;
|
||||
|
||||
private FastBufferReader m_ReceivedData;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
EventData.Serialize(writer);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, reader);
|
||||
m_ReceivedData = reader;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,21 +17,25 @@ namespace Unity.Netcode
|
||||
BytePacker.WriteValuePacked(writer, Message);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)
|
||||
{
|
||||
var message = new ServerLogMessage();
|
||||
reader.ReadValueSafe(out message.LogType);
|
||||
ByteUnpacker.ReadValuePacked(reader, out message.Message);
|
||||
message.Handle(context.SenderId, networkManager, reader.Length);
|
||||
reader.ReadValueSafe(out LogType);
|
||||
ByteUnpacker.ReadValuePacked(reader, out Message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, messageSize);
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var senderId = context.SenderId;
|
||||
|
||||
networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize);
|
||||
|
||||
switch (LogType)
|
||||
{
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct SnapshotDataMessage : INetworkMessage
|
||||
{
|
||||
public int CurrentTick;
|
||||
public ushort Sequence;
|
||||
|
||||
public ushort Range;
|
||||
|
||||
public byte[] SendMainBuffer;
|
||||
public NativeArray<byte> ReceiveMainBuffer;
|
||||
|
||||
public struct AckData
|
||||
{
|
||||
public ushort LastReceivedSequence;
|
||||
public ushort ReceivedSequenceMask;
|
||||
}
|
||||
|
||||
public AckData Ack;
|
||||
|
||||
public struct EntryData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ushort BehaviourIndex;
|
||||
public ushort VariableIndex;
|
||||
public int TickWritten;
|
||||
public ushort Position;
|
||||
public ushort Length;
|
||||
}
|
||||
|
||||
public NativeList<EntryData> Entries;
|
||||
|
||||
public struct SpawnData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public uint Hash;
|
||||
public bool IsSceneObject;
|
||||
|
||||
public bool IsPlayerObject;
|
||||
public ulong OwnerClientId;
|
||||
public ulong ParentNetworkId;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
|
||||
public int TickWritten;
|
||||
}
|
||||
|
||||
public NativeList<SpawnData> Spawns;
|
||||
|
||||
public struct DespawnData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public int TickWritten;
|
||||
}
|
||||
|
||||
public NativeList<DespawnData> Despawns;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(
|
||||
FastBufferWriter.GetWriteSize(CurrentTick) +
|
||||
FastBufferWriter.GetWriteSize(Sequence) +
|
||||
FastBufferWriter.GetWriteSize(Range) + Range +
|
||||
FastBufferWriter.GetWriteSize(Ack) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Entries.Length * sizeof(EntryData) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Spawns.Length * sizeof(SpawnData) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Despawns.Length * sizeof(DespawnData)
|
||||
))
|
||||
{
|
||||
Entries.Dispose();
|
||||
Spawns.Dispose();
|
||||
Despawns.Dispose();
|
||||
throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}");
|
||||
}
|
||||
writer.WriteValue(CurrentTick);
|
||||
writer.WriteValue(Sequence);
|
||||
|
||||
writer.WriteValue(Range);
|
||||
writer.WriteBytes(SendMainBuffer, Range);
|
||||
writer.WriteValue(Ack);
|
||||
|
||||
writer.WriteValue((ushort)Entries.Length);
|
||||
writer.WriteBytes((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
|
||||
|
||||
writer.WriteValue((ushort)Spawns.Length);
|
||||
writer.WriteBytes((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
|
||||
|
||||
writer.WriteValue((ushort)Despawns.Length);
|
||||
writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
|
||||
|
||||
Entries.Dispose();
|
||||
Spawns.Dispose();
|
||||
Despawns.Dispose();
|
||||
}
|
||||
|
||||
public static unsafe void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var message = new SnapshotDataMessage();
|
||||
if (!reader.TryBeginRead(
|
||||
FastBufferWriter.GetWriteSize(message.CurrentTick) +
|
||||
FastBufferWriter.GetWriteSize(message.Sequence) +
|
||||
FastBufferWriter.GetWriteSize(message.Range)
|
||||
))
|
||||
{
|
||||
throw new OverflowException($"Not enough space to deserialize {nameof(SnapshotDataMessage)}");
|
||||
}
|
||||
reader.ReadValue(out message.CurrentTick);
|
||||
reader.ReadValue(out message.Sequence);
|
||||
|
||||
reader.ReadValue(out message.Range);
|
||||
message.ReceiveMainBuffer = new NativeArray<byte>(message.Range, Allocator.Temp);
|
||||
reader.ReadBytesSafe((byte*)message.ReceiveMainBuffer.GetUnsafePtr(), message.Range);
|
||||
reader.ReadValueSafe(out message.Ack);
|
||||
|
||||
reader.ReadValueSafe(out ushort length);
|
||||
message.Entries = new NativeList<EntryData>(length, Allocator.Temp);
|
||||
message.Entries.Length = length;
|
||||
reader.ReadBytesSafe((byte*)message.Entries.GetUnsafePtr(), message.Entries.Length * sizeof(EntryData));
|
||||
|
||||
reader.ReadValueSafe(out length);
|
||||
message.Spawns = new NativeList<SpawnData>(length, Allocator.Temp);
|
||||
message.Spawns.Length = length;
|
||||
reader.ReadBytesSafe((byte*)message.Spawns.GetUnsafePtr(), message.Spawns.Length * sizeof(SpawnData));
|
||||
|
||||
reader.ReadValueSafe(out length);
|
||||
message.Despawns = new NativeList<DespawnData>(length, Allocator.Temp);
|
||||
message.Despawns.Length = length;
|
||||
reader.ReadBytesSafe((byte*)message.Despawns.GetUnsafePtr(), message.Despawns.Length * sizeof(DespawnData));
|
||||
|
||||
using (message.ReceiveMainBuffer)
|
||||
using (message.Entries)
|
||||
using (message.Spawns)
|
||||
using (message.Despawns)
|
||||
{
|
||||
message.Handle(context.SenderId, networkManager);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, NetworkManager networkManager)
|
||||
{
|
||||
// todo: temporary hack around bug
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
senderId = networkManager.ServerClientId;
|
||||
}
|
||||
|
||||
var snapshotSystem = networkManager.SnapshotSystem;
|
||||
snapshotSystem.HandleSnapshot(senderId, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cf75026c2ab86646aac16b39d7259ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct TimeSyncMessage : INetworkMessage
|
||||
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public int Tick;
|
||||
|
||||
@@ -9,21 +9,22 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(this);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.IsClient)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
reader.ReadValueSafe(out TimeSyncMessage message);
|
||||
message.Handle(context.SenderId, networkManager);
|
||||
reader.ReadValueSafe(out this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, NetworkManager networkManager)
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick);
|
||||
networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(senderId) / 1000d);
|
||||
networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(context.SenderId) / 1000d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,23 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal struct UnnamedMessage : INetworkMessage
|
||||
{
|
||||
public FastBufferWriter Data;
|
||||
public FastBufferWriter SendData;
|
||||
private FastBufferReader m_ReceivedData;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length);
|
||||
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
|
||||
}
|
||||
|
||||
public static void Receive(FastBufferReader reader, in NetworkContext context)
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader);
|
||||
m_ReceivedData = reader;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class HandlerNotRegisteredException : SystemException
|
||||
{
|
||||
public HandlerNotRegisteredException() { }
|
||||
public HandlerNotRegisteredException(string issue) : base(issue) { }
|
||||
}
|
||||
|
||||
internal class InvalidMessageStructureException : SystemException
|
||||
{
|
||||
@@ -23,6 +28,7 @@ namespace Unity.Netcode
|
||||
public MessageHeader Header;
|
||||
public ulong SenderId;
|
||||
public float Timestamp;
|
||||
public int MessageHeaderSerializedSize;
|
||||
}
|
||||
|
||||
private struct SendQueueItem
|
||||
@@ -39,34 +45,36 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal delegate void MessageHandler(FastBufferReader reader, in NetworkContext context);
|
||||
internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system);
|
||||
|
||||
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, byte> m_MessageTypes = new Dictionary<Type, byte>();
|
||||
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
|
||||
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
|
||||
|
||||
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
|
||||
|
||||
private byte m_HighMessageType;
|
||||
private uint m_HighMessageType;
|
||||
private object m_Owner;
|
||||
private IMessageSender m_MessageSender;
|
||||
private bool m_Disposed;
|
||||
|
||||
internal Type[] MessageTypes => m_ReverseTypeMap;
|
||||
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
|
||||
internal int MessageHandlerCount => m_HighMessageType;
|
||||
|
||||
internal byte GetMessageType(Type t)
|
||||
internal uint MessageHandlerCount => m_HighMessageType;
|
||||
|
||||
internal uint GetMessageType(Type t)
|
||||
{
|
||||
return m_MessageTypes[t];
|
||||
}
|
||||
|
||||
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = 64000;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
|
||||
|
||||
internal struct MessageWithHandler
|
||||
{
|
||||
@@ -74,6 +82,35 @@ namespace Unity.Netcode
|
||||
public MessageHandler Handler;
|
||||
}
|
||||
|
||||
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 == "Unity.Netcode.ConnectionRequestMessage" ||
|
||||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
|
||||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
|
||||
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
|
||||
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
return prioritizedTypes;
|
||||
}
|
||||
|
||||
public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
|
||||
{
|
||||
try
|
||||
@@ -88,6 +125,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);
|
||||
@@ -100,7 +138,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
if (m_Disposed)
|
||||
{
|
||||
@@ -113,6 +151,14 @@ namespace Unity.Netcode
|
||||
{
|
||||
CleanupDisconnectedClient(kvp.Key);
|
||||
}
|
||||
|
||||
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
|
||||
{
|
||||
// Avoid copies...
|
||||
ref var item = ref m_IncomingMessageQueue.ElementAt(queueIndex);
|
||||
item.Reader.Dispose();
|
||||
}
|
||||
|
||||
m_IncomingMessageQueue.Dispose();
|
||||
m_Disposed = true;
|
||||
}
|
||||
@@ -127,8 +173,20 @@ namespace Unity.Netcode
|
||||
m_Hooks.Add(hooks);
|
||||
}
|
||||
|
||||
public void Unhook(INetworkHooks hooks)
|
||||
{
|
||||
m_Hooks.Remove(hooks);
|
||||
}
|
||||
|
||||
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_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
|
||||
@@ -141,7 +199,7 @@ namespace Unity.Netcode
|
||||
fixed (byte* nativeData = data.Array)
|
||||
{
|
||||
var batchReader =
|
||||
new FastBufferReader(nativeData, Allocator.None, data.Count, data.Offset);
|
||||
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.");
|
||||
@@ -157,14 +215,23 @@ namespace Unity.Netcode
|
||||
|
||||
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
|
||||
{
|
||||
if (!batchReader.TryBeginRead(sizeof(MessageHeader)))
|
||||
|
||||
var messageHeader = new MessageHeader();
|
||||
var position = batchReader.Position;
|
||||
try
|
||||
{
|
||||
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageType);
|
||||
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageSize);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
|
||||
return;
|
||||
throw;
|
||||
}
|
||||
batchReader.ReadValue(out MessageHeader messageHeader);
|
||||
|
||||
if (!batchReader.TryBeginRead(messageHeader.MessageSize))
|
||||
var receivedHeaderSize = batchReader.Position - position;
|
||||
|
||||
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
|
||||
{
|
||||
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
|
||||
return;
|
||||
@@ -177,9 +244,10 @@ namespace Unity.Netcode
|
||||
// Copy the data for this message into a new FastBufferReader that owns that memory.
|
||||
// We can't guarantee the memory in the ArraySegment stays valid because we don't own it,
|
||||
// so we must move it to memory we do own.
|
||||
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, messageHeader.MessageSize)
|
||||
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, (int)messageHeader.MessageSize),
|
||||
MessageHeaderSerializedSize = receivedHeaderSize,
|
||||
});
|
||||
batchReader.Seek(batchReader.Position + messageHeader.MessageSize);
|
||||
batchReader.Seek(batchReader.Position + (int)messageHeader.MessageSize);
|
||||
}
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
@@ -189,11 +257,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanReceive(ulong clientId, Type messageType)
|
||||
private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType))
|
||||
if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType, messageContent, ref context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -202,7 +270,71 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp)
|
||||
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
|
||||
// This allows the server to tell the client which id it is using for which message and make sure the right
|
||||
// message is used when deserializing.
|
||||
internal void ReorderMessage(int desiredOrder, uint targetHash)
|
||||
{
|
||||
if (desiredOrder < 0)
|
||||
{
|
||||
throw new ArgumentException("ReorderMessage desiredOrder must be positive");
|
||||
}
|
||||
|
||||
if (desiredOrder < m_ReverseTypeMap.Length &&
|
||||
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash)
|
||||
{
|
||||
// matching positions and hashes. All good.
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"Unexpected hash for {desiredOrder}");
|
||||
|
||||
// Since the message at `desiredOrder` is not the expected one,
|
||||
// insert an empty placeholder and move the messages down
|
||||
var typesAsList = new List<Type>(m_ReverseTypeMap);
|
||||
|
||||
typesAsList.Insert(desiredOrder, null);
|
||||
var handlersAsList = new List<MessageHandler>(m_MessageHandlers);
|
||||
handlersAsList.Insert(desiredOrder, null);
|
||||
|
||||
// we added a dummy message, bump the end up
|
||||
m_HighMessageType++;
|
||||
|
||||
// Here, we rely on the server telling us about all messages, in order.
|
||||
// So, we know the handlers before desiredOrder are correct.
|
||||
// We start at desiredOrder to not shift them when we insert.
|
||||
int position = desiredOrder;
|
||||
bool found = false;
|
||||
while (position < typesAsList.Count)
|
||||
{
|
||||
if (typesAsList[position] != null &&
|
||||
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
// Copy the handler and type to the right index
|
||||
|
||||
typesAsList[desiredOrder] = typesAsList[position];
|
||||
handlersAsList[desiredOrder] = handlersAsList[position];
|
||||
typesAsList.RemoveAt(position);
|
||||
handlersAsList.RemoveAt(position);
|
||||
|
||||
// we removed a copy after moving a message, reduce the high message index
|
||||
m_HighMessageType--;
|
||||
}
|
||||
|
||||
m_ReverseTypeMap = typesAsList.ToArray();
|
||||
m_MessageHandlers = handlersAsList.ToArray();
|
||||
}
|
||||
|
||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
|
||||
{
|
||||
if (header.MessageType >= m_HighMessageType)
|
||||
{
|
||||
@@ -215,10 +347,13 @@ namespace Unity.Netcode
|
||||
SystemOwner = m_Owner,
|
||||
SenderId = senderId,
|
||||
Timestamp = timestamp,
|
||||
Header = header
|
||||
Header = header,
|
||||
SerializedHeaderSize = serializedHeaderSize,
|
||||
MessageSize = header.MessageSize,
|
||||
};
|
||||
|
||||
var type = m_ReverseTypeMap[header.MessageType];
|
||||
if (!CanReceive(senderId, type))
|
||||
if (!CanReceive(senderId, type, reader, ref context))
|
||||
{
|
||||
reader.Dispose();
|
||||
return;
|
||||
@@ -228,21 +363,33 @@ namespace Unity.Netcode
|
||||
{
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
handler.Invoke(reader, context);
|
||||
Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString()));
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
Debug.LogException(e);
|
||||
// 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)
|
||||
@@ -253,11 +400,15 @@ namespace Unity.Netcode
|
||||
|
||||
internal unsafe void ProcessIncomingMessageQueue()
|
||||
{
|
||||
for (var i = 0; i < m_IncomingMessageQueue.Length; ++i)
|
||||
for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
|
||||
{
|
||||
// Avoid copies...
|
||||
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(i);
|
||||
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp);
|
||||
ref var item = ref m_IncomingMessageQueue.ElementAt(index);
|
||||
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize);
|
||||
if (m_Disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_IncomingMessageQueue.Clear();
|
||||
@@ -287,12 +438,31 @@ namespace Unity.Netcode
|
||||
var queue = m_SendQueues[clientId];
|
||||
for (var i = 0; i < queue.Length; ++i)
|
||||
{
|
||||
queue.GetUnsafeList()->ElementAt(i).Writer.Dispose();
|
||||
queue.ElementAt(i).Writer.Dispose();
|
||||
}
|
||||
|
||||
queue.Dispose();
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
system.m_Hooks[hookIdx].OnBeforeHandleMessage(ref message, ref context);
|
||||
}
|
||||
|
||||
message.Handle(ref context);
|
||||
|
||||
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
system.m_Hooks[hookIdx].OnAfterHandleMessage(ref message, ref context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanSend(ulong clientId, Type messageType, NetworkDelivery delivery)
|
||||
{
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
@@ -306,7 +476,7 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<TMessageType, TClientIdListType>(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
|
||||
internal int SendMessage<TMessageType, TClientIdListType>(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
|
||||
where TMessageType : INetworkMessage
|
||||
where TClientIdListType : IReadOnlyList<ulong>
|
||||
{
|
||||
@@ -316,64 +486,81 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
|
||||
var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
using (tmpSerializer)
|
||||
|
||||
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
|
||||
message.Serialize(tmpSerializer);
|
||||
|
||||
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds);
|
||||
}
|
||||
|
||||
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds)
|
||||
where TMessageType : INetworkMessage
|
||||
{
|
||||
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
|
||||
|
||||
var header = new MessageHeader
|
||||
{
|
||||
message.Serialize(tmpSerializer);
|
||||
MessageSize = (uint)tmpSerializer.Length,
|
||||
MessageType = m_MessageTypes[typeof(TMessageType)],
|
||||
};
|
||||
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType);
|
||||
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageSize);
|
||||
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
for (var i = 0; i < clientIds.Count; ++i)
|
||||
{
|
||||
var clientId = clientIds[i];
|
||||
|
||||
if (!CanSend(clientId, typeof(TMessageType), delivery))
|
||||
{
|
||||
var clientId = clientIds[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CanSend(clientId, typeof(TMessageType), delivery))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery);
|
||||
}
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
|
||||
}
|
||||
|
||||
var sendQueueItem = m_SendQueues[clientId];
|
||||
if (sendQueueItem.Length == 0)
|
||||
var sendQueueItem = m_SendQueues[clientId];
|
||||
if (sendQueueItem.Length == 0)
|
||||
{
|
||||
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
|
||||
maxSize));
|
||||
sendQueueItem.ElementAt(0).Writer.Seek(sizeof(BatchHeader));
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
|
||||
if (lastQueueItem.NetworkDelivery != delivery ||
|
||||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
|
||||
< tmpSerializer.Length + headerSerializer.Length)
|
||||
{
|
||||
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
|
||||
maxSize));
|
||||
sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader));
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
|
||||
if (lastQueueItem.NetworkDelivery != delivery ||
|
||||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
|
||||
< tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>())
|
||||
{
|
||||
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
|
||||
maxSize));
|
||||
sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
|
||||
}
|
||||
}
|
||||
|
||||
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
|
||||
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
var header = new MessageHeader
|
||||
{
|
||||
MessageSize = (ushort)tmpSerializer.Length,
|
||||
MessageType = m_MessageTypes[typeof(TMessageType)],
|
||||
};
|
||||
|
||||
writeQueueItem.Writer.WriteValue(header);
|
||||
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
|
||||
writeQueueItem.BatchHeader.BatchSize++;
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||
sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
|
||||
}
|
||||
}
|
||||
|
||||
return tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>();
|
||||
ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
|
||||
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
|
||||
|
||||
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
|
||||
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
|
||||
writeQueueItem.BatchHeader.BatchSize++;
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return tmpSerializer.Length + headerSerializer.Length;
|
||||
}
|
||||
|
||||
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId)
|
||||
where TMessageType : INetworkMessage
|
||||
{
|
||||
ulong* clientIds = stackalloc ulong[] { clientId };
|
||||
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
|
||||
}
|
||||
|
||||
private struct PointerListWrapper<T> : IReadOnlyList<T>
|
||||
@@ -411,24 +598,24 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery,
|
||||
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery,
|
||||
ulong* clientIds, int numClientIds)
|
||||
where T : INetworkMessage
|
||||
{
|
||||
return SendMessage(message, delivery, new PointerListWrapper<ulong>(clientIds, numClientIds));
|
||||
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>(clientIds, numClientIds));
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery, ulong clientId)
|
||||
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, ulong clientId)
|
||||
where T : INetworkMessage
|
||||
{
|
||||
ulong* clientIds = stackalloc ulong[] { clientId };
|
||||
return SendMessage(message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
|
||||
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
|
||||
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
|
||||
where T : INetworkMessage
|
||||
{
|
||||
return SendMessage(message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
|
||||
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
|
||||
}
|
||||
|
||||
internal unsafe void ProcessSendQueues()
|
||||
@@ -439,7 +626,7 @@ namespace Unity.Netcode
|
||||
var sendQueueItem = kvp.Value;
|
||||
for (var i = 0; i < sendQueueItem.Length; ++i)
|
||||
{
|
||||
ref var queueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(i);
|
||||
ref var queueItem = ref sendQueueItem.ElementAt(i);
|
||||
if (queueItem.BatchHeader.BatchSize == 0)
|
||||
{
|
||||
queueItem.Writer.Dispose();
|
||||
@@ -461,16 +648,16 @@ namespace Unity.Netcode
|
||||
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);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueItem.Writer.Dispose();
|
||||
}
|
||||
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
|
||||
}
|
||||
}
|
||||
sendQueueItem.Clear();
|
||||
}
|
||||
|
||||
@@ -25,5 +25,15 @@ namespace Unity.Netcode
|
||||
/// The header data that was sent with the message
|
||||
/// </summary>
|
||||
public MessageHeader Header;
|
||||
|
||||
/// <summary>
|
||||
/// The actual serialized size of the header when packed into the buffer
|
||||
/// </summary>
|
||||
public int SerializedHeaderSize;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the message in the buffer, header excluded
|
||||
/// </summary>
|
||||
public uint MessageSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,18 @@ namespace Unity.Netcode
|
||||
|
||||
void TrackSceneEventReceived(ulong senderClientId, uint sceneEventType, string sceneName, long bytesCount);
|
||||
|
||||
void TrackPacketSent(uint packetCount);
|
||||
|
||||
void TrackPacketReceived(uint packetCount);
|
||||
|
||||
void UpdateRttToServer(int rtt);
|
||||
|
||||
void UpdateNetworkObjectsCount(int count);
|
||||
|
||||
void UpdateConnectionsCount(int count);
|
||||
|
||||
void UpdatePacketLoss(float packetLoss);
|
||||
|
||||
void DispatchFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,13 @@ namespace Unity.Netcode
|
||||
m_NetworkManager = networkManager;
|
||||
}
|
||||
|
||||
|
||||
public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery)
|
||||
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes)
|
||||
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
|
||||
{
|
||||
m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, messageType.Name, messageSizeBytes);
|
||||
m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, typeof(T).Name, messageSizeBytes);
|
||||
}
|
||||
|
||||
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||
@@ -53,9 +52,19 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
// TODO: Per-message metrics recording moved here
|
||||
}
|
||||
|
||||
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
// TODO: Per-message metrics recording moved here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ using System.Collections.Generic;
|
||||
using Unity.Multiplayer.Tools;
|
||||
using Unity.Multiplayer.Tools.MetricTypes;
|
||||
using Unity.Multiplayer.Tools.NetStats;
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class NetworkMetrics : INetworkMetrics
|
||||
{
|
||||
const ulong k_MaxMetricsPerFrame = 1000L;
|
||||
|
||||
static Dictionary<uint, string> s_SceneEventTypeNames;
|
||||
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()
|
||||
{
|
||||
@@ -63,6 +63,30 @@ namespace Unity.Netcode
|
||||
private readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id);
|
||||
private readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id);
|
||||
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Counter m_PacketReceivedCounter = new Counter(NetworkMetricTypes.PacketsReceived.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_RttToServerGauge = new Gauge(NetworkMetricTypes.RttToServer.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_NetworkObjectsGauge = new Gauge(NetworkMetricTypes.NetworkObjects.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_ConnectionsGauge = new Gauge(NetworkMetricTypes.ConnectedClients.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_PacketLossGauge = new Gauge(NetworkMetricTypes.PacketLoss.Id);
|
||||
#endif
|
||||
|
||||
private ulong m_NumberOfMetricsThisFrame;
|
||||
|
||||
public NetworkMetrics()
|
||||
@@ -79,6 +103,13 @@ namespace Unity.Netcode
|
||||
.WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent)
|
||||
.WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent)
|
||||
.WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent)
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
.WithCounters(m_PacketSentCounter, m_PacketReceivedCounter)
|
||||
.WithGauges(m_RttToServerGauge)
|
||||
.WithGauges(m_NetworkObjectsGauge)
|
||||
.WithGauges(m_ConnectionsGauge)
|
||||
.WithGauges(m_PacketLossGauge)
|
||||
#endif
|
||||
.Build();
|
||||
|
||||
Dispatcher.RegisterObserver(NetcodeObserver.Observer);
|
||||
@@ -404,9 +435,85 @@ namespace Unity.Netcode
|
||||
IncrementMetricCount();
|
||||
}
|
||||
|
||||
public void TrackPacketSent(uint packetCount)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_PacketSentCounter.Increment(packetCount);
|
||||
IncrementMetricCount();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void TrackPacketReceived(uint packetCount)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_PacketReceivedCounter.Increment(packetCount);
|
||||
IncrementMetricCount();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateRttToServer(int rttMilliseconds)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var rttSeconds = rttMilliseconds * 1e-3;
|
||||
m_RttToServerGauge.Set(rttSeconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateNetworkObjectsCount(int count)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_NetworkObjectsGauge.Set(count);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateConnectionsCount(int count)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_ConnectionsGauge.Set(count);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdatePacketLoss(float packetLoss)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_PacketLossGauge.Set(packetLoss);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void DispatchFrame()
|
||||
{
|
||||
s_FrameDispatch.Begin();
|
||||
Dispatcher.Dispatch();
|
||||
s_FrameDispatch.End();
|
||||
m_NumberOfMetricsThisFrame = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,30 @@ namespace Unity.Netcode
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackPacketSent(uint packetCount)
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackPacketReceived(uint packetCount)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateRttToServer(int rtt)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateNetworkObjectsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateConnectionsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdatePacketLoss(float packetLoss)
|
||||
{
|
||||
}
|
||||
|
||||
public void DispatchFrame()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
public static long SafeGetLengthOrDefault(this Stream stream)
|
||||
{
|
||||
return stream.CanSeek ? stream.Length : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61dd9b1558f6d7c46ad323b2c2c03c29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -25,33 +26,26 @@ namespace Unity.Netcode
|
||||
public event OnListChangedDelegate OnListChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and settings
|
||||
/// Constructor method for <see cref="NetworkList"/>
|
||||
/// </summary>
|
||||
public NetworkList() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission to use for the NetworkList</param>
|
||||
/// <param name="values">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(NetworkVariableReadPermission readPerm, IEnumerable<T> values) : base(readPerm)
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="values">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(IEnumerable<T> values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
m_List.Add(value);
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +53,10 @@ namespace Unity.Netcode
|
||||
public override void ResetDirty()
|
||||
{
|
||||
base.ResetDirty();
|
||||
m_DirtyEvents.Clear();
|
||||
if (m_DirtyEvents.Length > 0)
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -69,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)
|
||||
{
|
||||
@@ -85,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:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
writer.WriteValueSafe(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);
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
@@ -130,7 +140,7 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
{
|
||||
writer.WriteValueSafe(m_List[i]);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +151,8 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out ushort count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
reader.ReadValueSafe(out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -157,7 +168,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
reader.ReadValueSafe(out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
m_List.Add(value);
|
||||
|
||||
if (OnListChanged != null)
|
||||
@@ -178,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);
|
||||
reader.ReadValueSafe(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)
|
||||
{
|
||||
@@ -206,12 +228,14 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = m_List[index]
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
reader.ReadValueSafe(out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
int index = m_List.IndexOf(value);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -238,6 +262,7 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -265,25 +290,31 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
reader.ReadValueSafe(out T value);
|
||||
if (index < m_List.Length)
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
m_List[index] = value;
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
}
|
||||
|
||||
var previousValue = m_List[index];
|
||||
m_List[index] = value;
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,8 +324,10 @@ namespace Unity.Netcode
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -317,6 +350,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -339,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>()
|
||||
@@ -354,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>()
|
||||
@@ -368,12 +414,18 @@ namespace Unity.Netcode
|
||||
public bool Contains(T item)
|
||||
{
|
||||
int index = NativeArrayExtensions.IndexOf(m_List, item);
|
||||
return index == -1;
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(T item)
|
||||
{
|
||||
// check write permissions
|
||||
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
|
||||
}
|
||||
|
||||
int index = NativeArrayExtensions.IndexOf(m_List, item);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -403,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>()
|
||||
{
|
||||
@@ -419,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>()
|
||||
@@ -436,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);
|
||||
@@ -452,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
|
||||
@@ -464,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();
|
||||
@@ -528,6 +616,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public T Value;
|
||||
|
||||
/// <summary>
|
||||
/// The previous value when "Value" has changed, if available.
|
||||
/// </summary>
|
||||
public T PreviousValue;
|
||||
|
||||
/// <summary>
|
||||
/// the index changed, added or removed if available
|
||||
/// </summary>
|
||||
|
||||
@@ -6,8 +6,9 @@ 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> : NetworkVariableBase where T : unmanaged
|
||||
public class NetworkVariable<T> : NetworkVariableBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
@@ -21,41 +22,22 @@ namespace Unity.Netcode
|
||||
public OnValueChangedDelegate OnValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom read permission
|
||||
/// Constructor for <see cref="NetworkVariable{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
|
||||
public NetworkVariable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom read permission
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableReadPermission readPerm) : base(readPerm)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableReadPermission readPerm, T value) : base(readPerm)
|
||||
/// <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)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and the default read permission
|
||||
/// The internal value of the NetworkVariable
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(T value)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private protected T m_InternalValue;
|
||||
|
||||
@@ -67,22 +49,29 @@ namespace Unity.Netcode
|
||||
get => m_InternalValue;
|
||||
set
|
||||
{
|
||||
// this could be improved. The Networking Manager is not always initialized here
|
||||
// Good place to decouple network manager from the network variable
|
||||
|
||||
// Also, note this is not really very water-tight, if you are running as a host
|
||||
// we cannot tell if a NetworkVariable write is happening inside client-ish code
|
||||
if (m_NetworkBehaviour && (m_NetworkBehaviour.NetworkManager.IsClient && !m_NetworkBehaviour.NetworkManager.IsHost))
|
||||
// Compare bitwise
|
||||
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
|
||||
{
|
||||
throw new InvalidOperationException("Client can't write to NetworkVariables");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
|
||||
}
|
||||
|
||||
Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
@@ -97,7 +86,6 @@ namespace Unity.Netcode
|
||||
WriteField(writer);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads value from the reader and applies it
|
||||
/// </summary>
|
||||
@@ -105,12 +93,17 @@ namespace Unity.Netcode
|
||||
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
||||
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
||||
{
|
||||
// todo:
|
||||
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
|
||||
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
|
||||
// would be stored in different fields
|
||||
|
||||
T previousValue = m_InternalValue;
|
||||
reader.ReadValueSafe(out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
SetDirty(true);
|
||||
}
|
||||
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
@@ -119,13 +112,13 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void ReadField(FastBufferReader reader)
|
||||
{
|
||||
reader.ReadValueSafe(out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe(m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -10,21 +11,51 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// The delivery type (QoS) to send data with
|
||||
/// </summary>
|
||||
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableSequenced;
|
||||
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
|
||||
/// <summary>
|
||||
/// Maintains a link to the associated NetworkBehaviour
|
||||
/// </summary>
|
||||
private protected NetworkBehaviour m_NetworkBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the NetworkVariable
|
||||
/// </summary>
|
||||
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
|
||||
public void Initialize(NetworkBehaviour networkBehaviour)
|
||||
{
|
||||
m_NetworkBehaviour = networkBehaviour;
|
||||
}
|
||||
|
||||
protected NetworkVariableBase(NetworkVariableReadPermission readPermIn = NetworkVariableReadPermission.Everyone)
|
||||
/// <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)
|
||||
{
|
||||
ReadPerm = readPermIn;
|
||||
ReadPerm = readPerm;
|
||||
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
|
||||
@@ -37,12 +68,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>
|
||||
@@ -62,26 +110,46 @@ namespace Unity.Netcode
|
||||
return m_IsDirty;
|
||||
}
|
||||
|
||||
public virtual bool ShouldWrite(ulong clientId, bool isServer)
|
||||
{
|
||||
return IsDirty() && isServer && CanClientRead(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not a specific client can read to the varaible
|
||||
/// Gets if a specific client has permission to read the var or not
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the remote client</param>
|
||||
/// <returns>Whether or not the client can read to the variable</returns>
|
||||
/// <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)
|
||||
{
|
||||
default:
|
||||
case NetworkVariableReadPermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariableReadPermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariableReadPermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
default:
|
||||
case NetworkVariableWritePermission.Server:
|
||||
return clientId == NetworkManager.ServerClientId;
|
||||
case NetworkVariableWritePermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the ClientId of the owning client
|
||||
/// </summary>
|
||||
internal ulong OwnerClientId()
|
||||
{
|
||||
return m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,9 +175,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="reader">The stream to read the delta from</param>
|
||||
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
||||
|
||||
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
|
||||
|
||||
/// <summary>
|
||||
/// Virtual <see cref="IDisposable"/> implementation
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Permission type
|
||||
/// The permission types for reading a var
|
||||
/// </summary>
|
||||
public enum NetworkVariableReadPermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Everyone
|
||||
/// Everyone can read
|
||||
/// </summary>
|
||||
Everyone,
|
||||
|
||||
/// <summary>
|
||||
/// Owner-ownly
|
||||
/// Only the owner and the server can read
|
||||
/// </summary>
|
||||
OwnerOnly,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
331
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
331
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
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>
|
||||
/// 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
|
||||
{
|
||||
/// <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 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 static class NetworkVariableSerialization<T>
|
||||
{
|
||||
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
|
||||
{
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0;
|
||||
}
|
||||
|
||||
internal static bool EqualityEqualsObject<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
|
||||
{
|
||||
if (a == null)
|
||||
{
|
||||
return b == null;
|
||||
}
|
||||
|
||||
if (b == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
|
||||
{
|
||||
return a == b;
|
||||
}
|
||||
|
||||
internal static void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
Serializer.Write(writer, ref value);
|
||||
}
|
||||
|
||||
internal static void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
Serializer.Read(reader, ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c6ef5fdf2e94ec3b4ce8086d52700b3
|
||||
timeCreated: 1650985453
|
||||
@@ -37,14 +37,14 @@ namespace Unity.Netcode
|
||||
return marker;
|
||||
}
|
||||
|
||||
public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery)
|
||||
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
|
||||
{
|
||||
GetSenderProfilerMarker(messageType).Begin();
|
||||
GetSenderProfilerMarker(typeof(T)).Begin();
|
||||
}
|
||||
|
||||
public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes)
|
||||
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
|
||||
{
|
||||
GetSenderProfilerMarker(messageType).End();
|
||||
GetSenderProfilerMarker(typeof(T)).End();
|
||||
}
|
||||
|
||||
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||
@@ -82,9 +82,19 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
// nop
|
||||
}
|
||||
|
||||
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
internal static bool HasInterface(this Type type, Type interfaceType)
|
||||
{
|
||||
var ifaces = type.GetInterfaces();
|
||||
for (int i = 0; i < ifaces.Length; i++)
|
||||
{
|
||||
if (ifaces[i] == interfaceType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsNullable(this Type type)
|
||||
{
|
||||
if (!type.IsValueType)
|
||||
{
|
||||
return true; // ref-type
|
||||
}
|
||||
|
||||
return Nullable.GetUnderlyingType(type) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e168a2bc1a1e2642af0369780fb560c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user