Compare commits
5 Commits
1.0.0-pre.
...
1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e7078c160 | ||
|
|
a6969670f5 | ||
|
|
e15bd056c5 | ||
|
|
18ffd5fdc8 | ||
|
|
0f7a30d285 |
146
CHANGELOG.md
146
CHANGELOG.md
@@ -1,3 +1,4 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
@@ -6,6 +7,135 @@ 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
|
||||
@@ -19,14 +149,15 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
### 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).
|
||||
- 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)
|
||||
@@ -38,6 +169,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
## [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)
|
||||
@@ -240,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)
|
||||
@@ -367,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.
|
||||
|
||||
@@ -8,8 +8,10 @@ namespace Unity.Netcode
|
||||
/// Solves for incoming values that are jittered
|
||||
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of interpolated value</typeparam>
|
||||
public abstract class BufferedLinearInterpolator<T> where T : struct
|
||||
{
|
||||
internal float MaxInterpolationBound = 3.0f;
|
||||
private struct BufferedItem
|
||||
{
|
||||
public T Item;
|
||||
@@ -23,7 +25,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one.
|
||||
/// </summary>
|
||||
public float MaximumInterpolationTime = 0.1f;
|
||||
|
||||
@@ -72,7 +74,7 @@ namespace Unity.Netcode
|
||||
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Resets Interpolator to initial state
|
||||
/// Resets interpolator to initial state
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
@@ -84,6 +86,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Teleports current interpolation value to targetValue.
|
||||
/// </summary>
|
||||
/// <param name="targetValue">The target value to teleport instantly</param>
|
||||
/// <param name="serverTime">The current server time</param>
|
||||
public void ResetTo(T targetValue, double serverTime)
|
||||
{
|
||||
m_LifetimeConsumedCount = 1;
|
||||
@@ -158,6 +162,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">time since call</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
/// <returns>The newly interpolated value of type 'T'</returns>
|
||||
public T Update(float deltaTime, NetworkTime serverTime)
|
||||
{
|
||||
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
|
||||
@@ -169,6 +174,7 @@ namespace Unity.Netcode
|
||||
/// <param name="deltaTime">time since last call</param>
|
||||
/// <param name="renderTime">our current time</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
/// <returns>The newly interpolated value of type 'T'</returns>
|
||||
public T Update(float deltaTime, double renderTime, double serverTime)
|
||||
{
|
||||
TryConsumeFromBuffer(renderTime, serverTime);
|
||||
@@ -203,10 +209,9 @@ namespace Unity.Netcode
|
||||
t = 0.0f;
|
||||
}
|
||||
|
||||
if (t > 3.0f) // max extrapolation
|
||||
if (t > MaxInterpolationBound) // max extrapolation
|
||||
{
|
||||
// TODO this causes issues with teleport, investigate
|
||||
// todo make this configurable
|
||||
t = 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -222,6 +227,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
|
||||
/// </summary>
|
||||
/// <param name="newMeasurement">The new measurement value to use</param>
|
||||
/// <param name="sentTime">The time to record for measurement</param>
|
||||
public void AddMeasurement(T newMeasurement, double sentTime)
|
||||
{
|
||||
m_NbItemsReceivedThisFrame++;
|
||||
@@ -241,6 +248,8 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// Part the of reason for disabling extrapolation is how we add and use measurements over time.
|
||||
// TODO: Add detailed description of this area in Jira ticket
|
||||
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
@@ -251,6 +260,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets latest value from the interpolator. This is updated every update as time goes by.
|
||||
/// </summary>
|
||||
/// <returns>The current interpolated value of type 'T'</returns>
|
||||
public T GetInterpolatedValue()
|
||||
{
|
||||
return m_CurrentInterpValue;
|
||||
@@ -259,36 +269,63 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
|
||||
/// </summary>
|
||||
/// <param name="start">The start value (min)</param>
|
||||
/// <param name="end">The end value (max)</param>
|
||||
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
|
||||
/// <returns>The interpolated value</returns>
|
||||
protected abstract T Interpolate(T start, T end, float time);
|
||||
|
||||
/// <summary>
|
||||
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
|
||||
/// </summary>
|
||||
/// <param name="start">The start value (min)</param>
|
||||
/// <param name="end">The end value (max)</param>
|
||||
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
|
||||
/// <returns>The interpolated value</returns>
|
||||
protected abstract T InterpolateUnclamped(T start, T end, float time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="float"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override float InterpolateUnclamped(float start, float end, float time)
|
||||
{
|
||||
return Mathf.LerpUnclamped(start, end, time);
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float Interpolate(float start, float end, float time)
|
||||
{
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="Quaternion"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
{
|
||||
/// <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
@@ -9,6 +9,7 @@ namespace Unity.Netcode.Components
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody")]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Unity.Netcode.Components
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
|
||||
public class NetworkRigidbody2D : NetworkBehaviour
|
||||
{
|
||||
private Rigidbody2D m_Rigidbody;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Unity.Collections;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using UnityEngine;
|
||||
@@ -14,6 +15,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal static class CodeGenHelpers
|
||||
{
|
||||
public const string DotnetModuleName = "netstandard.dll";
|
||||
public const string UnityModuleName = "UnityEngine.CoreModule.dll";
|
||||
public const string NetcodeModuleName = "Unity.Netcode.Runtime.dll";
|
||||
|
||||
public const string RuntimeAssemblyName = "Unity.Netcode.Runtime";
|
||||
|
||||
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName;
|
||||
@@ -28,6 +33,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName;
|
||||
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
|
||||
public static readonly string INetworkSerializeByMemcpy_FullName = typeof(INetworkSerializeByMemcpy).FullName;
|
||||
public static readonly string IUTF8Bytes_FullName = typeof(IUTF8Bytes).FullName;
|
||||
public static readonly string UnityColor_FullName = typeof(Color).FullName;
|
||||
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;
|
||||
public static readonly string UnityVector2_FullName = typeof(Vector2).FullName;
|
||||
@@ -117,6 +123,19 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
try
|
||||
{
|
||||
var typeDef = typeReference.Resolve();
|
||||
// Note: this won't catch generics correctly.
|
||||
//
|
||||
// class Foo<T>: IInterface<T> {}
|
||||
// class Bar: Foo<int> {}
|
||||
//
|
||||
// Bar.HasInterface(IInterface<int>) -> returns false even though it should be true.
|
||||
//
|
||||
// This can be fixed (see GetAllFieldsAndResolveGenerics() in NetworkBehaviourILPP to understand how)
|
||||
// but right now we don't need that to work so it's left alone to reduce complexity
|
||||
if (typeDef.BaseType.HasInterface(interfaceTypeFullName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var typeFaces = typeDef.Interfaces;
|
||||
return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName);
|
||||
}
|
||||
@@ -378,5 +397,74 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
|
||||
private static void SearchForBaseModulesRecursive(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver, ref ModuleDefinition unityModule, ref ModuleDefinition netcodeModule, HashSet<string> visited)
|
||||
{
|
||||
|
||||
foreach (var module in assemblyDefinition.Modules)
|
||||
{
|
||||
if (module == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (unityModule == null && module.Name == UnityModuleName)
|
||||
{
|
||||
unityModule = module;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (netcodeModule == null && module.Name == NetcodeModuleName)
|
||||
{
|
||||
netcodeModule = module;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var assemblyNameReference in assemblyDefinition.MainModule.AssemblyReferences)
|
||||
{
|
||||
if (assemblyNameReference == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (visited.Contains(assemblyNameReference.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.Add(assemblyNameReference.Name);
|
||||
|
||||
var assembly = assemblyResolver.Resolve(assemblyNameReference);
|
||||
if (assembly == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SearchForBaseModulesRecursive(assembly, assemblyResolver, ref unityModule, ref netcodeModule, visited);
|
||||
|
||||
if (unityModule != null && netcodeModule != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static (ModuleDefinition UnityModule, ModuleDefinition NetcodeModule) FindBaseModules(AssemblyDefinition assemblyDefinition, PostProcessorAssemblyResolver assemblyResolver)
|
||||
{
|
||||
ModuleDefinition unityModule = null;
|
||||
ModuleDefinition netcodeModule = null;
|
||||
var visited = new HashSet<string>();
|
||||
SearchForBaseModulesRecursive(assemblyDefinition, assemblyResolver, ref unityModule, ref netcodeModule, visited);
|
||||
|
||||
return (unityModule, netcodeModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
@@ -17,9 +16,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
@@ -33,13 +30,22 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out m_AssemblyResolver);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// modules
|
||||
(_, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver);
|
||||
|
||||
if (m_NetcodeModule == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
@@ -61,7 +67,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -92,6 +98,8 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private ModuleDefinition m_NetcodeModule;
|
||||
private PostProcessorAssemblyResolver m_AssemblyResolver;
|
||||
|
||||
private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef;
|
||||
private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
|
||||
@@ -106,63 +114,102 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]);
|
||||
// Different environments seem to have different situations...
|
||||
// Some have these definitions in netstandard.dll...
|
||||
// some seem to have them elsewhere...
|
||||
// Since they're standard .net classes they're not going to cause
|
||||
// the same issues as referencing other assemblies, in theory, since
|
||||
// the definitions should be standard and consistent across platforms
|
||||
// (i.e., there's no #if UNITY_EDITOR in them that could create
|
||||
// invalid IL code)
|
||||
TypeDefinition typeTypeDef = moduleDefinition.ImportReference(typeof(Type)).Resolve();
|
||||
TypeDefinition listTypeDef = moduleDefinition.ImportReference(typeof(List<>)).Resolve();
|
||||
|
||||
var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler);
|
||||
m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerType);
|
||||
foreach (var fieldInfo in messageWithHandlerType.GetFields())
|
||||
TypeDefinition messageHandlerTypeDef = null;
|
||||
TypeDefinition 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;
|
||||
}
|
||||
}
|
||||
|
||||
var messagingSystemType = typeof(MessagingSystem);
|
||||
foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var methodDef in messagingSystemTypeDef.Methods)
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_ReceiveMessageName:
|
||||
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -218,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)
|
||||
|
||||
@@ -2,18 +2,14 @@ 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;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
|
||||
internal sealed class INetworkSerializableILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
@@ -71,139 +67,31 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ImportReferences(mainModule))
|
||||
var structTypes = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
|
||||
foreach (var type in structTypes)
|
||||
{
|
||||
// Initialize all the delegates for various NetworkVariable types to ensure they can be serailized
|
||||
|
||||
// Find all types we know we're going to want to serialize.
|
||||
// The list of these types includes:
|
||||
// - Non-generic INetworkSerializable types
|
||||
// - Non-Generic INetworkSerializeByMemcpy types
|
||||
// - Enums that are not declared within generic types
|
||||
// We can't process generic types because, to initialize a generic, we need a value
|
||||
// for `T` to initialize it with.
|
||||
var networkSerializableTypes = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
var structTypes = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
var enumTypes = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().IsEnum && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
|
||||
// Now, to support generics, we have to do an extra pass.
|
||||
// We look for any type that's a NetworkBehaviour type
|
||||
// Then we look through all the fields in that type, finding any field whose type is
|
||||
// descended from `NetworkVariableSerialization`. Then we check `NetworkVariableSerialization`'s
|
||||
// `T` value, and if it's a generic, then we know it was missed in the above sweep and needs
|
||||
// to be initialized. Now we have a full generic instance rather than a generic definition,
|
||||
// so we can validly generate an initializer for that particular instance of the generic type.
|
||||
var networkSerializableTypesSet = new HashSet<TypeReference>(networkSerializableTypes);
|
||||
var structTypesSet = new HashSet<TypeReference>(structTypes);
|
||||
var enumTypesSet = new HashSet<TypeReference>(enumTypes);
|
||||
var typeStack = new List<TypeReference>();
|
||||
foreach (var type in mainModule.GetTypes())
|
||||
// We'll avoid some confusion by ensuring users only choose one of the two
|
||||
// serialization schemes - by method OR by memcpy, not both. We'll also do a cursory
|
||||
// check that INetworkSerializeByMemcpy types are unmanaged.
|
||||
if (type.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
|
||||
{
|
||||
// Check if it's a NetworkBehaviour
|
||||
if (type.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName))
|
||||
if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
|
||||
{
|
||||
// Iterate fields looking for NetworkVariableSerialization fields
|
||||
foreach (var field in type.Fields)
|
||||
{
|
||||
// Get the field type and its base type
|
||||
var fieldType = field.FieldType;
|
||||
var baseType = fieldType.Resolve().BaseType;
|
||||
if (baseType == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// This type stack is used for resolving NetworkVariableSerialization's T value
|
||||
// When looking at base types, we get the type definition rather than the
|
||||
// type reference... which means that we get the generic definition with an
|
||||
// undefined T rather than the instance with the type filled in.
|
||||
// We then have to walk backward back down the type stack to resolve what T
|
||||
// is.
|
||||
typeStack.Clear();
|
||||
typeStack.Add(fieldType);
|
||||
// Iterate through the base types until we get to Object.
|
||||
// Object is the base for everything so we'll stop when we hit that.
|
||||
while (baseType.Name != mainModule.TypeSystem.Object.Name)
|
||||
{
|
||||
// If we've found a NetworkVariableSerialization type...
|
||||
if (baseType.IsGenericInstance && baseType.Resolve() == m_NetworkVariableSerializationType)
|
||||
{
|
||||
// Then we need to figure out what T is
|
||||
var genericType = (GenericInstanceType)baseType;
|
||||
var underlyingType = genericType.GenericArguments[0];
|
||||
if (underlyingType.Resolve() == null)
|
||||
{
|
||||
underlyingType = ResolveGenericType(underlyingType, typeStack);
|
||||
}
|
||||
|
||||
// If T is generic...
|
||||
if (underlyingType.IsGenericInstance)
|
||||
{
|
||||
// Then we pick the correct set to add it to and set it up
|
||||
// for initialization.
|
||||
if (underlyingType.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
|
||||
{
|
||||
networkSerializableTypesSet.Add(underlyingType);
|
||||
}
|
||||
|
||||
if (underlyingType.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
|
||||
{
|
||||
structTypesSet.Add(underlyingType);
|
||||
}
|
||||
|
||||
if (underlyingType.Resolve().IsEnum)
|
||||
{
|
||||
enumTypesSet.Add(underlyingType);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
typeStack.Add(baseType);
|
||||
baseType = baseType.Resolve().BaseType;
|
||||
}
|
||||
}
|
||||
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other.");
|
||||
}
|
||||
// We'll also avoid some confusion by ensuring users only choose one of the two
|
||||
// serialization schemes - by method OR by memcpy, not both. We'll also do a cursory
|
||||
// check that INetworkSerializeByMemcpy types are unmanaged.
|
||||
else if (type.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
|
||||
if (!type.IsValueType)
|
||||
{
|
||||
if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
|
||||
{
|
||||
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other.");
|
||||
}
|
||||
if (!type.IsValueType)
|
||||
{
|
||||
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types.");
|
||||
}
|
||||
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types.");
|
||||
}
|
||||
}
|
||||
|
||||
if (networkSerializableTypes.Count + structTypes.Count + enumTypes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Finally we add to the module initializer some code to initialize the delegates in
|
||||
// NetworkVariableSerialization<T> for all necessary values of T, by calling initialization
|
||||
// methods in NetworkVariableHelpers.
|
||||
CreateModuleInitializer(assemblyDefinition, networkSerializableTypesSet.ToList(), structTypesSet.ToList(), enumTypesSet.ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -228,102 +116,5 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private MethodReference m_InitializeDelegatesNetworkSerializable_MethodRef;
|
||||
private MethodReference m_InitializeDelegatesStruct_MethodRef;
|
||||
private MethodReference m_InitializeDelegatesEnum_MethodRef;
|
||||
|
||||
private TypeDefinition m_NetworkVariableSerializationType;
|
||||
|
||||
private const string k_InitializeNetworkSerializableMethodName = nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable);
|
||||
private const string k_InitializeStructMethodName = nameof(NetworkVariableHelper.InitializeDelegatesStruct);
|
||||
private const string k_InitializeEnumMethodName = nameof(NetworkVariableHelper.InitializeDelegatesEnum);
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
|
||||
var helperType = typeof(NetworkVariableHelper);
|
||||
foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
{
|
||||
case k_InitializeNetworkSerializableMethodName:
|
||||
m_InitializeDelegatesNetworkSerializable_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
case k_InitializeStructMethodName:
|
||||
m_InitializeDelegatesStruct_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
case k_InitializeEnumMethodName:
|
||||
m_InitializeDelegatesEnum_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_NetworkVariableSerializationType = moduleDefinition.ImportReference(typeof(NetworkVariableSerialization<>)).Resolve();
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
if (staticCtorMethodDef == null)
|
||||
{
|
||||
staticCtorMethodDef = new MethodDefinition(
|
||||
".cctor", // Static Constructor (constant-constructor)
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.RTSpecialName |
|
||||
MethodAttributes.Static,
|
||||
typeDefinition.Module.TypeSystem.Void);
|
||||
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
typeDefinition.Methods.Add(staticCtorMethodDef);
|
||||
}
|
||||
|
||||
return staticCtorMethodDef;
|
||||
}
|
||||
|
||||
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
|
||||
// message types in the assembly with MessagingSystem.
|
||||
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
|
||||
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
|
||||
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
|
||||
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeReference> networkSerializableTypes, List<TypeReference> structTypes, List<TypeReference> enumTypes)
|
||||
{
|
||||
foreach (var typeDefinition in assembly.MainModule.Types)
|
||||
{
|
||||
if (typeDefinition.FullName == "<Module>")
|
||||
{
|
||||
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
|
||||
|
||||
var processor = staticCtorMethodDef.Body.GetILProcessor();
|
||||
|
||||
var instructions = new List<Instruction>();
|
||||
|
||||
foreach (var type in structTypes)
|
||||
{
|
||||
var method = new GenericInstanceMethod(m_InitializeDelegatesStruct_MethodRef);
|
||||
method.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, method));
|
||||
}
|
||||
|
||||
foreach (var type in networkSerializableTypes)
|
||||
{
|
||||
var method = new GenericInstanceMethod(m_InitializeDelegatesNetworkSerializable_MethodRef);
|
||||
method.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, method));
|
||||
}
|
||||
|
||||
foreach (var type in enumTypes)
|
||||
{
|
||||
var method = new GenericInstanceMethod(m_InitializeDelegatesEnum_MethodRef);
|
||||
method.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, method));
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
@@ -23,8 +22,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
@@ -35,7 +33,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
@@ -46,11 +43,27 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return null;
|
||||
}
|
||||
|
||||
// modules
|
||||
(m_UnityModule, m_NetcodeModule) = CodeGenHelpers.FindBaseModules(assemblyDefinition, m_AssemblyResolver);
|
||||
|
||||
if (m_UnityModule == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot find Unity module: {CodeGenHelpers.UnityModuleName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (m_NetcodeModule == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot find Netcode module: {CodeGenHelpers.NetcodeModuleName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
m_MainModule = mainModule;
|
||||
|
||||
if (ImportReferences(mainModule))
|
||||
{
|
||||
// process `NetworkBehaviour` types
|
||||
@@ -60,10 +73,12 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
.Where(t => t.IsSubclassOf(CodeGenHelpers.NetworkBehaviour_FullName))
|
||||
.ToList()
|
||||
.ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines));
|
||||
|
||||
CreateNetworkVariableTypeInitializers(assemblyDefinition);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -92,7 +107,117 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
if (staticCtorMethodDef == null)
|
||||
{
|
||||
staticCtorMethodDef = new MethodDefinition(
|
||||
".cctor", // Static Constructor (constant-constructor)
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.RTSpecialName |
|
||||
MethodAttributes.Static,
|
||||
typeDefinition.Module.TypeSystem.Void);
|
||||
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
typeDefinition.Methods.Add(staticCtorMethodDef);
|
||||
}
|
||||
|
||||
return staticCtorMethodDef;
|
||||
}
|
||||
|
||||
private bool IsMemcpyableType(TypeReference type)
|
||||
{
|
||||
foreach (var supportedType in BaseSupportedTypes)
|
||||
{
|
||||
if (type.FullName == supportedType.FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
|
||||
{
|
||||
foreach (var typeDefinition in assembly.MainModule.Types)
|
||||
{
|
||||
if (typeDefinition.FullName == "<Module>")
|
||||
{
|
||||
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
|
||||
|
||||
var processor = staticCtorMethodDef.Body.GetILProcessor();
|
||||
|
||||
var instructions = new List<Instruction>();
|
||||
|
||||
foreach (var type in m_WrappedNetworkVariableTypes)
|
||||
{
|
||||
// If a serializable type isn't found, FallbackSerializer will be used automatically, which will
|
||||
// call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton
|
||||
// for types that aren't in our official supported types list.
|
||||
GenericInstanceMethod serializeMethod = null;
|
||||
GenericInstanceMethod equalityMethod;
|
||||
|
||||
if (type.IsValueType)
|
||||
{
|
||||
if (type.HasInterface(typeof(INetworkSerializeByMemcpy).FullName) || type.Resolve().IsEnum || IsMemcpyableType(type))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef);
|
||||
}
|
||||
else if (type.HasInterface(typeof(INetworkSerializable).FullName))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef);
|
||||
}
|
||||
else if (type.HasInterface(CodeGenHelpers.IUTF8Bytes_FullName) && type.HasInterface(k_INativeListBool_FullName))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef);
|
||||
}
|
||||
|
||||
if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">"))
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type.HasInterface(typeof(INetworkSerializable).FullName))
|
||||
{
|
||||
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef);
|
||||
}
|
||||
|
||||
if (type.HasInterface(typeof(IEquatable<>).FullName + "<" + type.FullName + ">"))
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (serializeMethod != null)
|
||||
{
|
||||
serializeMethod.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(serializeMethod)));
|
||||
}
|
||||
equalityMethod.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, m_MainModule.ImportReference(equalityMethod)));
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModuleDefinition m_MainModule;
|
||||
private ModuleDefinition m_UnityModule;
|
||||
private ModuleDefinition m_NetcodeModule;
|
||||
private PostProcessorAssemblyResolver m_AssemblyResolver;
|
||||
|
||||
private MethodReference m_Debug_LogError_MethodRef;
|
||||
@@ -123,14 +248,51 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
private FieldReference m_ServerRpcParams_Receive_FieldRef;
|
||||
private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef;
|
||||
private TypeReference m_ClientRpcParams_TypeRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
|
||||
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
|
||||
|
||||
private TypeReference m_FastBufferWriter_TypeRef;
|
||||
private Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
|
||||
private readonly Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private readonly List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
|
||||
|
||||
private TypeReference m_FastBufferReader_TypeRef;
|
||||
private Dictionary<string, MethodReference> m_FastBufferReader_ReadValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private List<MethodReference> m_FastBufferReader_ExtensionMethodRefs = new List<MethodReference>();
|
||||
private readonly Dictionary<string, MethodReference> m_FastBufferReader_ReadValue_MethodRefs = new Dictionary<string, MethodReference>();
|
||||
private readonly List<MethodReference> m_FastBufferReader_ExtensionMethodRefs = new List<MethodReference>();
|
||||
|
||||
private HashSet<TypeReference> m_WrappedNetworkVariableTypes = new HashSet<TypeReference>();
|
||||
|
||||
internal static readonly Type[] BaseSupportedTypes = new[]
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(char),
|
||||
typeof(decimal),
|
||||
typeof(double),
|
||||
typeof(float),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(Vector2),
|
||||
typeof(Vector3),
|
||||
typeof(Vector2Int),
|
||||
typeof(Vector3Int),
|
||||
typeof(Vector4),
|
||||
typeof(Quaternion),
|
||||
typeof(Color),
|
||||
typeof(Color32),
|
||||
typeof(Ray),
|
||||
typeof(Ray2D)
|
||||
};
|
||||
|
||||
private const string k_Debug_LogError = nameof(Debug.LogError);
|
||||
private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId);
|
||||
@@ -157,160 +319,243 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
private const string k_ServerRpcParams_Receive = nameof(ServerRpcParams.Receive);
|
||||
private const string k_ServerRpcReceiveParams_SenderClientId = nameof(ServerRpcReceiveParams.SenderClientId);
|
||||
|
||||
// CodeGen cannot reference the collections assembly to do a typeof() on it due to a bug that causes that to crash.
|
||||
private const string k_INativeListBool_FullName = "Unity.Collections.INativeList`1<System.Byte>";
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
var debugType = typeof(Debug);
|
||||
foreach (var methodInfo in debugType.GetMethods())
|
||||
TypeDefinition debugTypeDef = null;
|
||||
foreach (var unityTypeDef in m_UnityModule.GetAllTypes())
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
if (debugTypeDef == null && unityTypeDef.FullName == typeof(Debug).FullName)
|
||||
{
|
||||
debugTypeDef = unityTypeDef;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TypeDefinition networkManagerTypeDef = null;
|
||||
TypeDefinition networkBehaviourTypeDef = null;
|
||||
TypeDefinition networkHandlerDelegateTypeDef = null;
|
||||
TypeDefinition rpcParamsTypeDef = null;
|
||||
TypeDefinition serverRpcParamsTypeDef = null;
|
||||
TypeDefinition clientRpcParamsTypeDef = null;
|
||||
TypeDefinition fastBufferWriterTypeDef = null;
|
||||
TypeDefinition fastBufferReaderTypeDef = null;
|
||||
TypeDefinition networkVariableSerializationTypesTypeDef = null;
|
||||
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
|
||||
{
|
||||
if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager))
|
||||
{
|
||||
networkManagerTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkBehaviourTypeDef == null && netcodeTypeDef.Name == nameof(NetworkBehaviour))
|
||||
{
|
||||
networkBehaviourTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler))
|
||||
{
|
||||
networkHandlerDelegateTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(__RpcParams))
|
||||
{
|
||||
rpcParamsTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serverRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ServerRpcParams))
|
||||
{
|
||||
serverRpcParamsTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (clientRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ClientRpcParams))
|
||||
{
|
||||
clientRpcParamsTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fastBufferWriterTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferWriter))
|
||||
{
|
||||
fastBufferWriterTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fastBufferReaderTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferReader))
|
||||
{
|
||||
fastBufferReaderTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (networkVariableSerializationTypesTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableSerializationTypes))
|
||||
{
|
||||
networkVariableSerializationTypesTypeDef = netcodeTypeDef;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodDef in debugTypeDef.Methods)
|
||||
{
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_Debug_LogError:
|
||||
if (methodInfo.GetParameters().Length == 1)
|
||||
if (methodDef.Parameters.Count == 1)
|
||||
{
|
||||
m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_Debug_LogError_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var networkManagerType = typeof(NetworkManager);
|
||||
m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerType);
|
||||
foreach (var propertyInfo in networkManagerType.GetProperties())
|
||||
m_NetworkManager_TypeRef = moduleDefinition.ImportReference(networkManagerTypeDef);
|
||||
foreach (var propertyDef in networkManagerTypeDef.Properties)
|
||||
{
|
||||
switch (propertyInfo.Name)
|
||||
switch (propertyDef.Name)
|
||||
{
|
||||
case k_NetworkManager_LocalClientId:
|
||||
m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getLocalClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsListening:
|
||||
m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsListening_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsHost:
|
||||
m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsHost_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsServer:
|
||||
m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsServer_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkManager_IsClient:
|
||||
m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkManager_getIsClient_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fieldInfo in networkManagerType.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var fieldDef in networkManagerTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_NetworkManager_LogLevel:
|
||||
m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
case k_NetworkManager_rpc_func_table:
|
||||
m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add"));
|
||||
m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add");
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef.DeclaringType = fieldDef.FieldType;
|
||||
m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_func_table_Add_MethodRef);
|
||||
break;
|
||||
case k_NetworkManager_rpc_name_table:
|
||||
m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(fieldInfo.FieldType.GetMethod("Add"));
|
||||
m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add");
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef.DeclaringType = fieldDef.FieldType;
|
||||
m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_name_table_Add_MethodRef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var networkBehaviourType = typeof(NetworkBehaviour);
|
||||
m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourType);
|
||||
foreach (var propertyInfo in networkBehaviourType.GetProperties())
|
||||
m_NetworkBehaviour_TypeRef = moduleDefinition.ImportReference(networkBehaviourTypeDef);
|
||||
foreach (var propertyDef in networkBehaviourTypeDef.Properties)
|
||||
{
|
||||
switch (propertyInfo.Name)
|
||||
switch (propertyDef.Name)
|
||||
{
|
||||
case k_NetworkBehaviour_NetworkManager:
|
||||
m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkBehaviour_getNetworkManager_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
case k_NetworkBehaviour_OwnerClientId:
|
||||
m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyInfo.GetMethod);
|
||||
m_NetworkBehaviour_getOwnerClientId_MethodRef = moduleDefinition.ImportReference(propertyDef.GetMethod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodInfo in networkBehaviourType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var methodDef in networkBehaviourTypeDef.Methods)
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
switch (methodDef.Name)
|
||||
{
|
||||
case k_NetworkBehaviour_beginSendServerRpc:
|
||||
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_NetworkBehaviour_endSendServerRpc:
|
||||
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_NetworkBehaviour_beginSendClientRpc:
|
||||
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
case k_NetworkBehaviour_endSendClientRpc:
|
||||
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fieldInfo in networkBehaviourType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
foreach (var fieldDef in networkBehaviourTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_NetworkBehaviour_rpc_exec_stage:
|
||||
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var networkHandlerDelegateType = typeof(NetworkManager.RpcReceiveHandler);
|
||||
m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(networkHandlerDelegateType.GetConstructor(new[] { typeof(object), typeof(IntPtr) }));
|
||||
|
||||
var rpcParamsType = typeof(__RpcParams);
|
||||
m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsType);
|
||||
foreach (var fieldInfo in rpcParamsType.GetFields())
|
||||
foreach (var ctor in networkHandlerDelegateTypeDef.Resolve().GetConstructors())
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
if (ctor.HasParameters &&
|
||||
ctor.Parameters.Count == 2 &&
|
||||
ctor.Parameters[0].ParameterType.Name == nameof(System.Object) &&
|
||||
ctor.Parameters[1].ParameterType.Name == nameof(IntPtr))
|
||||
{
|
||||
m_NetworkHandlerDelegateCtor_MethodRef = moduleDefinition.ImportReference(ctor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_RpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsTypeDef);
|
||||
foreach (var fieldDef in rpcParamsTypeDef.Fields)
|
||||
{
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_RpcParams_Server:
|
||||
m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_RpcParams_Server_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
case k_RpcParams_Client:
|
||||
m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var serverRpcParamsType = typeof(ServerRpcParams);
|
||||
m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsType);
|
||||
foreach (var fieldInfo in serverRpcParamsType.GetFields())
|
||||
m_ServerRpcParams_TypeRef = moduleDefinition.ImportReference(serverRpcParamsTypeDef);
|
||||
foreach (var fieldDef in serverRpcParamsTypeDef.Fields)
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
switch (fieldDef.Name)
|
||||
{
|
||||
case k_ServerRpcParams_Receive:
|
||||
foreach (var recvFieldInfo in fieldInfo.FieldType.GetFields())
|
||||
foreach (var recvFieldDef in fieldDef.FieldType.Resolve().Fields)
|
||||
{
|
||||
switch (recvFieldInfo.Name)
|
||||
switch (recvFieldDef.Name)
|
||||
{
|
||||
case k_ServerRpcReceiveParams_SenderClientId:
|
||||
m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldInfo);
|
||||
m_ServerRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
m_ServerRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var clientRpcParamsType = typeof(ClientRpcParams);
|
||||
m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsType);
|
||||
m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsTypeDef);
|
||||
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterTypeDef);
|
||||
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderTypeDef);
|
||||
|
||||
var fastBufferWriterType = typeof(FastBufferWriter);
|
||||
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType);
|
||||
|
||||
var fastBufferReaderType = typeof(FastBufferReader);
|
||||
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType);
|
||||
|
||||
// Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented
|
||||
// methods to be called.
|
||||
// Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented methods to be called
|
||||
var assemblies = new List<AssemblyDefinition> { m_MainModule.Assembly };
|
||||
foreach (var reference in m_MainModule.AssemblyReferences)
|
||||
{
|
||||
@@ -371,9 +616,94 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var method in networkVariableSerializationTypesTypeDef.Methods)
|
||||
{
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (method.Name)
|
||||
{
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedByMemcpy):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializable):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_FixedString):
|
||||
m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedIEquatable):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedIEquatable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatable):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatable_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method;
|
||||
break;
|
||||
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_ManagedClassEquals):
|
||||
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This gets all fields from this type as well as any parent types, up to (but not including) the base NetworkBehaviour class
|
||||
// Importantly... this also resolves any generics, so if the base class is Foo<T> and contains a field of NetworkVariable<T>,
|
||||
// and this class is Bar : Foo<int>, it will properly resolve NetworkVariable<T> to NetworkVariable<int>.
|
||||
private void GetAllFieldsAndResolveGenerics(TypeDefinition type, ref List<TypeReference> fieldTypes, Dictionary<string, TypeReference> genericParameters = null)
|
||||
{
|
||||
foreach (var field in type.Fields)
|
||||
{
|
||||
if (field.FieldType.IsGenericInstance)
|
||||
{
|
||||
var genericType = (GenericInstanceType)field.FieldType;
|
||||
var newGenericType = new GenericInstanceType(field.FieldType.Resolve());
|
||||
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
|
||||
{
|
||||
var argument = genericType.GenericArguments[i];
|
||||
|
||||
if (genericParameters != null && genericParameters.ContainsKey(argument.Name))
|
||||
{
|
||||
newGenericType.GenericArguments.Add(genericParameters[argument.Name]);
|
||||
}
|
||||
else
|
||||
{
|
||||
newGenericType.GenericArguments.Add(argument);
|
||||
}
|
||||
}
|
||||
fieldTypes.Add(newGenericType);
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldTypes.Add(field.FieldType);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.BaseType == null || type.BaseType.Name == nameof(NetworkBehaviour))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var genericParams = new Dictionary<string, TypeReference>();
|
||||
var resolved = type.BaseType.Resolve();
|
||||
if (type.BaseType.IsGenericInstance)
|
||||
{
|
||||
var genericType = (GenericInstanceType)type.BaseType;
|
||||
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
|
||||
{
|
||||
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
|
||||
}
|
||||
}
|
||||
GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams);
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines)
|
||||
{
|
||||
var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>();
|
||||
@@ -416,6 +746,28 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance)
|
||||
{
|
||||
var fieldTypes = new List<TypeReference>();
|
||||
GetAllFieldsAndResolveGenerics(typeDefinition, ref fieldTypes);
|
||||
foreach (var type in fieldTypes)
|
||||
{
|
||||
//var type = field.FieldType;
|
||||
if (type.IsGenericInstance)
|
||||
{
|
||||
if (type.Resolve().Name == typeof(NetworkVariable<>).Name || type.Resolve().Name == typeof(NetworkList<>).Name)
|
||||
{
|
||||
var genericInstanceType = (GenericInstanceType)type;
|
||||
var wrappedType = genericInstanceType.GenericArguments[0];
|
||||
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
|
||||
{
|
||||
m_WrappedNetworkVariableTypes.Add(wrappedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rpcHandlers.Count > 0 || rpcNames.Count > 0)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
@@ -609,11 +961,21 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
#if CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES
|
||||
var resolvedConstraint = constraint.Resolve();
|
||||
var constraintTypeRef = constraint;
|
||||
#else
|
||||
var resolvedConstraint = constraint.ConstraintType.Resolve();
|
||||
var constraintTypeRef = constraint.ConstraintType;
|
||||
#endif
|
||||
|
||||
var resolvedConstraintName = resolvedConstraint.FullNameWithGenericParameters(new[] { method.GenericParameters[0] }, new[] { checkType });
|
||||
if (constraintTypeRef.IsGenericInstance)
|
||||
{
|
||||
var genericConstraint = (GenericInstanceType)constraintTypeRef;
|
||||
if (genericConstraint.HasGenericArguments && genericConstraint.GenericArguments[0].Resolve() != null)
|
||||
{
|
||||
resolvedConstraintName = constraintTypeRef.FullName;
|
||||
}
|
||||
}
|
||||
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
||||
@@ -659,7 +1021,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
if (parameters[1].IsIn)
|
||||
{
|
||||
if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() &&
|
||||
if (((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName &&
|
||||
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
|
||||
{
|
||||
methodRef = method;
|
||||
@@ -669,8 +1031,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (parameters[1].ParameterType.Resolve() == paramType.Resolve() &&
|
||||
if (parameters[1].ParameterType.FullName == paramType.FullName &&
|
||||
parameters[1].ParameterType.IsArray == paramType.IsArray)
|
||||
{
|
||||
methodRef = method;
|
||||
@@ -743,12 +1104,22 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
#if CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES
|
||||
var resolvedConstraint = constraint.Resolve();
|
||||
var constraintTypeRef = constraint;
|
||||
#else
|
||||
var resolvedConstraint = constraint.ConstraintType.Resolve();
|
||||
var constraintTypeRef = constraint.ConstraintType;
|
||||
#endif
|
||||
|
||||
|
||||
var resolvedConstraintName = resolvedConstraint.FullNameWithGenericParameters(new[] { method.GenericParameters[0] }, new[] { checkType });
|
||||
if (constraintTypeRef.IsGenericInstance)
|
||||
{
|
||||
var genericConstraint = (GenericInstanceType)constraintTypeRef;
|
||||
if (genericConstraint.HasGenericArguments && genericConstraint.GenericArguments[0].Resolve() != null)
|
||||
{
|
||||
resolvedConstraintName = constraintTypeRef.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
|
||||
@@ -793,7 +1164,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
var parameters = method.Resolve().Parameters;
|
||||
if (method.Name == k_ReadValueMethodName &&
|
||||
parameters[1].IsOut &&
|
||||
parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() &&
|
||||
((ByReferenceType)parameters[1].ParameterType).ElementType.FullName == paramType.FullName &&
|
||||
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
|
||||
{
|
||||
methodRef = method;
|
||||
@@ -1142,7 +1513,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError(methodDefinition, $"Don't know how to serialize {paramType.Name} - implement {nameof(INetworkSerializable)}, tag memcpyable struct with {nameof(INetworkSerializeByMemcpy)}, or add an extension method for {nameof(FastBufferWriter)}.{k_WriteValueMethodName} to define serialization.");
|
||||
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1456,7 +1827,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError(methodDefinition, $"Don't know how to serialize {paramType.Name} - implement {nameof(INetworkSerializable)}, tag memcpyable struct with {nameof(INetworkSerializeByMemcpy)}, or add an extension method for {nameof(FastBufferWriter)}.{k_WriteValueMethodName} to define serialization.");
|
||||
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
case nameof(NetworkBehaviour):
|
||||
ProcessNetworkBehaviour(typeDefinition);
|
||||
break;
|
||||
case nameof(NetworkVariableHelper):
|
||||
ProcessNetworkVariableHelper(typeDefinition);
|
||||
break;
|
||||
case nameof(__RpcParams):
|
||||
typeDefinition.IsPublic = true;
|
||||
break;
|
||||
@@ -103,25 +100,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkVariableHelper(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var methodDefinition in typeDefinition.Methods)
|
||||
{
|
||||
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesEnum))
|
||||
{
|
||||
methodDefinition.IsPublic = true;
|
||||
}
|
||||
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesStruct))
|
||||
{
|
||||
methodDefinition.IsPublic = true;
|
||||
}
|
||||
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable))
|
||||
{
|
||||
methodDefinition.IsPublic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var nestedType in typeDefinition.NestedTypes)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"name": "Unity.Netcode.Editor.CodeGen",
|
||||
"rootNamespace": "Unity.Netcode.Editor.CodeGen",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime"
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
|
||||
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
|
||||
@@ -151,6 +151,8 @@ namespace Unity.Netcode.Editor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
@@ -218,6 +220,11 @@ namespace Unity.Netcode.Editor
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
// This can be null and throw an exception when running test runner in the editor
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// When we first add a NetworkBehaviour this editor will be enabled
|
||||
// so we go ahead and check for an already existing NetworkObject here
|
||||
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
|
||||
@@ -225,6 +232,11 @@ namespace Unity.Netcode.Editor
|
||||
|
||||
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
|
||||
|
||||
/// <summary>
|
||||
/// Recursively finds the root parent of a <see cref="Transform"/>
|
||||
/// </summary>
|
||||
/// <param name="transform">The current <see cref="Transform"/> we are inspecting for a parent</param>
|
||||
/// <returns>the root parent for the first <see cref="Transform"/> passed into the method</returns>
|
||||
public static Transform GetRootParentTransform(Transform transform)
|
||||
{
|
||||
if (transform.parent == null || transform.parent == transform)
|
||||
@@ -239,6 +251,8 @@ namespace Unity.Netcode.Editor
|
||||
/// does not already have a NetworkObject component. If not it will notify
|
||||
/// the user that NetworkBehaviours require a NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="gameObject"><see cref="GameObject"/> to start checking for a <see cref="NetworkObject"/></param>
|
||||
/// <param name="networkObjectRemoved">used internally</param>
|
||||
public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false)
|
||||
{
|
||||
// If there are no NetworkBehaviours or no gameObject, then exit early
|
||||
@@ -249,11 +263,42 @@ namespace Unity.Netcode.Editor
|
||||
|
||||
// Now get the root parent transform to the current GameObject (or itself)
|
||||
var rootTransform = GetRootParentTransform(gameObject.transform);
|
||||
if (!rootTransform.TryGetComponent<NetworkManager>(out var networkManager))
|
||||
{
|
||||
networkManager = rootTransform.GetComponentInChildren<NetworkManager>();
|
||||
}
|
||||
|
||||
// If there is a NetworkManager, then notify the user that a NetworkManager cannot have NetworkBehaviour components
|
||||
if (networkManager != null)
|
||||
{
|
||||
var networkBehaviours = networkManager.gameObject.GetComponents<NetworkBehaviour>();
|
||||
var networkBehavioursChildren = networkManager.gameObject.GetComponentsInChildren<NetworkBehaviour>();
|
||||
if (networkBehaviours.Length > 0 || networkBehavioursChildren.Length > 0)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("NetworkBehaviour or NetworkManager Cannot Be Added", $"{nameof(NetworkManager)}s cannot have {nameof(NetworkBehaviour)} components added to the root parent or any of its children." +
|
||||
$" Would you like to remove the NetworkManager or NetworkBehaviour?", "NetworkManager", "NetworkBehaviour"))
|
||||
{
|
||||
DestroyImmediate(networkManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var networkBehaviour in networkBehaviours)
|
||||
{
|
||||
DestroyImmediate(networkBehaviour);
|
||||
}
|
||||
|
||||
foreach (var networkBehaviour in networkBehaviours)
|
||||
{
|
||||
DestroyImmediate(networkBehaviour);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
|
||||
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
|
||||
var networkObject = rootTransform.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
if (!rootTransform.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -200,6 +204,7 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
@@ -209,18 +214,6 @@ namespace Unity.Netcode.Editor
|
||||
DrawInstallMultiplayerToolsTip();
|
||||
#endif
|
||||
|
||||
{
|
||||
var iterator = serializedObject.GetIterator();
|
||||
|
||||
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
|
||||
{
|
||||
EditorGUILayout.PropertyField(iterator, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Unity.Netcode.Editor
|
||||
{
|
||||
var scenesList = EditorBuildSettings.scenes.ToList();
|
||||
var activeScene = SceneManager.GetActiveScene();
|
||||
var isSceneInBuildSettings = scenesList.Where((c) => c.path == activeScene.path).Count() == 1;
|
||||
var isSceneInBuildSettings = scenesList.Count((c) => c.path == activeScene.path) == 1;
|
||||
var networkManager = Object.FindObjectOfType<NetworkManager>();
|
||||
if (!isSceneInBuildSettings && networkManager != null)
|
||||
{
|
||||
@@ -109,9 +109,8 @@ namespace Unity.Netcode.Editor
|
||||
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
|
||||
{
|
||||
// Check for any NetworkObject at the same gameObject relative layer
|
||||
var networkObject = networkManager.gameObject.GetComponent<NetworkObject>();
|
||||
|
||||
if (networkObject == null)
|
||||
if (!networkManager.gameObject.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
// if none is found, check to see if any children have a NetworkObject
|
||||
networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,9 @@ using Unity.Netcode.Components;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CustomEditor"/> for <see cref="NetworkTransform"/>
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkTransform), true)]
|
||||
public class NetworkTransformEditor : UnityEditor.Editor
|
||||
{
|
||||
@@ -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);
|
||||
|
||||
@@ -13,6 +13,26 @@
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
"expression": "",
|
||||
"define": "MULTIPLAYER_TOOLS"
|
||||
},
|
||||
{
|
||||
"name": "Unity",
|
||||
"expression": "(0,2022.2.0a5)",
|
||||
"define": "UNITY_UNET_PRESENT"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.animation",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_ANIMATION"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics2d",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS2D"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Netcode for GameObjects is a Unity package that provides networking capabilities
|
||||
|
||||
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
|
||||
|
||||
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks.
|
||||
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld) guide for a taste of how to use the framework for basic networked tasks.
|
||||
|
||||
### Community and Feedback
|
||||
|
||||
|
||||
@@ -12,3 +12,4 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.Tools.Adapters.Ngo1WithUtp2")]
|
||||
|
||||
@@ -150,8 +150,16 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
/// <summary>
|
||||
/// The number of RTT samples that is kept as an average for calculations
|
||||
/// </summary>
|
||||
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
|
||||
|
||||
/// <summary>
|
||||
/// The number of slots used for RTT calculations. This is the maximum amount of in-flight messages
|
||||
/// </summary>
|
||||
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
|
||||
|
||||
/// <summary>
|
||||
/// Returns a base64 encoded version of the configuration
|
||||
/// </summary>
|
||||
|
||||
@@ -235,14 +235,42 @@ namespace Unity.Netcode
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||
{
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
if (clientRpcParams.Send.TargetClientIds != null)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
client.Key,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
targetClientId,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
}
|
||||
}
|
||||
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
{
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
targetClientId,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
|
||||
while (observerEnumerator.MoveNext())
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
observerEnumerator.Current,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcWriteSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -331,7 +359,8 @@ namespace Unity.Netcode
|
||||
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
|
||||
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
|
||||
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
|
||||
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
|
||||
// We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy)
|
||||
if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -435,14 +464,41 @@ namespace Unity.Netcode
|
||||
IsSpawned = true;
|
||||
InitializeVariables();
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkSpawn();
|
||||
}
|
||||
|
||||
internal void VisibleOnNetworkSpawn()
|
||||
{
|
||||
try
|
||||
{
|
||||
OnNetworkSpawn();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
|
||||
InitializeVariables();
|
||||
if (IsServer)
|
||||
{
|
||||
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
|
||||
// NetworkList, we need to mark the object as free of updates.
|
||||
// This should happen for all objects on the machine triggering the spawn.
|
||||
PostNetworkVariableWrite(true);
|
||||
}
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkDespawn()
|
||||
{
|
||||
IsSpawned = false;
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkDespawn();
|
||||
try
|
||||
{
|
||||
OnNetworkDespawn();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -470,6 +526,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
|
||||
/// </summary>
|
||||
/// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param>
|
||||
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
|
||||
|
||||
private bool m_VarInit = false;
|
||||
@@ -574,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 targetClientId)
|
||||
internal void PreVariableUpdate()
|
||||
{
|
||||
if (!m_VarInit)
|
||||
{
|
||||
@@ -591,6 +662,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong targetClientId)
|
||||
{
|
||||
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
@@ -662,11 +737,11 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,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
|
||||
@@ -764,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].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
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// The main component of the library
|
||||
/// </summary>
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkManager), -100)]
|
||||
[AddComponentMenu("Netcode/Network Manager", -100)]
|
||||
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
|
||||
{
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
@@ -51,15 +51,25 @@ namespace Unity.Netcode
|
||||
|
||||
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
|
||||
{
|
||||
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
|
||||
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\"";
|
||||
}
|
||||
|
||||
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; }
|
||||
internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
|
||||
|
||||
internal void MarkNetworkObjectDirty(NetworkObject networkObject)
|
||||
{
|
||||
BehaviourUpdater.AddForUpdate(networkObject);
|
||||
}
|
||||
|
||||
internal MessagingSystem MessagingSystem { get; private set; }
|
||||
|
||||
private NetworkPrefabHandler m_PrefabHandler;
|
||||
|
||||
internal Dictionary<ulong, ConnectionApprovalResponse> ClientsToApprove = new Dictionary<ulong, ConnectionApprovalResponse>();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="NetworkPrefabHandler"/> instance created after starting the <see cref="NetworkManager"/>
|
||||
/// </summary>
|
||||
public NetworkPrefabHandler PrefabHandler
|
||||
{
|
||||
get
|
||||
@@ -177,8 +187,7 @@ namespace Unity.Netcode
|
||||
/// <returns>a <see cref="GameObject"/> that is either the override or if no overrides exist it returns the same as the one passed in as a parameter</returns>
|
||||
public GameObject GetNetworkPrefabOverride(GameObject gameObject)
|
||||
{
|
||||
var networkObject = gameObject.GetComponent<NetworkObject>();
|
||||
if (networkObject != null)
|
||||
if (gameObject.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
if (NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
|
||||
{
|
||||
@@ -195,23 +204,38 @@ namespace Unity.Netcode
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the <see cref="NetworkTimeSystem"/> of the NetworkManager.
|
||||
/// Prefer the use of the LocalTime and ServerTime properties
|
||||
/// </summary>
|
||||
public NetworkTimeSystem NetworkTimeSystem { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the <see cref="NetworkTickSystem"/> of the NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkTickSystem NetworkTickSystem { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The local <see cref="NetworkTime"/>
|
||||
/// </summary>
|
||||
public NetworkTime LocalTime => NetworkTickSystem?.LocalTime ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="NetworkTime"/> on the server
|
||||
/// </summary>
|
||||
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the application should be set to run in background
|
||||
/// </summary>
|
||||
[HideInInspector] public bool RunInBackground = true;
|
||||
[HideInInspector]
|
||||
public bool RunInBackground = true;
|
||||
|
||||
/// <summary>
|
||||
/// The log level to use
|
||||
/// </summary>
|
||||
[HideInInspector] public LogLevel LogLevel = LogLevel.Normal;
|
||||
[HideInInspector]
|
||||
public LogLevel LogLevel = LogLevel.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// The singleton instance of the NetworkManager
|
||||
@@ -225,10 +249,19 @@ namespace Unity.Netcode
|
||||
|
||||
internal IDeferredMessageManager DeferredMessageManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CustomMessagingManager for this NetworkManager
|
||||
/// </summary>
|
||||
public CustomMessagingManager CustomMessagingManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="NetworkSceneManager"/> instance created after starting the <see cref="NetworkManager"/>
|
||||
/// </summary>
|
||||
public NetworkSceneManager SceneManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The client id used to represent the server
|
||||
/// </summary>
|
||||
public const ulong ServerClientId = 0;
|
||||
|
||||
/// <summary>
|
||||
@@ -333,11 +366,25 @@ namespace Unity.Netcode
|
||||
public bool IsListening { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are connected as a client
|
||||
/// When true, the client is connected, approved, and synchronized with
|
||||
/// the server.
|
||||
/// </summary>
|
||||
public bool IsConnectedClient { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is true when the client has been approved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only reflects the client's approved status and does not mean the client
|
||||
/// has finished the connection and synchronization process. The server-host will
|
||||
/// always be approved upon being starting the <see cref="NetworkManager"/>
|
||||
/// <see cref="IsConnectedClient"/>
|
||||
/// </remarks>
|
||||
public bool IsApproved { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to determine if the <see cref="NetworkManager"/> is currently shutting itself down
|
||||
/// </summary>
|
||||
public bool ShutdownInProgress { get { return m_ShuttingDown; } }
|
||||
|
||||
/// <summary>
|
||||
@@ -358,26 +405,87 @@ namespace Unity.Netcode
|
||||
public event Action OnServerStarted = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type called when connection has been approved. This only has to be set on the server.
|
||||
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
||||
/// </summary>
|
||||
/// <param name="createPlayerObject">If true, a player object will be created. Otherwise the client will have no object.</param>
|
||||
/// <param name="playerPrefabHash">The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.</param>
|
||||
/// <param name="approved">Whether or not the client was approved</param>
|
||||
/// <param name="position">The position to spawn the client at. If null, the prefab position is used.</param>
|
||||
/// <param name="rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
|
||||
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation);
|
||||
/// <remarks>
|
||||
/// A failure of the transport is always followed by the <see cref="NetworkManager"/> shutting down. Recovering
|
||||
/// from a transport failure would normally entail reconfiguring the transport (e.g. re-authenticating, or
|
||||
/// recreating a new service allocation depending on the transport) and restarting the client/server/host.
|
||||
/// </remarks>
|
||||
public event Action OnTransportFailure = null;
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke during connection approval
|
||||
/// Connection Approval Response
|
||||
/// </summary>
|
||||
public event Action<byte[], ulong, ConnectionApprovedDelegate> ConnectionApprovalCallback = null;
|
||||
public class ConnectionApprovalResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not the client was approved
|
||||
/// </summary>
|
||||
public bool Approved;
|
||||
/// <summary>
|
||||
/// If true, a player object will be created. Otherwise the client will have no object.
|
||||
/// </summary>
|
||||
public bool CreatePlayerObject;
|
||||
/// <summary>
|
||||
/// The prefabHash to use for the client. If createPlayerObject is false, this is ignored. If playerPrefabHash is null, the default player prefab is used.
|
||||
/// </summary>
|
||||
public uint? PlayerPrefabHash;
|
||||
/// <summary>
|
||||
/// The position to spawn the client at. If null, the prefab position is used.
|
||||
/// </summary>
|
||||
public Vector3? Position;
|
||||
/// <summary>
|
||||
/// The rotation to spawn the client with. If null, the prefab position is used.
|
||||
/// </summary>
|
||||
public Quaternion? Rotation;
|
||||
/// <summary>
|
||||
/// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
|
||||
/// </summary>
|
||||
public bool Pending;
|
||||
}
|
||||
|
||||
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) => ConnectionApprovalCallback?.Invoke(payload, clientId, action);
|
||||
/// <summary>
|
||||
/// Connection Approval Request
|
||||
/// </summary>
|
||||
public struct ConnectionApprovalRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The connection data payload
|
||||
/// </summary>
|
||||
public byte[] Payload;
|
||||
/// <summary>
|
||||
/// The Network Id of the client we are about to handle
|
||||
/// </summary>
|
||||
public ulong ClientNetworkId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
|
||||
/// </summary>
|
||||
public Action<ConnectionApprovalRequest, ConnectionApprovalResponse> ConnectionApprovalCallback
|
||||
{
|
||||
get => m_ConnectionApprovalCallback;
|
||||
set
|
||||
{
|
||||
if (value != null && value.GetInvocationList().Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException($"Only one {nameof(ConnectionApprovalCallback)} can be registered at a time.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ConnectionApprovalCallback = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Action<ConnectionApprovalRequest, ConnectionApprovalResponse> m_ConnectionApprovalCallback;
|
||||
|
||||
/// <summary>
|
||||
/// The current NetworkConfig
|
||||
/// </summary>
|
||||
[HideInInspector] public NetworkConfig NetworkConfig;
|
||||
[HideInInspector]
|
||||
public NetworkConfig NetworkConfig;
|
||||
|
||||
/// <summary>
|
||||
/// The current host name we are connected to, used to validate certificate
|
||||
@@ -389,7 +497,7 @@ namespace Unity.Netcode
|
||||
internal static event Action OnSingletonReady;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
internal void OnValidate()
|
||||
{
|
||||
if (NetworkConfig == null)
|
||||
{
|
||||
@@ -422,8 +530,7 @@ namespace Unity.Netcode
|
||||
var networkPrefabGo = networkPrefab?.Prefab;
|
||||
if (networkPrefabGo != null)
|
||||
{
|
||||
var networkObject = networkPrefabGo.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
if (!networkPrefabGo.TryGetComponent<NetworkObject>(out var networkObject))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -541,6 +648,47 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a prefab from the prefab list.
|
||||
/// As with AddNetworkPrefab, this is specific to the client it's called on -
|
||||
/// calling it on the server does not automatically remove anything on any of the
|
||||
/// client processes.
|
||||
///
|
||||
/// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled,
|
||||
/// this cannot be called after connecting.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
public void RemoveNetworkPrefab(GameObject prefab)
|
||||
{
|
||||
if (IsListening && NetworkConfig.ForceSamePrefabs)
|
||||
{
|
||||
throw new Exception($"Prefabs cannot be removed after starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
|
||||
}
|
||||
|
||||
var globalObjectIdHash = prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
for (var i = 0; i < NetworkConfig.NetworkPrefabs.Count; ++i)
|
||||
{
|
||||
if (NetworkConfig.NetworkPrefabs[i].Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash == globalObjectIdHash)
|
||||
{
|
||||
NetworkConfig.NetworkPrefabs.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
{
|
||||
PrefabHandler.RemoveHandler(globalObjectIdHash);
|
||||
}
|
||||
if (NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, out var targetPrefab))
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Remove(globalObjectIdHash);
|
||||
var targetHash = targetPrefab.Prefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
if (NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetHash))
|
||||
{
|
||||
NetworkConfig.OverrideToNetworkPrefab.Remove(targetHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabGlobalObjectIdHash, out uint targetPrefabGlobalObjectIdHash, int index = -1)
|
||||
{
|
||||
sourcePrefabGlobalObjectIdHash = 0;
|
||||
@@ -557,8 +705,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else if (networkPrefab.Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
if (!networkPrefab.Prefab.TryGetComponent(out networkObject))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
@@ -604,8 +751,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
if (!networkPrefab.SourcePrefabToOverride.TryGetComponent(out networkObject))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
@@ -743,6 +889,8 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
IsApproved = false;
|
||||
|
||||
ComponentFactory.SetDefaults();
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
@@ -830,8 +978,7 @@ namespace Unity.Netcode
|
||||
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
|
||||
if (NetworkConfig.PlayerPrefab != null)
|
||||
{
|
||||
var playerPrefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
|
||||
if (playerPrefabNetworkObject != null)
|
||||
if (NetworkConfig.PlayerPrefab.TryGetComponent<NetworkObject>(out var playerPrefabNetworkObject))
|
||||
{
|
||||
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
|
||||
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject
|
||||
@@ -865,11 +1012,13 @@ namespace Unity.Netcode
|
||||
m_ConnectedClientIds.Clear();
|
||||
LocalClient = null;
|
||||
NetworkObject.OrphanChildren.Clear();
|
||||
ClientsToApprove.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a server
|
||||
/// </summary>
|
||||
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in server mode successfully.</returns>
|
||||
public bool StartServer()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
@@ -883,23 +1032,38 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
Initialize(true);
|
||||
IsServer = true;
|
||||
IsClient = false;
|
||||
IsListening = true;
|
||||
|
||||
// If we failed to start then shutdown and notify user that the transport failed to start
|
||||
if (NetworkConfig.NetworkTransport.StartServer())
|
||||
try
|
||||
{
|
||||
IsServer = true;
|
||||
IsClient = false;
|
||||
IsListening = true;
|
||||
// If we failed to start then shutdown and notify user that the transport failed to start
|
||||
if (NetworkConfig.NetworkTransport.StartServer())
|
||||
{
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
OnServerStarted?.Invoke();
|
||||
IsApproved = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsServer = false;
|
||||
IsClient = false;
|
||||
IsListening = false;
|
||||
|
||||
OnServerStarted?.Invoke();
|
||||
return true;
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
Shutdown();
|
||||
IsServer = false;
|
||||
IsClient = false;
|
||||
IsListening = false;
|
||||
throw;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -908,6 +1072,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Starts a client
|
||||
/// </summary>
|
||||
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in client mode successfully.</returns>
|
||||
public bool StartClient()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
@@ -926,6 +1091,7 @@ namespace Unity.Netcode
|
||||
if (!NetworkConfig.NetworkTransport.StartClient())
|
||||
{
|
||||
Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
@@ -940,6 +1106,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Starts a Host
|
||||
/// </summary>
|
||||
/// <returns>(<see cref="true"/>/<see cref="false"/>) returns true if <see cref="NetworkManager"/> started in host mode successfully.</returns>
|
||||
public bool StartHost()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
@@ -954,43 +1121,62 @@ namespace Unity.Netcode
|
||||
|
||||
Initialize(true);
|
||||
|
||||
// If we failed to start then shutdown and notify user that the transport failed to start
|
||||
if (!NetworkConfig.NetworkTransport.StartServer())
|
||||
IsServer = true;
|
||||
IsClient = true;
|
||||
IsListening = true;
|
||||
|
||||
try
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
Shutdown();
|
||||
return false;
|
||||
// If we failed to start then shutdown and notify user that the transport failed to start
|
||||
if (!NetworkConfig.NetworkTransport.StartServer())
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown();
|
||||
|
||||
IsServer = false;
|
||||
IsClient = false;
|
||||
IsListening = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
IsServer = false;
|
||||
IsClient = false;
|
||||
IsListening = false;
|
||||
throw;
|
||||
}
|
||||
|
||||
MessagingSystem.ClientConnected(ServerClientId);
|
||||
LocalClientId = ServerClientId;
|
||||
NetworkMetrics.SetConnectionId(LocalClientId);
|
||||
|
||||
IsServer = true;
|
||||
IsClient = true;
|
||||
IsListening = true;
|
||||
|
||||
if (NetworkConfig.ConnectionApproval)
|
||||
if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null)
|
||||
{
|
||||
InvokeConnectionApproval(NetworkConfig.ConnectionData, ServerClientId,
|
||||
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
|
||||
var response = new ConnectionApprovalResponse();
|
||||
ConnectionApprovalCallback(new ConnectionApprovalRequest { Payload = NetworkConfig.ConnectionData, ClientNetworkId = ServerClientId }, response);
|
||||
if (!response.Approved)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
// You cannot decline the local server. Force approved to true
|
||||
if (!approved)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
"You cannot decline the host connection. The connection was automatically approved.");
|
||||
}
|
||||
}
|
||||
NetworkLog.LogWarning("You cannot decline the host connection. The connection was automatically approved.");
|
||||
}
|
||||
}
|
||||
|
||||
HandleApproval(ServerClientId, createPlayerObject, playerPrefabHash, true, position, rotation);
|
||||
});
|
||||
response.Approved = true;
|
||||
IsApproved = true;
|
||||
HandleConnectionApproval(ServerClientId, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleApproval(ServerClientId, NetworkConfig.PlayerPrefab != null, null, true, null, null);
|
||||
var response = new ConnectionApprovalResponse
|
||||
{
|
||||
Approved = true,
|
||||
CreatePlayerObject = NetworkConfig.PlayerPrefab != null
|
||||
};
|
||||
HandleConnectionApproval(ServerClientId, response);
|
||||
}
|
||||
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
@@ -1048,6 +1234,9 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set this NetworkManager instance as the static NetworkManager singleton
|
||||
/// </summary>
|
||||
public void SetSingleton()
|
||||
{
|
||||
Singleton = this;
|
||||
@@ -1076,7 +1265,6 @@ namespace Unity.Netcode
|
||||
private void Awake()
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||
NetworkVariableHelper.InitializeAllBaseDelegates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1240,6 +1428,20 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
IsConnectedClient = false;
|
||||
IsApproved = false;
|
||||
|
||||
// We need to clean up NetworkObjects before we reset the IsServer
|
||||
// and IsClient properties. This provides consistency of these two
|
||||
// property values for NetworkObjects that are still spawned when
|
||||
// the shutdown cycle begins.
|
||||
if (SpawnManager != null)
|
||||
{
|
||||
SpawnManager.DespawnAndDestroyNetworkObjects();
|
||||
SpawnManager.ServerResetShudownStateForSceneObjects();
|
||||
|
||||
SpawnManager = null;
|
||||
}
|
||||
|
||||
IsServer = false;
|
||||
IsClient = false;
|
||||
|
||||
@@ -1262,14 +1464,6 @@ namespace Unity.Netcode
|
||||
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
|
||||
}
|
||||
|
||||
if (SpawnManager != null)
|
||||
{
|
||||
SpawnManager.DespawnAndDestroyNetworkObjects();
|
||||
SpawnManager.ServerResetShudownStateForSceneObjects();
|
||||
|
||||
SpawnManager = null;
|
||||
}
|
||||
|
||||
if (DeferredMessageManager != null)
|
||||
{
|
||||
DeferredMessageManager.CleanupAllTriggers();
|
||||
@@ -1310,7 +1504,7 @@ namespace Unity.Netcode
|
||||
ClearClients();
|
||||
}
|
||||
|
||||
// INetworkUpdateSystem
|
||||
/// <inheritdoc />
|
||||
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||
{
|
||||
switch (updateStage)
|
||||
@@ -1327,6 +1521,43 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPendingApprovals()
|
||||
{
|
||||
List<ulong> senders = null;
|
||||
|
||||
foreach (var responsePair in ClientsToApprove)
|
||||
{
|
||||
var response = responsePair.Value;
|
||||
var senderId = responsePair.Key;
|
||||
|
||||
if (!response.Pending)
|
||||
{
|
||||
try
|
||||
{
|
||||
HandleConnectionApproval(senderId, response);
|
||||
|
||||
if (senders == null)
|
||||
{
|
||||
senders = new List<ulong>();
|
||||
}
|
||||
senders.Add(senderId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (senders != null)
|
||||
{
|
||||
foreach (var sender in senders)
|
||||
{
|
||||
ClientsToApprove.Remove(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkEarlyUpdate()
|
||||
{
|
||||
if (!IsListening)
|
||||
@@ -1334,6 +1565,8 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessPendingApprovals();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_TransportPoll.Begin();
|
||||
#endif
|
||||
@@ -1367,7 +1600,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// Only update RTT here, server time is updated by time sync messages
|
||||
var reset = NetworkTimeSystem.Advance(Time.deltaTime);
|
||||
var reset = NetworkTimeSystem.Advance(Time.unscaledDeltaTime);
|
||||
if (reset)
|
||||
{
|
||||
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
|
||||
@@ -1376,7 +1609,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (IsServer == false)
|
||||
{
|
||||
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.deltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
|
||||
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.unscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1421,7 +1654,9 @@ namespace Unity.Netcode
|
||||
{
|
||||
var message = new ConnectionRequestMessage
|
||||
{
|
||||
ConfigHash = NetworkConfig.GetConfig(),
|
||||
// Since only a remote client will send a connection request,
|
||||
// we should always force the rebuilding of the NetworkConfig hash value
|
||||
ConfigHash = NetworkConfig.GetConfig(false),
|
||||
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
|
||||
ConnectionData = NetworkConfig.ConnectionData
|
||||
};
|
||||
@@ -1430,23 +1665,73 @@ namespace Unity.Netcode
|
||||
|
||||
private IEnumerator ApprovalTimeout(ulong clientId)
|
||||
{
|
||||
NetworkTime timeStarted = LocalTime;
|
||||
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
|
||||
var timedOut = false;
|
||||
var connectionApproved = false;
|
||||
var connectionNotApproved = false;
|
||||
var timeoutMarker = timeStarted + NetworkConfig.ClientConnectionBufferTimeout;
|
||||
|
||||
//We yield every frame incase a pending client disconnects and someone else gets its connection id
|
||||
while ((LocalTime - timeStarted).Time < NetworkConfig.ClientConnectionBufferTimeout && PendingClients.ContainsKey(clientId))
|
||||
while (IsListening && !ShutdownInProgress && !timedOut && !connectionApproved)
|
||||
{
|
||||
yield return null;
|
||||
// Check if we timed out
|
||||
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
// When the client is no longer in the pending clients list and is in the connected clients list
|
||||
// it has been approved
|
||||
connectionApproved = !PendingClients.ContainsKey(clientId) && ConnectedClients.ContainsKey(clientId);
|
||||
|
||||
// For the server side, if the client is in neither list then it was declined or the client disconnected
|
||||
connectionNotApproved = !PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionApproved = IsApproved;
|
||||
}
|
||||
}
|
||||
|
||||
if (PendingClients.ContainsKey(clientId) && !ConnectedClients.ContainsKey(clientId))
|
||||
// Exit coroutine if we are no longer listening or a shutdown is in progress (client or server)
|
||||
if (!IsListening || ShutdownInProgress)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// If the client timed out or was not approved
|
||||
if (timedOut || connectionNotApproved)
|
||||
{
|
||||
// Timeout
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"Client {clientId} Handshake Timed Out");
|
||||
if (timedOut)
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
// Log a warning that the transport detected a connection but then did not receive a follow up connection request message.
|
||||
// (hacking or something happened to the server's network connection)
|
||||
NetworkLog.LogWarning($"Server detected a transport connection from Client-{clientId}, but timed out waiting for the connection request message.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// We only provide informational logging for the client side
|
||||
NetworkLog.LogInfo("Timed out waiting for the server to approve the connection request.");
|
||||
}
|
||||
}
|
||||
else if (connectionNotApproved)
|
||||
{
|
||||
NetworkLog.LogInfo($"Client-{clientId} was either denied approval or disconnected while being approved.");
|
||||
}
|
||||
}
|
||||
|
||||
DisconnectClient(clientId);
|
||||
if (IsServer)
|
||||
{
|
||||
DisconnectClient(clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Shutdown(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1554,6 +1839,12 @@ namespace Unity.Netcode
|
||||
s_TransportDisconnect.End();
|
||||
#endif
|
||||
break;
|
||||
|
||||
case NetworkEvent.TransportFailure:
|
||||
Debug.LogError($"Shutting down due to network transport failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
OnTransportFailure?.Invoke();
|
||||
Shutdown(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1717,13 +2008,20 @@ namespace Unity.Netcode
|
||||
var playerObject = networkClient.PlayerObject;
|
||||
if (playerObject != null)
|
||||
{
|
||||
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
|
||||
if (!playerObject.DontDestroyWithOwner)
|
||||
{
|
||||
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
|
||||
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].PlayerObject.GlobalObjectIdHash))
|
||||
{
|
||||
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].PlayerObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(playerObject.gameObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(playerObject.gameObject);
|
||||
playerObject.RemoveOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1818,15 +2116,11 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Server Side: Handles the approval of a client
|
||||
/// </summary>
|
||||
/// <param name="ownerClientId">client being approved</param>
|
||||
/// <param name="createPlayerObject">whether we want to create a player or not</param>
|
||||
/// <param name="playerPrefabHash">the GlobalObjectIdHash value for the Network Prefab to create as the player</param>
|
||||
/// <param name="approved">Is the player approved or not?</param>
|
||||
/// <param name="position">Used when createPlayerObject is true, position of the player when spawned </param>
|
||||
/// <param name="rotation">Used when createPlayerObject is true, rotation of the player when spawned</param>
|
||||
internal void HandleApproval(ulong ownerClientId, bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation)
|
||||
/// <param name="ownerClientId">The Network Id of the client being approved</param>
|
||||
/// <param name="response">The response to allow the player in or not, with its parameters</param>
|
||||
internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalResponse response)
|
||||
{
|
||||
if (approved)
|
||||
if (response.Approved)
|
||||
{
|
||||
// Inform new client it got approved
|
||||
PendingClients.Remove(ownerClientId);
|
||||
@@ -1836,10 +2130,40 @@ namespace Unity.Netcode
|
||||
m_ConnectedClientsList.Add(client);
|
||||
m_ConnectedClientIds.Add(client.ClientId);
|
||||
|
||||
if (createPlayerObject)
|
||||
if (response.CreatePlayerObject)
|
||||
{
|
||||
var networkObject = SpawnManager.CreateLocalNetworkObject(false, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, null, position, rotation);
|
||||
SpawnManager.SpawnNetworkObjectLocally(networkObject, SpawnManager.GetNetworkObjectId(), false, true, ownerClientId, false);
|
||||
var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
|
||||
// Generate a SceneObject for the player object to spawn
|
||||
var sceneObject = new NetworkObject.SceneObject
|
||||
{
|
||||
Header = new NetworkObject.SceneObject.HeaderData
|
||||
{
|
||||
IsPlayerObject = true,
|
||||
OwnerClientId = ownerClientId,
|
||||
IsSceneObject = false,
|
||||
HasTransform = true,
|
||||
Hash = playerPrefabHash,
|
||||
},
|
||||
TargetClientId = ownerClientId,
|
||||
Transform = new NetworkObject.SceneObject.TransformData
|
||||
{
|
||||
Position = response.Position.GetValueOrDefault(),
|
||||
Rotation = response.Rotation.GetValueOrDefault()
|
||||
}
|
||||
};
|
||||
|
||||
// Create the player NetworkObject locally
|
||||
var networkObject = SpawnManager.CreateLocalNetworkObject(sceneObject);
|
||||
|
||||
// Spawn the player NetworkObject locally
|
||||
SpawnManager.SpawnNetworkObjectLocally(
|
||||
networkObject,
|
||||
SpawnManager.GetNetworkObjectId(),
|
||||
sceneObject: false,
|
||||
playerObject: true,
|
||||
ownerClientId,
|
||||
destroyWithScene: false);
|
||||
|
||||
ConnectedClients[ownerClientId].PlayerObject = networkObject;
|
||||
}
|
||||
@@ -1862,6 +2186,20 @@ namespace Unity.Netcode
|
||||
|
||||
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
||||
|
||||
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
|
||||
{
|
||||
if (MessagingSystem.MessageTypes[index] != null)
|
||||
{
|
||||
var orderingMessage = new OrderingMessage
|
||||
{
|
||||
Order = index,
|
||||
Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName)
|
||||
};
|
||||
|
||||
SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
|
||||
if (!NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
@@ -1879,13 +2217,13 @@ namespace Unity.Netcode
|
||||
InvokeOnClientConnectedCallback(ownerClientId);
|
||||
}
|
||||
|
||||
if (!createPlayerObject || (playerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
||||
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Separating this into a contained function call for potential further future separation of when this notification is sent.
|
||||
ApprovedPlayerSpawn(ownerClientId, playerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
|
||||
ApprovedPlayerSpawn(ownerClientId, response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -2,13 +2,14 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A component used to identify that a GameObject in the network
|
||||
/// </summary>
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkObject), -99)]
|
||||
[AddComponentMenu("Netcode/Network Object", -99)]
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkObject : MonoBehaviour
|
||||
{
|
||||
@@ -75,7 +76,7 @@ namespace Unity.Netcode
|
||||
public bool IsPlayerObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// Gets if the object is the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId;
|
||||
|
||||
@@ -128,7 +129,7 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to destroy this object if it's owner is destroyed.
|
||||
/// If false, the objects ownership will be given to the server.
|
||||
/// If true, the objects ownership will be given to the server.
|
||||
/// </summary>
|
||||
public bool DontDestroyWithOwner;
|
||||
|
||||
@@ -151,6 +152,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
private readonly HashSet<ulong> m_EmptyULongHashSet = new HashSet<ulong>();
|
||||
/// <summary>
|
||||
/// Returns Observers enumerator
|
||||
/// </summary>
|
||||
@@ -159,7 +161,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
return m_EmptyULongHashSet.GetEnumerator();
|
||||
}
|
||||
|
||||
return Observers.GetEnumerator();
|
||||
@@ -174,21 +176,78 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
return false;
|
||||
}
|
||||
return Observers.Contains(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the event the scene of origin gets unloaded, we keep
|
||||
/// the most important part to uniquely identify in-scene
|
||||
/// placed NetworkObjects
|
||||
/// </summary>
|
||||
internal int SceneOriginHandle = 0;
|
||||
|
||||
private Scene m_SceneOrigin;
|
||||
/// <summary>
|
||||
/// The scene where the NetworkObject was first instantiated
|
||||
/// Note: Primarily for in-scene placed NetworkObjects
|
||||
/// We need to keep track of the original scene of origin for
|
||||
/// the NetworkObject in order to be able to uniquely identify it
|
||||
/// using the scene of origin's handle.
|
||||
/// </summary>
|
||||
internal Scene SceneOrigin
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_SceneOrigin;
|
||||
}
|
||||
|
||||
return Observers.Contains(clientId);
|
||||
set
|
||||
{
|
||||
// The scene origin should only be set once.
|
||||
// Once set, it should never change.
|
||||
if (SceneOriginHandle == 0 && value.IsValid() && value.isLoaded)
|
||||
{
|
||||
m_SceneOrigin = value;
|
||||
SceneOriginHandle = value.handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to return the correct scene handle
|
||||
/// Note: Do not use this within NetworkSpawnManager.SpawnNetworkObjectLocallyCommon
|
||||
/// </summary>
|
||||
internal int GetSceneOriginHandle()
|
||||
{
|
||||
if (SceneOriginHandle == 0 && IsSpawned && IsSceneObject != false)
|
||||
{
|
||||
throw new Exception($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
|
||||
}
|
||||
return SceneOriginHandle != 0 ? SceneOriginHandle : gameObject.scene.handle;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SetCachedParent(transform.parent);
|
||||
SceneOrigin = gameObject.scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a previously hidden <see cref="NetworkObject"/> to a client
|
||||
/// Makes the previously hidden <see cref="NetworkObject"/> "netcode visible" to the targeted client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to show the <see cref="NetworkObject"/> to</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to start sending updates for a previously hidden <see cref="NetworkObject"/> to the targeted client.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client side.<br />
|
||||
/// In-Scene Placed: The instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkShow(ulong)"/><br />
|
||||
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public void NetworkShow(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
@@ -211,11 +270,22 @@ namespace Unity.Netcode
|
||||
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Shows a list of previously hidden <see cref="NetworkObject"/>s to a client
|
||||
/// Makes a list of previously hidden <see cref="NetworkObject"/>s "netcode visible" for the client specified.
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The <see cref="NetworkObject"/>s to show</param>
|
||||
/// <param name="clientId">The client to show the objects to</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to start sending updates for previously hidden <see cref="NetworkObject"/>s to the targeted client.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client's side.<br />
|
||||
/// In-Scene Placed: Already instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client's side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkShow(ulong)"/><br />
|
||||
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="networkObjects">The objects to become "netcode visible" to the targeted client</param>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId)
|
||||
{
|
||||
if (networkObjects == null || networkObjects.Count == 0)
|
||||
@@ -256,9 +326,19 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a object from a specific client
|
||||
/// Hides the <see cref="NetworkObject"/> from the targeted client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to hide the object for</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
|
||||
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
|
||||
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public void NetworkHide(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
@@ -285,7 +365,8 @@ namespace Unity.Netcode
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
DestroyGameObject = !IsSceneObject.Value
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
@@ -293,10 +374,20 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a list of objects from a client
|
||||
/// Hides a list of <see cref="NetworkObject"/>s from the targeted client.
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The objects to hide</param>
|
||||
/// <param name="clientId">The client to hide the objects from</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for the currently visible <see cref="NetworkObject"/>s.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
|
||||
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkHide(ulong)"/><br />
|
||||
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="networkObjects">The <see cref="NetworkObject"/>s that will become "netcode invisible" to the targeted client</param>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
|
||||
{
|
||||
if (networkObjects == null || networkObjects.Count == 0)
|
||||
@@ -344,7 +435,7 @@ namespace Unity.Netcode
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
|
||||
(IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
|
||||
(IsSceneObject == null || (IsSceneObject.Value != true)))
|
||||
{
|
||||
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
|
||||
}
|
||||
@@ -372,7 +463,7 @@ namespace Unity.Netcode
|
||||
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
||||
}
|
||||
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene);
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene);
|
||||
|
||||
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
@@ -405,8 +496,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Spawns a <see cref="NetworkObject"/> across the network and makes it the player object for the given client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId whos player object this is</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
/// <param name="clientId">The clientId who's player object this is</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
|
||||
public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(destroyWithScene, clientId, true);
|
||||
@@ -418,6 +509,7 @@ namespace Unity.Netcode
|
||||
/// <param name="destroy">(true) the <see cref="GameObject"/> will be destroyed (false) the <see cref="GameObject"/> will persist after being despawned</param>
|
||||
public void Despawn(bool destroy = true)
|
||||
{
|
||||
MarkVariablesDirty(false);
|
||||
NetworkManager.SpawnManager.DespawnObject(this, destroy);
|
||||
}
|
||||
|
||||
@@ -462,7 +554,14 @@ namespace Unity.Netcode
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnGainedOwnership();
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnGainedOwnership();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,33 +573,84 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private bool m_IsReparented; // Did initial parent (came from the scene hierarchy) change at runtime?
|
||||
private ulong? m_LatestParent; // What is our last set parent NetworkObject's ID?
|
||||
private Transform m_CachedParent; // What is our last set parent Transform reference?
|
||||
private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent
|
||||
|
||||
internal void SetCachedParent(Transform parentTransform)
|
||||
{
|
||||
m_CachedParent = parentTransform;
|
||||
}
|
||||
|
||||
internal (bool IsReparented, ulong? LatestParent) GetNetworkParenting() => (m_IsReparented, m_LatestParent);
|
||||
internal ulong? GetNetworkParenting() => m_LatestParent;
|
||||
|
||||
internal void SetNetworkParenting(bool isReparented, ulong? latestParent)
|
||||
internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays)
|
||||
{
|
||||
m_IsReparented = isReparented;
|
||||
m_LatestParent = latestParent;
|
||||
m_CachedWorldPositionStays = worldPositionStays;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent of the NetworkObject transform.
|
||||
/// </summary>
|
||||
/// <param name="parent">The new parent for this NetworkObject transform will be the child of.</param>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(Transform parent, bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
|
||||
var networkObject = parent.GetComponent<NetworkObject>();
|
||||
|
||||
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
|
||||
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent of the NetworkObject transform.
|
||||
/// </summary>
|
||||
/// <param name="parent">The new parent for this NetworkObject transform will be the child of.</param>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(GameObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
|
||||
// If we are removing ourself from a parent
|
||||
if (parent == null)
|
||||
{
|
||||
return TrySetParent((NetworkObject)null, worldPositionStays);
|
||||
}
|
||||
|
||||
var networkObject = parent.GetComponent<NetworkObject>();
|
||||
|
||||
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
|
||||
return networkObject == null ? false : TrySetParent(networkObject, worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used when despawning the parent, we want to preserve the cached WorldPositionStays value
|
||||
/// </summary>
|
||||
internal bool TryRemoveParentCachedWorldPositionStays()
|
||||
{
|
||||
return TrySetParent((NetworkObject)null, m_CachedWorldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the parent of the NetworkObject's transform
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a more convenient way to remove the parent without having to cast the null value to either <see cref="GameObject"/> or <see cref="NetworkObject"/>
|
||||
/// </remarks>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryRemoveParent(bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent((NetworkObject)null, worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent of the NetworkObject transform.
|
||||
/// </summary>
|
||||
/// <param name="parent">The new parent for this NetworkObject transform will be the child of.</param>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
if (!AutoObjectParentSync)
|
||||
@@ -523,17 +673,21 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent != null && !parent.IsSpawned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_CachedWorldPositionStays = worldPositionStays;
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
return false;
|
||||
transform.SetParent(null, worldPositionStays);
|
||||
}
|
||||
|
||||
if (!parent.IsSpawned)
|
||||
else
|
||||
{
|
||||
return false;
|
||||
transform.SetParent(parent.transform, worldPositionStays);
|
||||
}
|
||||
|
||||
transform.SetParent(parent.transform, worldPositionStays);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -569,12 +723,11 @@ namespace Unity.Netcode
|
||||
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
|
||||
return;
|
||||
}
|
||||
|
||||
var removeParent = false;
|
||||
var parentTransform = transform.parent;
|
||||
if (parentTransform != null)
|
||||
{
|
||||
var parentObject = transform.parent.GetComponent<NetworkObject>();
|
||||
if (parentObject == null)
|
||||
if (!transform.parent.TryGetComponent<NetworkObject>(out var parentObject))
|
||||
{
|
||||
transform.parent = m_CachedParent;
|
||||
Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent"));
|
||||
@@ -593,19 +746,31 @@ namespace Unity.Netcode
|
||||
else
|
||||
{
|
||||
m_LatestParent = null;
|
||||
removeParent = m_CachedParent != null;
|
||||
}
|
||||
|
||||
m_IsReparented = true;
|
||||
ApplyNetworkParenting();
|
||||
ApplyNetworkParenting(removeParent);
|
||||
|
||||
var message = new ParentSyncMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
IsReparented = m_IsReparented,
|
||||
IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue,
|
||||
LatestParent = m_LatestParent
|
||||
LatestParent = m_LatestParent,
|
||||
RemoveParent = removeParent,
|
||||
WorldPositionStays = m_CachedWorldPositionStays,
|
||||
Position = m_CachedWorldPositionStays ? transform.position : transform.localPosition,
|
||||
Rotation = m_CachedWorldPositionStays ? transform.rotation : transform.localRotation,
|
||||
Scale = transform.localScale,
|
||||
};
|
||||
|
||||
// We need to preserve the m_CachedWorldPositionStays value until after we create the message
|
||||
// in order to assure any local space values changed/reset get applied properly. If our
|
||||
// parent is null then go ahead and reset the m_CachedWorldPositionStays the default value.
|
||||
if (parentTransform == null)
|
||||
{
|
||||
m_CachedWorldPositionStays = true;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
var maxCount = NetworkManager.ConnectedClientsIds.Count;
|
||||
@@ -629,46 +794,94 @@ namespace Unity.Netcode
|
||||
// For instance, if we're spawning NetworkObject 5 and its parent is 10, what should happen if we do not have 10 yet?
|
||||
// let's say 10 is on the way to be replicated in a few frames and we could fix that parent-child relationship later.
|
||||
//
|
||||
// If you couldn't find your parent, we put you into OrphanChildren set and everytime we spawn another NetworkObject locally due to replication,
|
||||
// If you couldn't find your parent, we put you into OrphanChildren set and every time we spawn another NetworkObject locally due to replication,
|
||||
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
|
||||
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
|
||||
|
||||
internal bool ApplyNetworkParenting()
|
||||
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false)
|
||||
{
|
||||
if (!AutoObjectParentSync)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsSpawned)
|
||||
// SPECIAL CASE:
|
||||
// The ignoreNotSpawned is a special case scenario where a late joining client has joined
|
||||
// and loaded one or more scenes that contain nested in-scene placed NetworkObject children
|
||||
// yet the server's synchronization information does not indicate the NetworkObject in question
|
||||
// has a parent. Under this scenario, we want to remove the parent before spawning and setting
|
||||
// the transform values. This is the only scenario where the ignoreNotSpawned parameter is used.
|
||||
if (!IsSpawned && !ignoreNotSpawned)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_IsReparented)
|
||||
// Handle the first in-scene placed NetworkObject parenting scenarios. Once the m_LatestParent
|
||||
// has been set, this will not be entered into again (i.e. the later code will be invoked and
|
||||
// users will get notifications when the parent changes).
|
||||
var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value;
|
||||
if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced)
|
||||
{
|
||||
return true;
|
||||
var parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
|
||||
|
||||
// If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component
|
||||
// attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting.
|
||||
// Note: We only start tracking parenting if the user removes the child from the standard GameObject
|
||||
// parent and then re-parents the child under a GameObject with a NetworkObject component attached.
|
||||
if (parentNetworkObject == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else // If the parent still isn't spawned add this to the orphaned children and return false
|
||||
if (!parentNetworkObject.IsSpawned)
|
||||
{
|
||||
OrphanChildren.Add(this);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we made it this far, go ahead and set the network parenting values
|
||||
// with the default WorldPoisitonSays value
|
||||
SetNetworkParenting(parentNetworkObject.NetworkObjectId, true);
|
||||
|
||||
// Set the cached parent
|
||||
m_CachedParent = parentNetworkObject.transform;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_LatestParent == null || !m_LatestParent.HasValue)
|
||||
// If we are removing the parent or our latest parent is not set, then remove the parent
|
||||
// removeParent is only set when:
|
||||
// - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed
|
||||
// - The client-side when handling a ParentSyncMessage
|
||||
// When clients are synchronizing only the m_LatestParent.HasValue will not have a value if there is no parent
|
||||
// or a parent was removed prior to the client connecting (i.e. in-scene placed NetworkObjects)
|
||||
if (removeParent || !m_LatestParent.HasValue)
|
||||
{
|
||||
m_CachedParent = null;
|
||||
transform.parent = null;
|
||||
|
||||
// We must use Transform.SetParent when taking WorldPositionStays into
|
||||
// consideration, otherwise just setting transform.parent = null defaults
|
||||
// to WorldPositionStays which can cause scaling issues if the parent's
|
||||
// scale is not the default (Vetctor3.one) value.
|
||||
transform.SetParent(null, m_CachedWorldPositionStays);
|
||||
InvokeBehaviourOnNetworkObjectParentChanged(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
|
||||
// If we have a latest parent id but it hasn't been spawned yet, then add this instance to the orphanChildren
|
||||
// HashSet and return false (i.e. parenting not applied yet)
|
||||
if (m_LatestParent.HasValue && !NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
|
||||
{
|
||||
OrphanChildren.Add(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we made it here, then parent this instance under the parentObject
|
||||
var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value];
|
||||
|
||||
m_CachedParent = parentObject.transform;
|
||||
transform.parent = parentObject.transform;
|
||||
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
|
||||
|
||||
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
|
||||
return true;
|
||||
@@ -696,7 +909,21 @@ namespace Unity.Netcode
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].VisibleOnNetworkSpawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,12 +972,12 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void MarkVariablesDirty()
|
||||
internal void MarkVariablesDirty(bool dirty)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var behavior = ChildNetworkBehaviours[i];
|
||||
behavior.MarkVariablesDirty();
|
||||
behavior.MarkVariablesDirty(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,8 +990,8 @@ namespace Unity.Netcode
|
||||
// if and when we have different systems for where it is expected that orphans survive across ticks,
|
||||
// then this warning will remind us that we need to revamp the system because then we can no longer simply
|
||||
// spawn the orphan without its parent (at least, not when its transform is set to local coords mode)
|
||||
// - because then you’ll have children popping at the wrong location not having their parent’s global position to root them
|
||||
// - and then they’ll pop to the correct location after they get the parent, and that would be not good
|
||||
// - because then you'll have children popping at the wrong location not having their parent's global position to root them
|
||||
// - and then they'll pop to the correct location after they get the parent, and that would be not good
|
||||
internal static void VerifyParentingStatus()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
@@ -839,7 +1066,6 @@ namespace Unity.Netcode
|
||||
public bool HasParent;
|
||||
public bool IsSceneObject;
|
||||
public bool HasTransform;
|
||||
public bool IsReparented;
|
||||
}
|
||||
|
||||
public HeaderData Header;
|
||||
@@ -852,6 +1078,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
}
|
||||
|
||||
public TransformData Transform;
|
||||
@@ -865,16 +1092,24 @@ namespace Unity.Netcode
|
||||
public NetworkObject OwnerObject;
|
||||
public ulong TargetClientId;
|
||||
|
||||
public int NetworkSceneHandle;
|
||||
|
||||
public bool WorldPositionStays;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(
|
||||
sizeof(HeaderData) +
|
||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
||||
(Header.IsReparented
|
||||
? FastBufferWriter.GetWriteSize(IsLatestParentSet) +
|
||||
(IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0)
|
||||
: 0)))
|
||||
var writeSize = sizeof(HeaderData);
|
||||
if (Header.HasParent)
|
||||
{
|
||||
writeSize += FastBufferWriter.GetWriteSize(ParentObjectId);
|
||||
writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
|
||||
writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
|
||||
writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
|
||||
}
|
||||
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!writer.TryBeginWrite(writeSize))
|
||||
{
|
||||
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
@@ -884,6 +1119,12 @@ namespace Unity.Netcode
|
||||
if (Header.HasParent)
|
||||
{
|
||||
writer.WriteValue(ParentObjectId);
|
||||
writer.WriteValue(WorldPositionStays);
|
||||
writer.WriteValue(IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
writer.WriteValue(LatestParent.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (Header.HasTransform)
|
||||
@@ -891,13 +1132,14 @@ namespace Unity.Netcode
|
||||
writer.WriteValue(Transform);
|
||||
}
|
||||
|
||||
if (Header.IsReparented)
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
|
||||
// sizes for dynamically spawned NetworkObjects.
|
||||
if (Header.IsSceneObject)
|
||||
{
|
||||
writer.WriteValue(IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
writer.WriteValue((ulong)LatestParent);
|
||||
}
|
||||
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||
}
|
||||
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
@@ -910,17 +1152,41 @@ namespace Unity.Netcode
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
reader.ReadValue(out Header);
|
||||
if (!reader.TryBeginRead(
|
||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
||||
(Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) : 0)))
|
||||
var readSize = 0;
|
||||
if (Header.HasParent)
|
||||
{
|
||||
readSize += FastBufferWriter.GetWriteSize(ParentObjectId);
|
||||
readSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
|
||||
readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
|
||||
// We need to read at this point in order to get the IsLatestParentSet value
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
|
||||
// Read the initial parenting related properties
|
||||
reader.ReadValue(out ParentObjectId);
|
||||
reader.ReadValue(out WorldPositionStays);
|
||||
reader.ReadValue(out IsLatestParentSet);
|
||||
|
||||
// Now calculate the remaining bytes to read
|
||||
readSize = 0;
|
||||
readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
|
||||
}
|
||||
|
||||
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
// Try to begin reading the remaining bytes
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
|
||||
if (Header.HasParent)
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValue(out ParentObjectId);
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
|
||||
if (Header.HasTransform)
|
||||
@@ -928,18 +1194,26 @@ namespace Unity.Netcode
|
||||
reader.ReadValue(out Transform);
|
||||
}
|
||||
|
||||
if (Header.IsReparented)
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// Client-side NetworkSceneManagers use this to locate their local instance of the
|
||||
// NetworkObject instance.
|
||||
if (Header.IsSceneObject)
|
||||
{
|
||||
reader.ReadValue(out IsLatestParentSet);
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
reader.ReadValueSafe(out NetworkSceneHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void PostNetworkVariableWrite()
|
||||
{
|
||||
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
|
||||
internal SceneObject GetMessageSceneObject(ulong targetClientId)
|
||||
{
|
||||
var obj = new SceneObject
|
||||
@@ -963,25 +1237,12 @@ namespace Unity.Netcode
|
||||
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (parentNetworkObject)
|
||||
if (parentNetworkObject != null)
|
||||
{
|
||||
obj.Header.HasParent = true;
|
||||
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
|
||||
}
|
||||
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
|
||||
{
|
||||
obj.Header.HasTransform = true;
|
||||
obj.Transform = new SceneObject.TransformData
|
||||
{
|
||||
Position = transform.position,
|
||||
Rotation = transform.rotation
|
||||
};
|
||||
}
|
||||
|
||||
var (isReparented, latestParent) = GetNetworkParenting();
|
||||
obj.Header.IsReparented = isReparented;
|
||||
if (isReparented)
|
||||
{
|
||||
obj.WorldPositionStays = m_CachedWorldPositionStays;
|
||||
var latestParent = GetNetworkParenting();
|
||||
var isLatestParentSet = latestParent != null && latestParent.HasValue;
|
||||
obj.IsLatestParentSet = isLatestParentSet;
|
||||
if (isLatestParentSet)
|
||||
@@ -990,6 +1251,25 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
|
||||
{
|
||||
obj.Header.HasTransform = true;
|
||||
obj.Transform = new SceneObject.TransformData
|
||||
{
|
||||
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
|
||||
// values as opposed world space values.
|
||||
Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position,
|
||||
Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation,
|
||||
|
||||
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can
|
||||
// impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale
|
||||
// which can be thought of as "world space scale".
|
||||
// More information:
|
||||
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
|
||||
Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale,
|
||||
};
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1003,27 +1283,8 @@ namespace Unity.Netcode
|
||||
/// <returns>optional to use NetworkObject deserialized</returns>
|
||||
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager)
|
||||
{
|
||||
Vector3? position = null;
|
||||
Quaternion? rotation = null;
|
||||
ulong? parentNetworkId = null;
|
||||
|
||||
if (sceneObject.Header.HasTransform)
|
||||
{
|
||||
position = sceneObject.Transform.Position;
|
||||
rotation = sceneObject.Transform.Rotation;
|
||||
}
|
||||
|
||||
if (sceneObject.Header.HasParent)
|
||||
{
|
||||
parentNetworkId = sceneObject.ParentObjectId;
|
||||
}
|
||||
|
||||
//Attempt to create a local NetworkObject
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(
|
||||
sceneObject.Header.IsSceneObject, sceneObject.Header.Hash,
|
||||
sceneObject.Header.OwnerClientId, parentNetworkId, position, rotation, sceneObject.Header.IsReparented);
|
||||
|
||||
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -1038,7 +1299,7 @@ namespace Unity.Netcode
|
||||
return null;
|
||||
}
|
||||
|
||||
// Spawn the NetworkObject(
|
||||
// Spawn the NetworkObject
|
||||
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false);
|
||||
|
||||
return networkObject;
|
||||
@@ -1067,5 +1328,21 @@ namespace Unity.Netcode
|
||||
|
||||
return GlobalObjectIdHash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkBehaviour from the ChildNetworkBehaviours list when destroyed
|
||||
/// while the NetworkObject is still spawned.
|
||||
/// </summary>
|
||||
internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour)
|
||||
{
|
||||
if (networkBehaviour.IsSpawned && IsSpawned)
|
||||
{
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)");
|
||||
}
|
||||
ChildNetworkBehaviours.Remove(networkBehaviour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,8 +14,23 @@ namespace Unity.Netcode
|
||||
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
|
||||
|
||||
// internal logging
|
||||
|
||||
/// <summary>
|
||||
/// Locally logs a info log with Netcode prefixing.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Locally logs a warning log with Netcode prefixing.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Locally logs a error log with Netcode prefixing.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -79,6 +79,7 @@ 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)
|
||||
|
||||
@@ -101,16 +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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
ObjectInfo.Deserialize(reader);
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo.Header.IsSceneObject, ObjectInfo.Header.Hash))
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Unity.Netcode
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: c2e5a740c1abd4315801e3f26ecf8adb
|
||||
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,18 +14,36 @@ 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 bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
@@ -34,24 +54,30 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out NetworkObjectId);
|
||||
reader.ReadValueSafe(out IsReparented);
|
||||
if (IsReparented)
|
||||
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId);
|
||||
reader.ReadValueSafe(out RemoveParent);
|
||||
reader.ReadValueSafe(out WorldPositionStays);
|
||||
if (!RemoveParent)
|
||||
{
|
||||
reader.ReadValueSafe(out IsLatestParentSet);
|
||||
|
||||
if (IsLatestParentSet)
|
||||
{
|
||||
reader.ReadValueSafe(out ulong latestParent);
|
||||
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
}
|
||||
|
||||
// Whether parenting or removing a parent, we always update the position, rotation, and scale
|
||||
reader.ReadValueSafe(out Position);
|
||||
reader.ReadValueSafe(out Rotation);
|
||||
reader.ReadValueSafe(out Scale);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -59,8 +85,22 @@ namespace Unity.Netcode
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
networkObject.SetNetworkParenting(IsReparented, LatestParent);
|
||||
networkObject.ApplyNetworkParenting();
|
||||
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
|
||||
networkObject.ApplyNetworkParenting(RemoveParent);
|
||||
|
||||
// We set all of the transform values after parenting as they are
|
||||
// the values of the server-side post-parenting transform values
|
||||
if (!WorldPositionStays)
|
||||
{
|
||||
networkObject.transform.localPosition = Position;
|
||||
networkObject.transform.localRotation = Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject.transform.position = Position;
|
||||
networkObject.transform.rotation = Rotation;
|
||||
}
|
||||
networkObject.transform.localScale = Scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class HandlerNotRegisteredException : SystemException
|
||||
{
|
||||
public HandlerNotRegisteredException() { }
|
||||
public HandlerNotRegisteredException(string issue) : base(issue) { }
|
||||
}
|
||||
|
||||
internal class InvalidMessageStructureException : SystemException
|
||||
{
|
||||
@@ -44,8 +49,9 @@ namespace Unity.Netcode
|
||||
|
||||
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
|
||||
|
||||
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
|
||||
private Type[] m_ReverseTypeMap = new Type[255];
|
||||
// These array will grow as we need more message handlers. 4 is just a starting size.
|
||||
private MessageHandler[] m_MessageHandlers = new MessageHandler[4];
|
||||
private Type[] m_ReverseTypeMap = new Type[4];
|
||||
|
||||
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
|
||||
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
|
||||
@@ -59,6 +65,7 @@ namespace Unity.Netcode
|
||||
|
||||
internal Type[] MessageTypes => m_ReverseTypeMap;
|
||||
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
|
||||
|
||||
internal uint MessageHandlerCount => m_HighMessageType;
|
||||
|
||||
internal uint GetMessageType(Type t)
|
||||
@@ -75,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
|
||||
@@ -89,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);
|
||||
@@ -143,6 +180,13 @@ namespace Unity.Netcode
|
||||
|
||||
private void RegisterMessageType(MessageWithHandler messageWithHandler)
|
||||
{
|
||||
// if we are out of space, perform amortized linear growth
|
||||
if (m_HighMessageType == m_MessageHandlers.Length)
|
||||
{
|
||||
Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length);
|
||||
Array.Resize(ref m_ReverseTypeMap, 2 * m_ReverseTypeMap.Length);
|
||||
}
|
||||
|
||||
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
|
||||
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
|
||||
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
|
||||
@@ -226,6 +270,70 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
|
||||
// This allows the server to tell the client which id it is using for which message and make sure the right
|
||||
// message is used when deserializing.
|
||||
internal void ReorderMessage(int desiredOrder, uint targetHash)
|
||||
{
|
||||
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)
|
||||
@@ -259,18 +367,29 @@ namespace Unity.Netcode
|
||||
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, ref context, this);
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,14 @@ using Unity.Multiplayer.Tools;
|
||||
using Unity.Multiplayer.Tools.MetricTypes;
|
||||
using Unity.Multiplayer.Tools.NetStats;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class NetworkMetrics : INetworkMetrics
|
||||
{
|
||||
const ulong k_MaxMetricsPerFrame = 1000L;
|
||||
|
||||
static Dictionary<uint, string> s_SceneEventTypeNames;
|
||||
|
||||
static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
|
||||
private const ulong k_MaxMetricsPerFrame = 1000L;
|
||||
private static Dictionary<uint, string> s_SceneEventTypeNames;
|
||||
private static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
|
||||
|
||||
static NetworkMetrics()
|
||||
{
|
||||
@@ -531,7 +528,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetcodeObserver
|
||||
internal class NetcodeObserver
|
||||
{
|
||||
public static IMetricObserver Observer { get; } = MetricObserverFactory.Construct();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
class NetworkObjectProvider : INetworkObjectProvider
|
||||
internal class NetworkObjectProvider : INetworkObjectProvider
|
||||
{
|
||||
private readonly NetworkManager m_NetworkManager;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Unity.Netcode
|
||||
|
||||
public Object GetNetworkObject(ulong networkObjectId)
|
||||
{
|
||||
if(m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
|
||||
if (m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out NetworkObject value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -8,10 +9,9 @@ namespace Unity.Netcode
|
||||
/// Event based NetworkVariable container for syncing Lists
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the list</typeparam>
|
||||
public class NetworkList<T> : NetworkVariableSerialization<T> where T : unmanaged, IEquatable<T>
|
||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
|
||||
|
||||
/// <summary>
|
||||
@@ -25,16 +25,27 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public event OnListChangedDelegate OnListChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor method for <see cref="NetworkList"/>
|
||||
/// </summary>
|
||||
public NetworkList() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="readPerm"></param>
|
||||
/// <param name="writePerm"></param>
|
||||
public NetworkList(IEnumerable<T> values = default,
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
foreach (var value in values)
|
||||
// allow null IEnumerable<T> to mean "no values"
|
||||
if (values != null)
|
||||
{
|
||||
m_List.Add(value);
|
||||
foreach (var value in values)
|
||||
{
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +56,6 @@ namespace Unity.Netcode
|
||||
if (m_DirtyEvents.Length > 0)
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
m_ListAtLastReset.CopyFrom(m_List);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,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)
|
||||
{
|
||||
@@ -72,34 +94,35 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
|
||||
for (int i = 0; i < m_DirtyEvents.Length; i++)
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Type);
|
||||
switch (m_DirtyEvents[i].Type)
|
||||
var element = m_DirtyEvents.ElementAt(i);
|
||||
writer.WriteValueSafe(element.Type);
|
||||
switch (element.Type)
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
@@ -114,10 +137,10 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
|
||||
for (int i = 0; i < m_ListAtLastReset.Length; i++)
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
{
|
||||
Write(writer, m_ListAtLastReset[i]);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +151,8 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out ushort count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -144,7 +168,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
m_List.Add(value);
|
||||
|
||||
if (OnListChanged != null)
|
||||
@@ -165,15 +190,25 @@ namespace Unity.Netcode
|
||||
Index = m_List.Length - 1,
|
||||
Value = m_List[m_List.Length - 1]
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
Read(reader, out T value);
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = value;
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
|
||||
if (index < m_List.Length)
|
||||
{
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_List.Add(value);
|
||||
}
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
@@ -193,12 +228,14 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = m_List[index]
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
int index = m_List.IndexOf(value);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -225,6 +262,7 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -252,13 +290,15 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
Read(reader, out T value);
|
||||
var value = new T();
|
||||
NetworkVariableSerialization<T>.Read(reader, ref value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
@@ -287,6 +327,7 @@ namespace Unity.Netcode
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -309,6 +350,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -331,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>()
|
||||
@@ -346,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>()
|
||||
@@ -366,6 +420,12 @@ namespace Unity.Netcode
|
||||
/// <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)
|
||||
{
|
||||
@@ -395,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>()
|
||||
{
|
||||
@@ -411,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>()
|
||||
@@ -428,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);
|
||||
@@ -444,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
|
||||
@@ -456,10 +547,14 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden <see cref="IDisposable"/> implementation.
|
||||
/// CAUTION: If you derive from this class and override the <see cref="Dispose"/> method,
|
||||
/// you **must** always invoke the base.Dispose() method!
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
m_List.Dispose();
|
||||
m_ListAtLastReset.Dispose();
|
||||
m_DirtyEvents.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A variable that can be synchronized over the network.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : NetworkVariableSerialization<T> where T : unmanaged
|
||||
public class NetworkVariable<T> : NetworkVariableBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
@@ -22,7 +21,12 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public OnValueChangedDelegate OnValueChanged;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for <see cref="NetworkVariable{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="value">initial value set that is of type T</param>
|
||||
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> for this <see cref="NetworkVariable{T}"/></param>
|
||||
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> for this <see cref="NetworkVariable{T}"/></param>
|
||||
public NetworkVariable(T value = default,
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
@@ -31,6 +35,9 @@ namespace Unity.Netcode
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The internal value of the NetworkVariable
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private protected T m_InternalValue;
|
||||
|
||||
@@ -43,7 +50,7 @@ namespace Unity.Netcode
|
||||
set
|
||||
{
|
||||
// Compare bitwise
|
||||
if (ValueEquals(ref m_InternalValue, ref value))
|
||||
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -57,24 +64,14 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Compares two values of the same unmanaged type by underlying memory
|
||||
// Ignoring any overriden value checks
|
||||
// Size is fixed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe bool ValueEquals(ref T a, ref T b)
|
||||
{
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
|
||||
/// if there are subscribers to that event.
|
||||
/// </summary>
|
||||
/// <param name="value">the new value of type `T` to be set/></param>
|
||||
private protected void Set(T value)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
SetDirty(true);
|
||||
T previousValue = m_InternalValue;
|
||||
m_InternalValue = value;
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
@@ -102,11 +99,11 @@ namespace Unity.Netcode
|
||||
// would be stored in different fields
|
||||
|
||||
T previousValue = m_InternalValue;
|
||||
Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
SetDirty(true);
|
||||
}
|
||||
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
@@ -115,13 +112,13 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void ReadField(FastBufferReader reader)
|
||||
{
|
||||
Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
Write(writer, m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -12,16 +13,36 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default read permissions
|
||||
/// </summary>
|
||||
public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone;
|
||||
|
||||
/// <summary>
|
||||
/// The default write permissions
|
||||
/// </summary>
|
||||
public const NetworkVariableWritePermission DefaultWritePerm = NetworkVariableWritePermission.Server;
|
||||
|
||||
/// <summary>
|
||||
/// The default constructor for <see cref="NetworkVariableBase"/> that can be used to create a
|
||||
/// custom NetworkVariable.
|
||||
/// </summary>
|
||||
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> access settings</param>
|
||||
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> access settings</param>
|
||||
protected NetworkVariableBase(
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
@@ -30,7 +51,11 @@ namespace Unity.Netcode
|
||||
WritePerm = writePerm;
|
||||
}
|
||||
|
||||
private protected bool m_IsDirty;
|
||||
/// <summary>
|
||||
/// The <see cref="m_IsDirty"/> property is used to determine if the
|
||||
/// value of the `NetworkVariable` has changed.
|
||||
/// </summary>
|
||||
private bool m_IsDirty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the network variable's instance
|
||||
@@ -43,14 +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>
|
||||
@@ -70,6 +110,11 @@ namespace Unity.Netcode
|
||||
return m_IsDirty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if a specific client has permission to read the var or not
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client id</param>
|
||||
/// <returns>Whether or not the client has permission to read</returns>
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (ReadPerm)
|
||||
@@ -78,10 +123,15 @@ namespace Unity.Netcode
|
||||
case NetworkVariableReadPermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariableReadPermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if a specific client has permission to write the var or not
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client id</param>
|
||||
/// <returns>Whether or not the client has permission to write</returns>
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (WritePerm)
|
||||
@@ -127,6 +177,9 @@ namespace Unity.Netcode
|
||||
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
||||
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
|
||||
|
||||
/// <summary>
|
||||
/// Virtual <see cref="IDisposable"/> implementation
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public class NetworkVariableHelper
|
||||
{
|
||||
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
|
||||
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
//
|
||||
// RuntimeAccessModifiersILPP will make this `public`
|
||||
internal static void InitializeDelegatesNetworkSerializable<T>() where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteNetworkSerializable);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadNetworkSerializable);
|
||||
}
|
||||
internal static void InitializeDelegatesStruct<T>() where T : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteStruct);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadStruct);
|
||||
}
|
||||
internal static void InitializeDelegatesEnum<T>() where T : unmanaged, Enum
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteEnum);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadEnum);
|
||||
}
|
||||
internal static void InitializeDelegatesPrimitive<T>() where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WritePrimitive);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadPrimitive);
|
||||
}
|
||||
|
||||
internal static void InitializeAllBaseDelegates()
|
||||
{
|
||||
// Built-in C# types, serialized through a generic method
|
||||
InitializeDelegatesPrimitive<bool>();
|
||||
InitializeDelegatesPrimitive<byte>();
|
||||
InitializeDelegatesPrimitive<sbyte>();
|
||||
InitializeDelegatesPrimitive<char>();
|
||||
InitializeDelegatesPrimitive<decimal>();
|
||||
InitializeDelegatesPrimitive<float>();
|
||||
InitializeDelegatesPrimitive<double>();
|
||||
InitializeDelegatesPrimitive<short>();
|
||||
InitializeDelegatesPrimitive<ushort>();
|
||||
InitializeDelegatesPrimitive<int>();
|
||||
InitializeDelegatesPrimitive<uint>();
|
||||
InitializeDelegatesPrimitive<long>();
|
||||
InitializeDelegatesPrimitive<ulong>();
|
||||
|
||||
// Built-in Unity types, serialized with specific overloads because they're structs without ISerializeByMemcpy attached
|
||||
NetworkVariableSerialization<Vector2>.SetWriteDelegate((FastBufferWriter writer, in Vector2 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Vector3>.SetWriteDelegate((FastBufferWriter writer, in Vector3 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Vector4>.SetWriteDelegate((FastBufferWriter writer, in Vector4 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Quaternion>.SetWriteDelegate((FastBufferWriter writer, in Quaternion value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Color>.SetWriteDelegate((FastBufferWriter writer, in Color value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Color32>.SetWriteDelegate((FastBufferWriter writer, in Color32 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Ray>.SetWriteDelegate((FastBufferWriter writer, in Ray value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Ray2D>.SetWriteDelegate((FastBufferWriter writer, in Ray2D value) => { writer.WriteValueSafe(value); });
|
||||
|
||||
NetworkVariableSerialization<Vector2>.SetReadDelegate((FastBufferReader reader, out Vector2 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Vector3>.SetReadDelegate((FastBufferReader reader, out Vector3 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Vector4>.SetReadDelegate((FastBufferReader reader, out Vector4 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Quaternion>.SetReadDelegate((FastBufferReader reader, out Quaternion value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Color>.SetReadDelegate((FastBufferReader reader, out Color value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Color32>.SetReadDelegate((FastBufferReader reader, out Color32 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Ray>.SetReadDelegate((FastBufferReader reader, out Ray value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Ray2D>.SetReadDelegate((FastBufferReader reader, out Ray2D value) => { reader.ReadValueSafe(out value); });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,32 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// The permission types for reading a var
|
||||
/// </summary>
|
||||
public enum NetworkVariableReadPermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Everyone can read
|
||||
/// </summary>
|
||||
Everyone,
|
||||
/// <summary>
|
||||
/// Only the owner and the server can read
|
||||
/// </summary>
|
||||
Owner,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The permission types for writing a var
|
||||
/// </summary>
|
||||
public enum NetworkVariableWritePermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Only the server can write
|
||||
/// </summary>
|
||||
Server,
|
||||
/// <summary>
|
||||
/// Only the owner can write
|
||||
/// </summary>
|
||||
Owner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,169 +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 includes various read/write delegates
|
||||
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
|
||||
/// which is invoked by code generated by ILPP during module load.
|
||||
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
|
||||
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
|
||||
///
|
||||
/// Any type that inherits from `NetworkVariableSerialization<T>` will implicitly result in any `T`
|
||||
/// passed to it being picked up and initialized by ILPP.
|
||||
///
|
||||
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
|
||||
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
|
||||
/// but there's no way to achieve the same thing with a class, this sets up various read/write schemes
|
||||
/// based on which constraints are met by `T` using reflection, which is done at module load time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
|
||||
[Serializable]
|
||||
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
|
||||
public static class NetworkVariableSerialization<T>
|
||||
{
|
||||
// Functions that know how to serialize INetworkSerializable
|
||||
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializable
|
||||
internal static INetworkVariableSerializer<T> Serializer = new FallbackSerializer<T>();
|
||||
|
||||
internal delegate bool EqualsDelegate(ref T a, ref T b);
|
||||
internal static EqualsDelegate AreEqual;
|
||||
|
||||
// Compares two values of the same unmanaged type by underlying memory
|
||||
// Ignoring any overridden value checks
|
||||
// Size is fixed
|
||||
internal static unsafe bool ValueEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged
|
||||
{
|
||||
writer.WriteNetworkSerializable(value);
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(TValueType)) == 0;
|
||||
}
|
||||
|
||||
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializable
|
||||
internal static bool EqualityEqualsObject<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class, IEquatable<TValueType>
|
||||
{
|
||||
reader.ReadNetworkSerializable(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize structs
|
||||
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize enums
|
||||
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, Enum
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, Enum
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize other types
|
||||
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
|
||||
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Should never be reachable at runtime. All calls to this should be replaced with the correct
|
||||
// call above by ILPP.
|
||||
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
{
|
||||
if (value is INetworkSerializable)
|
||||
if (a == null)
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
return b == null;
|
||||
}
|
||||
else if (value is INetworkSerializeByMemcpy)
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (value is Enum)
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
|
||||
|
||||
if (b == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
NetworkVariableSerialization<TForMethod>.Write(writer, value);
|
||||
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
internal static bool EqualityEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : unmanaged, IEquatable<TValueType>
|
||||
{
|
||||
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
|
||||
|
||||
}
|
||||
NetworkVariableSerialization<TForMethod>.Read(reader, out value);
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
|
||||
|
||||
protected internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
|
||||
|
||||
// These static delegates provide the right implementation for writing and reading a particular network variable type.
|
||||
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
|
||||
//
|
||||
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
|
||||
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
|
||||
//
|
||||
// In the future we may be able to use this to provide packing implementations for floats and integers to optimize bandwidth usage.
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
private static WriteDelegate<T> s_Write = WriteValue;
|
||||
private static ReadDelegate<T> s_Read = ReadValue;
|
||||
|
||||
protected static void Write(FastBufferWriter writer, in T value)
|
||||
internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b) where TValueType : class
|
||||
{
|
||||
s_Write(writer, value);
|
||||
return a == b;
|
||||
}
|
||||
|
||||
protected static void Read(FastBufferReader reader, out T value)
|
||||
internal static void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
s_Read(reader, out value);
|
||||
Serializer.Write(writer, ref value);
|
||||
}
|
||||
|
||||
internal static void SetWriteDelegate(WriteDelegate<T> write)
|
||||
{
|
||||
s_Write = write;
|
||||
}
|
||||
|
||||
internal static void SetReadDelegate(ReadDelegate<T> read)
|
||||
{
|
||||
s_Read = read;
|
||||
}
|
||||
|
||||
protected NetworkVariableSerialization(
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
internal static void Read(FastBufferReader reader, ref T value)
|
||||
{
|
||||
Serializer.Read(reader, ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
@@ -10,19 +9,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal interface ISceneManagerHandler
|
||||
{
|
||||
// Generic action to call when a scene is finished loading/unloading
|
||||
struct SceneEventAction
|
||||
{
|
||||
internal uint SceneEventId;
|
||||
internal Action<uint> EventAction;
|
||||
internal void Invoke()
|
||||
{
|
||||
EventAction.Invoke(SceneEventId);
|
||||
}
|
||||
}
|
||||
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
|
||||
|
||||
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction);
|
||||
|
||||
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction);
|
||||
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Unity.Netcode
|
||||
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
|
||||
/// scene event status.<br/>
|
||||
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// See also: <br/>
|
||||
/// <seealso cref="SceneEventType"/>
|
||||
/// </summary>
|
||||
@@ -166,6 +167,7 @@ namespace Unity.Netcode
|
||||
/// <item><term><see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed</term></item>
|
||||
/// <item><term><see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed</term></item>
|
||||
/// </list>
|
||||
/// <em>Note: Do not start new scene events within NetworkSceneManager scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event SceneEventDelegate OnSceneEvent;
|
||||
|
||||
@@ -239,13 +241,15 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnLoadDelegateHandler OnLoad;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnUnloadDelegateHandler OnUnload;
|
||||
|
||||
@@ -254,7 +258,8 @@ namespace Unity.Netcode
|
||||
/// after a client is approved for connection in order to synchronize the client with the currently loaded
|
||||
/// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.
|
||||
/// This event is generated on a per newly connected and approved client basis.</em>
|
||||
/// This event is generated on a per newly connected and approved client basis.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnSynchronizeDelegateHandler OnSynchronize;
|
||||
|
||||
@@ -263,7 +268,8 @@ namespace Unity.Netcode
|
||||
/// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains
|
||||
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
|
||||
/// finished the <see cref="SceneEventType.Load"/> event.<br/>
|
||||
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em>
|
||||
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
|
||||
|
||||
@@ -273,21 +279,24 @@ namespace Unity.Netcode
|
||||
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
|
||||
/// finished the <see cref="SceneEventType.Unload"/> event.<br/>
|
||||
/// <em>Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will
|
||||
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em>
|
||||
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
|
||||
/// <em>Note: The server receives this message from all clients (including itself).
|
||||
/// Each client receives their own notification sent to the server.</em>
|
||||
/// Each client receives their own notification sent to the server.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnLoadCompleteDelegateHandler OnLoadComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
|
||||
/// <em>Note: The server receives this message from all clients (including itself).
|
||||
/// Each client receives their own notification sent to the server.</em>
|
||||
/// Each client receives their own notification sent to the server.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
|
||||
|
||||
@@ -296,6 +305,7 @@ namespace Unity.Netcode
|
||||
/// <em> Note: The server receives this message from the client, but will never generate this event for itself.
|
||||
/// Each client receives their own notification sent to the server. This is useful to know that a client has
|
||||
/// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
|
||||
|
||||
@@ -319,30 +329,30 @@ namespace Unity.Netcode
|
||||
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
||||
|
||||
/// <summary>
|
||||
/// Proof of concept: Interface version that allows for the decoupling from
|
||||
/// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage)
|
||||
/// The SceneManagerHandler implementation
|
||||
/// </summary>
|
||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||
|
||||
/// <summary>
|
||||
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
|
||||
/// </summary>
|
||||
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
||||
{
|
||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
|
||||
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
|
||||
sceneEventProgress.SetAsyncOperation(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var operation = SceneManager.UnloadSceneAsync(scene);
|
||||
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
|
||||
sceneEventProgress.SetAsyncOperation(operation);
|
||||
return operation;
|
||||
}
|
||||
}
|
||||
|
||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||
/// End of Proof of Concept
|
||||
|
||||
|
||||
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
||||
|
||||
/// <summary>
|
||||
@@ -425,6 +435,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
SceneUnloadEventHandler.Shutdown();
|
||||
|
||||
foreach (var keypair in SceneEventDataStore)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
@@ -632,6 +644,12 @@ namespace Unity.Netcode
|
||||
return validated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for NetcodeIntegrationTest testing in order to properly
|
||||
/// assign the right loaded scene to the right client's ScenesLoaded list
|
||||
/// </summary>
|
||||
internal Func<string, Scene> OverrideGetAndAddNewlyLoadedSceneByName;
|
||||
|
||||
/// <summary>
|
||||
/// Since SceneManager.GetSceneByName only returns the first scene that matches the name
|
||||
/// we must "find" a newly added scene by looking through all loaded scenes and determining
|
||||
@@ -643,20 +661,27 @@ namespace Unity.Netcode
|
||||
/// <returns></returns>
|
||||
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
if (OverrideGetAndAddNewlyLoadedSceneByName != null)
|
||||
{
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
return OverrideGetAndAddNewlyLoadedSceneByName.Invoke(sceneName);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
{
|
||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
{
|
||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -720,18 +745,18 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="globalObjectIdHash"></param>
|
||||
/// <returns></returns>
|
||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash)
|
||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash, int? networkSceneHandle)
|
||||
{
|
||||
if (ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(SceneBeingSynchronized.handle))
|
||||
var sceneHandle = SceneBeingSynchronized.handle;
|
||||
if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0)
|
||||
{
|
||||
var inScenePlacedNetworkObject = ScenePlacedObjects[globalObjectIdHash][SceneBeingSynchronized.handle];
|
||||
|
||||
// We can only have 1 duplicated globalObjectIdHash per scene instance, so remove it once it has been returned
|
||||
ScenePlacedObjects[globalObjectIdHash].Remove(SceneBeingSynchronized.handle);
|
||||
|
||||
return inScenePlacedNetworkObject;
|
||||
sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value];
|
||||
}
|
||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
|
||||
{
|
||||
return ScenePlacedObjects[globalObjectIdHash][sceneHandle];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -859,16 +884,17 @@ namespace Unity.Netcode
|
||||
/// Callback for the <see cref="SceneEventProgress.OnComplete"/> <see cref="SceneEventProgress.OnCompletedDelegate"/> handler
|
||||
/// </summary>
|
||||
/// <param name="sceneEventProgress"></param>
|
||||
/// <returns></returns>
|
||||
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var sceneEventData = BeginSceneEvent();
|
||||
var clientsThatCompleted = sceneEventProgress.GetClientsWithStatus(true);
|
||||
var clientsThatTimedOut = sceneEventProgress.GetClientsWithStatus(false);
|
||||
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
|
||||
sceneEventData.SceneHash = sceneEventProgress.SceneHash;
|
||||
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
|
||||
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
|
||||
sceneEventData.ClientsCompleted = clientsThatCompleted;
|
||||
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
|
||||
sceneEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList();
|
||||
sceneEventData.ClientsTimedOut = clientsThatTimedOut;
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -889,8 +915,8 @@ namespace Unity.Netcode
|
||||
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
LoadSceneMode = sceneEventProgress.LoadSceneMode,
|
||||
ClientsThatCompleted = sceneEventProgress.DoneClients,
|
||||
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
|
||||
ClientsThatCompleted = clientsThatCompleted,
|
||||
ClientsThatTimedOut = clientsThatTimedOut,
|
||||
});
|
||||
|
||||
if (sceneEventData.SceneEventType == SceneEventType.LoadEventCompleted)
|
||||
@@ -911,7 +937,7 @@ namespace Unity.Netcode
|
||||
/// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene.
|
||||
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/>
|
||||
/// </summary>
|
||||
/// <param name="sceneName">scene name to unload</param>
|
||||
/// <param name="scene"></param>
|
||||
/// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns>
|
||||
public SceneEventProgressStatus UnloadScene(Scene scene)
|
||||
{
|
||||
@@ -945,11 +971,9 @@ namespace Unity.Netcode
|
||||
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
|
||||
|
||||
ScenesLoaded.Remove(scene.handle);
|
||||
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
|
||||
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
|
||||
|
||||
// Notify local server that a scene is going to be unloaded
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -993,9 +1017,10 @@ namespace Unity.Netcode
|
||||
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
|
||||
}
|
||||
m_IsSceneEventActive = true;
|
||||
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle],
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress);
|
||||
|
||||
ScenesLoaded.Remove(sceneHandle);
|
||||
|
||||
@@ -1021,6 +1046,12 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private void OnSceneUnloaded(uint sceneEventId)
|
||||
{
|
||||
// If we are shutdown or about to shutdown, then ignore this event
|
||||
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
// First thing we do, if we are a server, is to send the unload scene event.
|
||||
if (m_NetworkManager.IsServer)
|
||||
@@ -1033,7 +1064,7 @@ namespace Unity.Netcode
|
||||
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1064,7 +1095,7 @@ namespace Unity.Netcode
|
||||
|
||||
private void EmptySceneUnloadedOperation(uint sceneEventId)
|
||||
{
|
||||
// Do nothing (this is a stub call since it is only used to flush all currently loaded scenes)
|
||||
// Do nothing (this is a stub call since it is only used to flush all additively loaded scenes)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1074,6 +1105,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
|
||||
{
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
|
||||
var currentActiveScene = SceneManager.GetActiveScene();
|
||||
foreach (var keyHandleEntry in ScenesLoaded)
|
||||
@@ -1081,17 +1113,11 @@ namespace Unity.Netcode
|
||||
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
|
||||
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
|
||||
{
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
|
||||
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = sceneUnload,
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = keyHandleEntry.Value.name,
|
||||
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
|
||||
ClientId = NetworkManager.ServerClientId
|
||||
});
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation;
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress);
|
||||
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
||||
}
|
||||
}
|
||||
// clear out our scenes loaded list
|
||||
@@ -1104,6 +1130,7 @@ namespace Unity.Netcode
|
||||
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/>
|
||||
/// </summary>
|
||||
/// <param name="sceneName">the name of the scene to be loaded</param>
|
||||
/// <param name="loadSceneMode">how the scene will be loaded (single or additive mode)</param>
|
||||
/// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns>
|
||||
public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSceneMode)
|
||||
{
|
||||
@@ -1124,12 +1151,12 @@ namespace Unity.Netcode
|
||||
sceneEventData.SceneEventType = SceneEventType.Load;
|
||||
sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName);
|
||||
sceneEventData.LoadSceneMode = loadSceneMode;
|
||||
|
||||
var sceneEventId = sceneEventData.SceneEventId;
|
||||
// This both checks to make sure the scene is valid and if not resets the active scene event
|
||||
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
|
||||
if (!m_IsSceneEventActive)
|
||||
{
|
||||
EndSceneEvent(sceneEventData.SceneEventId);
|
||||
EndSceneEvent(sceneEventId);
|
||||
return SceneEventProgressStatus.SceneFailedVerification;
|
||||
}
|
||||
|
||||
@@ -1142,15 +1169,16 @@ namespace Unity.Netcode
|
||||
MoveObjectsToDontDestroyOnLoad();
|
||||
|
||||
// Now Unload all currently additively loaded scenes
|
||||
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
|
||||
UnloadAdditivelyLoadedScenes(sceneEventId);
|
||||
|
||||
// Register the active scene for unload scene event notifications
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
// Now start loading the scene
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded });
|
||||
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
||||
// Notify the local server that a scene loading event has begun
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
@@ -1167,6 +1195,114 @@ namespace Unity.Netcode
|
||||
return sceneEventProgress.Status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to handle "odd ball" scene unload event notification scenarios
|
||||
/// when scene switching.
|
||||
/// </summary>
|
||||
internal class SceneUnloadEventHandler
|
||||
{
|
||||
private static Dictionary<NetworkManager, List<SceneUnloadEventHandler>> s_Instances = new Dictionary<NetworkManager, List<SceneUnloadEventHandler>>();
|
||||
|
||||
internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||
{
|
||||
var networkManager = networkSceneManager.m_NetworkManager;
|
||||
if (!s_Instances.ContainsKey(networkManager))
|
||||
{
|
||||
s_Instances.Add(networkManager, new List<SceneUnloadEventHandler>());
|
||||
}
|
||||
var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId;
|
||||
s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation));
|
||||
}
|
||||
|
||||
private static void SceneUnloadComplete(SceneUnloadEventHandler sceneUnloadEventHandler)
|
||||
{
|
||||
if (sceneUnloadEventHandler == null || sceneUnloadEventHandler.m_NetworkSceneManager == null || sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var networkManager = sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager;
|
||||
if (s_Instances.ContainsKey(networkManager))
|
||||
{
|
||||
s_Instances[networkManager].Remove(sceneUnloadEventHandler);
|
||||
if (s_Instances[networkManager].Count == 0)
|
||||
{
|
||||
s_Instances.Remove(networkManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by NetworkSceneManager when it is disposing
|
||||
/// </summary>
|
||||
internal static void Shutdown()
|
||||
{
|
||||
foreach (var instanceEntry in s_Instances)
|
||||
{
|
||||
foreach (var instance in instanceEntry.Value)
|
||||
{
|
||||
instance.OnShutdown();
|
||||
}
|
||||
instanceEntry.Value.Clear();
|
||||
}
|
||||
s_Instances.Clear();
|
||||
}
|
||||
|
||||
private NetworkSceneManager m_NetworkSceneManager;
|
||||
private AsyncOperation m_AsyncOperation;
|
||||
private LoadSceneMode m_LoadSceneMode;
|
||||
private ulong m_ClientId;
|
||||
private Scene m_Scene;
|
||||
private bool m_ShuttingDown;
|
||||
|
||||
private void OnShutdown()
|
||||
{
|
||||
m_ShuttingDown = true;
|
||||
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||
}
|
||||
|
||||
private void SceneUnloaded(Scene scene)
|
||||
{
|
||||
if (m_Scene.handle == scene.handle && !m_ShuttingDown)
|
||||
{
|
||||
if (m_NetworkSceneManager != null && m_NetworkSceneManager.m_NetworkManager != null)
|
||||
{
|
||||
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = m_AsyncOperation,
|
||||
SceneEventType = SceneEventType.UnloadComplete,
|
||||
SceneName = m_Scene.name,
|
||||
LoadSceneMode = m_LoadSceneMode,
|
||||
ClientId = m_ClientId
|
||||
});
|
||||
m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name);
|
||||
}
|
||||
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||
SceneUnloadComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||
{
|
||||
m_LoadSceneMode = loadSceneMode;
|
||||
m_AsyncOperation = asyncOperation;
|
||||
m_NetworkSceneManager = networkSceneManager;
|
||||
m_ClientId = clientId;
|
||||
m_Scene = scene;
|
||||
SceneManager.sceneUnloaded += SceneUnloaded;
|
||||
// Send the initial unload event notification
|
||||
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = m_AsyncOperation,
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = m_Scene.name,
|
||||
LoadSceneMode = m_LoadSceneMode,
|
||||
ClientId = clientId
|
||||
});
|
||||
|
||||
m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.m_NetworkManager.LocalClientId, m_Scene.name, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Side:
|
||||
/// Handles both forms of scene loading
|
||||
@@ -1201,10 +1337,15 @@ namespace Unity.Netcode
|
||||
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
||||
{
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
}
|
||||
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded });
|
||||
// Register the active scene for unload scene event notifications
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
|
||||
}
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress);
|
||||
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
@@ -1218,13 +1359,18 @@ namespace Unity.Netcode
|
||||
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Client and Server:
|
||||
/// Generic on scene loaded callback method to be called upon a scene loading
|
||||
/// </summary>
|
||||
private void OnSceneLoaded(uint sceneEventId)
|
||||
{
|
||||
// If we are shutdown or about to shutdown, then ignore this event
|
||||
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
|
||||
if (!nextScene.isLoaded || !nextScene.IsValid())
|
||||
@@ -1295,6 +1441,9 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
|
||||
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||
|
||||
// Set the server's scene's handle so the client can build a look up table
|
||||
sceneEventData.SceneHandle = scene.handle;
|
||||
|
||||
@@ -1330,7 +1479,7 @@ namespace Unity.Netcode
|
||||
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(NetworkManager.ServerClientId);
|
||||
}
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
@@ -1363,6 +1512,13 @@ namespace Unity.Netcode
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for integration testing, due to the complexities of having all clients loading scenes
|
||||
/// this is needed to "filter" out the scenes not loaded by NetworkSceneManager
|
||||
/// (i.e. we don't want a late joining player to load all of the other client scenes)
|
||||
/// </summary>
|
||||
internal Func<Scene, bool> ExcludeSceneFromSychronization;
|
||||
|
||||
/// <summary>
|
||||
/// Server Side:
|
||||
/// This is used for players that have just had their connection approved and will assure they are synchronized
|
||||
@@ -1389,6 +1545,13 @@ namespace Unity.Netcode
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
|
||||
// NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager
|
||||
// unless the scene in question is the currently active scene.
|
||||
if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
||||
|
||||
// This would depend upon whether we are additive or not
|
||||
@@ -1406,11 +1569,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle);
|
||||
}
|
||||
|
||||
sceneEventData.AddSpawnedNetworkObjects();
|
||||
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -1447,7 +1610,7 @@ namespace Unity.Netcode
|
||||
var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive;
|
||||
|
||||
// Store the sceneHandle and hash
|
||||
sceneEventData.ClientSceneHandle = sceneHandle;
|
||||
sceneEventData.NetworkSceneHandle = sceneHandle;
|
||||
sceneEventData.ClientSceneHash = sceneHash;
|
||||
|
||||
// If this is the beginning of the synchronization event, then send client a notification that synchronization has begun
|
||||
@@ -1490,8 +1653,10 @@ namespace Unity.Netcode
|
||||
if (!shouldPassThrough)
|
||||
{
|
||||
// If not, then load the scene
|
||||
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization });
|
||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
||||
sceneEventProgress.SceneEventId = sceneEventId;
|
||||
sceneEventProgress.OnSceneEventCompleted = ClientLoadedSynchronization;
|
||||
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
||||
|
||||
// Notify local client that a scene load has begun
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1536,9 +1701,9 @@ namespace Unity.Netcode
|
||||
SceneManager.SetActiveScene(nextScene);
|
||||
}
|
||||
|
||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle))
|
||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.NetworkSceneHandle))
|
||||
{
|
||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle);
|
||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.NetworkSceneHandle, nextScene.handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1708,7 +1873,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
|
||||
}
|
||||
EndSceneEvent(sceneEventId);
|
||||
break;
|
||||
@@ -1717,7 +1882,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(clientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].ClientFinishedSceneEvent(clientId);
|
||||
}
|
||||
// Notify the local server that the client has finished unloading a scene
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1862,10 +2027,9 @@ namespace Unity.Netcode
|
||||
foreach (var networkObjectInstance in networkObjects)
|
||||
{
|
||||
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
||||
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
|
||||
var sceneHandle = networkObjectInstance.GetSceneOriginHandle();
|
||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
|
||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
|
||||
sceneHandle == sceneToFilterBy.handle)
|
||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && sceneHandle == sceneToFilterBy.handle)
|
||||
{
|
||||
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Unity.Netcode
|
||||
|
||||
// Used by the client during synchronization
|
||||
internal uint ClientSceneHash;
|
||||
internal int ClientSceneHandle;
|
||||
internal int NetworkSceneHandle;
|
||||
|
||||
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
|
||||
/// NetworkVariable information. If that process changes, then we need to update this
|
||||
@@ -118,6 +118,9 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
|
||||
|
||||
private List<NetworkObject> m_DespawnedInSceneObjectsSync = new List<NetworkObject>();
|
||||
private Dictionary<int, List<uint>> m_DespawnedInSceneObjects = new Dictionary<int, List<uint>>();
|
||||
|
||||
/// <summary>
|
||||
/// Server Side Re-Synchronization:
|
||||
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
|
||||
@@ -240,7 +243,40 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Add(sobj);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by parents before children
|
||||
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
|
||||
|
||||
// Sort by INetworkPrefabInstanceHandler implementation before the
|
||||
// NetworkObjects spawned by the implementation
|
||||
m_NetworkObjectsSync.Sort(SortNetworkObjects);
|
||||
|
||||
// This is useful to know what NetworkObjects a client is going to be synchronized with
|
||||
// as well as the order in which they will be deserialized
|
||||
if (m_NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
|
||||
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
|
||||
foreach (var networkObject in m_NetworkObjectsSync)
|
||||
{
|
||||
messageBuilder.Append($"{networkObject.name}");
|
||||
}
|
||||
NetworkLog.LogInfo(messageBuilder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddDespawnedInSceneNetworkObjects()
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Clear();
|
||||
// Find all active and non-active in-scene placed NetworkObjects
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
|
||||
foreach (var sobj in inSceneNetworkObjects)
|
||||
{
|
||||
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Add(sobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -307,6 +343,32 @@ namespace Unity.Netcode
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the synchronization order of the NetworkObjects to be serialized
|
||||
/// by parents before children.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only handles late joining players. Spawning and nesting several children
|
||||
/// dynamically is still handled by the orphaned child list when deserialized out of
|
||||
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
|
||||
/// dropped and re-sent but child object is received and processed)
|
||||
/// </remarks>
|
||||
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
|
||||
{
|
||||
// If the first has a parent, move the first down
|
||||
if (first.transform.parent != null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else // If the second has a parent and the first does not, then move the first up
|
||||
if (second.transform.parent != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Client and Server Side:
|
||||
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
|
||||
@@ -372,7 +434,6 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
|
||||
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
|
||||
|
||||
|
||||
// Store our current position in the stream to come back and say how much data we have written
|
||||
var positionStart = writer.Position;
|
||||
|
||||
@@ -383,17 +444,30 @@ namespace Unity.Netcode
|
||||
int totalBytes = 0;
|
||||
|
||||
// Write the number of NetworkObjects we are serializing
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync.Count());
|
||||
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count);
|
||||
// Serialize all NetworkObjects that are spawned
|
||||
for (var i = 0; i < m_NetworkObjectsSync.Count; ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle);
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
|
||||
sceneObject.Serialize(writer);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Write the number of despawned in-scene placed NetworkObjects
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
|
||||
// Write the scene handle and GlobalObjectIdHash value
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Size Place Holder -- End
|
||||
var positionEnd = writer.Position;
|
||||
var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint)));
|
||||
@@ -433,6 +507,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Write the number of despawned in-scene placed NetworkObjects
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count);
|
||||
// Write the scene handle and GlobalObjectIdHash value
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
|
||||
{
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
}
|
||||
|
||||
var tailPosition = writer.Position;
|
||||
// Reposition to our count position to the head before we wrote our object count
|
||||
writer.Seek(headPosition);
|
||||
@@ -550,6 +633,8 @@ namespace Unity.Netcode
|
||||
sceneObject.Deserialize(InternalBuffer);
|
||||
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
|
||||
}
|
||||
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
|
||||
DeserializeDespawnedInScenePlacedNetworkObjects();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -672,6 +757,84 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For synchronizing any despawned in-scene placed NetworkObjects that were
|
||||
/// despawned by the server during synchronization or scene loading
|
||||
/// </summary>
|
||||
private void DeserializeDespawnedInScenePlacedNetworkObjects()
|
||||
{
|
||||
// Process all de-spawned in-scene NetworkObjects for this network session
|
||||
m_DespawnedInSceneObjects.Clear();
|
||||
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
|
||||
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
|
||||
|
||||
for (int i = 0; i < despawnedObjectsCount; i++)
|
||||
{
|
||||
// We just need to get the scene
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
|
||||
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
|
||||
if (!sceneCache.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
|
||||
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
|
||||
{
|
||||
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
|
||||
|
||||
// Find all active and non-active in-scene placed NetworkObjects
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
|
||||
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||
|
||||
foreach (var inSceneObject in inSceneNetworkObjects)
|
||||
{
|
||||
if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash))
|
||||
{
|
||||
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
|
||||
}
|
||||
}
|
||||
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
|
||||
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else // Use the cached NetworkObjects if they exist
|
||||
{
|
||||
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
|
||||
}
|
||||
|
||||
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
|
||||
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
// Since this is a NetworkObject that was never spawned, we just need to send a notification
|
||||
// out that it was despawned so users can make adjustments
|
||||
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Side:
|
||||
/// During the processing of a server sent Event_Sync, this method will be called for each scene once
|
||||
@@ -683,15 +846,16 @@ namespace Unity.Netcode
|
||||
{
|
||||
try
|
||||
{
|
||||
// Process all NetworkObjects for this scene
|
||||
InternalBuffer.ReadValueSafe(out int newObjectsCount);
|
||||
// Process all spawned NetworkObjects for this network session
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
|
||||
|
||||
|
||||
for (int i = 0; i < newObjectsCount; i++)
|
||||
{
|
||||
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
|
||||
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
|
||||
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
|
||||
InternalBuffer.ReadValueSafe(out int handle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
|
||||
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
@@ -703,6 +867,10 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
|
||||
DeserializeDespawnedInScenePlacedNetworkObjects();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -58,12 +58,13 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// List of clientIds of those clients that is done loading the scene.
|
||||
/// </summary>
|
||||
internal List<ulong> DoneClients { get; } = new List<ulong>();
|
||||
internal Dictionary<ulong, bool> ClientsProcessingSceneEvent { get; } = new Dictionary<ulong, bool>();
|
||||
internal List<ulong> ClientsThatDisconnected = new List<ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkTime at the moment the scene switch was initiated by the server.
|
||||
/// This is when the current scene event will have timed out
|
||||
/// </summary>
|
||||
internal NetworkTime TimeAtInitiation { get; }
|
||||
internal float WhenSceneEventHasTimedOut;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
||||
@@ -75,17 +76,15 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal OnCompletedDelegate OnComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Is this scene switch progresses completed, all clients are done loading the scene or a timeout has occurred.
|
||||
/// </summary>
|
||||
internal bool IsCompleted { get; private set; }
|
||||
|
||||
internal bool TimedOut { get; private set; }
|
||||
internal Action<uint> OnSceneEventCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// If all clients are done loading the scene, at the moment of completed.
|
||||
/// This will make sure that we only have timed out if we never completed
|
||||
/// </summary>
|
||||
internal bool AreAllClientsDoneLoading { get; private set; }
|
||||
internal bool HasTimedOut()
|
||||
{
|
||||
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The hash value generated from the full scene path
|
||||
@@ -93,9 +92,10 @@ namespace Unity.Netcode
|
||||
internal uint SceneHash { get; set; }
|
||||
|
||||
internal Guid Guid { get; } = Guid.NewGuid();
|
||||
internal uint SceneEventId;
|
||||
|
||||
private Coroutine m_TimeOutCoroutine;
|
||||
private AsyncOperation m_SceneLoadOperation;
|
||||
private AsyncOperation m_AsyncOperation;
|
||||
|
||||
private NetworkManager m_NetworkManager { get; }
|
||||
|
||||
@@ -105,55 +105,169 @@ namespace Unity.Netcode
|
||||
|
||||
internal LoadSceneMode LoadSceneMode;
|
||||
|
||||
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
|
||||
{
|
||||
var clients = new List<ulong>();
|
||||
foreach (var clientStatus in ClientsProcessingSceneEvent)
|
||||
{
|
||||
if (clientStatus.Value == completedSceneEvent)
|
||||
{
|
||||
clients.Add(clientStatus.Key);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are getting the list of clients that have not completed the
|
||||
// scene event, then add any clients that disconnected during this
|
||||
// scene event.
|
||||
if (!completedSceneEvent)
|
||||
{
|
||||
clients.AddRange(ClientsThatDisconnected);
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
||||
{
|
||||
if (status == SceneEventProgressStatus.Started)
|
||||
{
|
||||
m_NetworkManager = networkManager;
|
||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||
TimeAtInitiation = networkManager.LocalTime;
|
||||
|
||||
if (networkManager.IsServer)
|
||||
{
|
||||
m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
|
||||
// Track the clients that were connected when we started this event
|
||||
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
|
||||
{
|
||||
ClientsProcessingSceneEvent.Add(connectedClientId, false);
|
||||
}
|
||||
|
||||
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
|
||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||
}
|
||||
}
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the client from the clients processing the current scene event
|
||||
/// Add this client to the clients that disconnected list
|
||||
/// </summary>
|
||||
private void OnClientDisconnectCallback(ulong clientId)
|
||||
{
|
||||
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
|
||||
{
|
||||
ClientsThatDisconnected.Add(clientId);
|
||||
ClientsProcessingSceneEvent.Remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that checks to see if the scene event is complete every network tick period.
|
||||
/// This will handle completing the scene event when one or more client(s) disconnect(s)
|
||||
/// during a scene event and if it does not complete within the scene loading time out period
|
||||
/// it will time out the scene event.
|
||||
/// </summary>
|
||||
internal IEnumerator TimeOutSceneEventProgress()
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(m_NetworkManager.NetworkConfig.LoadSceneTimeOut);
|
||||
TimedOut = true;
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
internal void AddClientAsDone(ulong clientId)
|
||||
{
|
||||
DoneClients.Add(clientId);
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
internal void RemoveClientAsDone(ulong clientId)
|
||||
{
|
||||
DoneClients.Remove(clientId);
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
internal void SetSceneLoadOperation(AsyncOperation sceneLoadOperation)
|
||||
{
|
||||
m_SceneLoadOperation = sceneLoadOperation;
|
||||
m_SceneLoadOperation.completed += operation => CheckCompletion();
|
||||
}
|
||||
|
||||
internal void CheckCompletion()
|
||||
{
|
||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && m_SceneLoadOperation.isDone) || (!IsCompleted && TimedOut))
|
||||
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
|
||||
while (!HasTimedOut())
|
||||
{
|
||||
IsCompleted = true;
|
||||
AreAllClientsDoneLoading = true;
|
||||
yield return waitForNetworkTick;
|
||||
|
||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||
TryFinishingSceneEventProgress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the client's scene event progress to finished/true
|
||||
/// </summary>
|
||||
internal void ClientFinishedSceneEvent(ulong clientId)
|
||||
{
|
||||
if (ClientsProcessingSceneEvent.ContainsKey(clientId))
|
||||
{
|
||||
ClientsProcessingSceneEvent[clientId] = true;
|
||||
TryFinishingSceneEventProgress();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the scene event has finished for both
|
||||
/// client(s) and server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The server checks if all known clients processing this scene event
|
||||
/// have finished and then it returns its local AsyncOperation status.
|
||||
/// Clients finish when their AsyncOperation finishes.
|
||||
/// </remarks>
|
||||
private bool HasFinished()
|
||||
{
|
||||
// If the network session is terminated/terminating then finish tracking
|
||||
// this scene event
|
||||
if (!IsNetworkSessionActive())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clients skip over this
|
||||
foreach (var clientStatus in ClientsProcessingSceneEvent)
|
||||
{
|
||||
if (!clientStatus.Value)
|
||||
{
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the local scene event's AsyncOperation status
|
||||
return m_AsyncOperation.isDone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the AsyncOperation for the scene load/unload event
|
||||
/// </summary>
|
||||
internal void SetAsyncOperation(AsyncOperation asyncOperation)
|
||||
{
|
||||
m_AsyncOperation = asyncOperation;
|
||||
m_AsyncOperation.completed += new Action<AsyncOperation>(asyncOp2 =>
|
||||
{
|
||||
// Don't invoke the callback if the network session is disconnected
|
||||
// during a SceneEventProgress
|
||||
if (IsNetworkSessionActive())
|
||||
{
|
||||
OnSceneEventCompleted?.Invoke(SceneEventId);
|
||||
}
|
||||
|
||||
// Go ahead and try finishing even if the network session is terminated/terminating
|
||||
// as we might need to stop the coroutine
|
||||
TryFinishingSceneEventProgress();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
internal bool IsNetworkSessionActive()
|
||||
{
|
||||
return m_NetworkManager != null && m_NetworkManager.IsListening && !m_NetworkManager.ShutdownInProgress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will try to finish the current scene event in progress as long as
|
||||
/// all conditions are met.
|
||||
/// </summary>
|
||||
internal void TryFinishingSceneEventProgress()
|
||||
{
|
||||
if (HasFinished() || HasTimedOut())
|
||||
{
|
||||
// Don't attempt to finalize this scene event if we are no longer listening or a shutdown is in progress
|
||||
if (IsNetworkSessionActive())
|
||||
{
|
||||
OnComplete?.Invoke(this);
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
m_NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback;
|
||||
}
|
||||
|
||||
if (m_TimeOutCoroutine != null)
|
||||
{
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to count the number of bytes or bits needed to serialize a value.
|
||||
/// </summary>
|
||||
public static class BitCounter
|
||||
{
|
||||
// Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest)
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Unity.Netcode
|
||||
|
||||
private const int k_BitsPerByte = 8;
|
||||
|
||||
private int BytePosition => m_BitPosition >> 3;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
|
||||
/// </summary>
|
||||
@@ -98,11 +100,6 @@ namespace Unity.Netcode
|
||||
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
|
||||
}
|
||||
|
||||
if (bitCount < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
|
||||
}
|
||||
|
||||
int checkPos = (int)(m_BitPosition + bitCount);
|
||||
if (checkPos > m_AllowedBitwiseReadMark)
|
||||
{
|
||||
@@ -165,7 +162,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
|
||||
int offset = m_BitPosition & 7;
|
||||
int pos = m_BitPosition >> 3;
|
||||
int pos = BytePosition;
|
||||
bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
|
||||
++m_BitPosition;
|
||||
}
|
||||
@@ -175,7 +172,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
var val = new T();
|
||||
byte* ptr = ((byte*)&val) + offsetBytes;
|
||||
byte* bufferPointer = m_BufferPointer + m_Position;
|
||||
byte* bufferPointer = m_BufferPointer + BytePosition;
|
||||
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
|
||||
|
||||
m_BitPosition += bytesToRead * k_BitsPerByte;
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Unity.Netcode
|
||||
get => (m_BitPosition & 7) == 0;
|
||||
}
|
||||
|
||||
private int BytePosition => m_BitPosition >> 3;
|
||||
|
||||
internal unsafe BitWriter(FastBufferWriter writer)
|
||||
{
|
||||
m_Writer = writer;
|
||||
@@ -181,7 +183,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
|
||||
int offset = m_BitPosition & 7;
|
||||
int pos = m_BitPosition >> 3;
|
||||
int pos = BytePosition;
|
||||
++m_BitPosition;
|
||||
m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
|
||||
}
|
||||
@@ -190,7 +192,7 @@ namespace Unity.Netcode
|
||||
private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
|
||||
{
|
||||
byte* ptr = ((byte*)&value) + offsetBytes;
|
||||
byte* bufferPointer = m_BufferPointer + m_Position;
|
||||
byte* bufferPointer = m_BufferPointer + BytePosition;
|
||||
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
|
||||
|
||||
m_BitPosition += bytesToWrite * k_BitsPerByte;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -61,63 +62,526 @@ namespace Unity.Netcode
|
||||
return m_Implementation.GetFastBufferWriter();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a string
|
||||
/// </summary>
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValue(ref s, oneByteChars);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a single byte
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref byte value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of primitive values (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an enum value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of enum values
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct value implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct values implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct or class value implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct or class values implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector2 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector2Int value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector3Int value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector4 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector4 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Quaternion value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Quaternion values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Quaternion[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Color value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Color[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color32 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Color32 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color32 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Color32[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Ray value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Ray[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray2D value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray2D values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpy'ing the whole thing even if
|
||||
// most of it isn't used.
|
||||
/// <summary>
|
||||
/// Read or write a FixedString value
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of FixedStrings</param>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a NetworkSerializable value.
|
||||
/// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Performs an advance check to ensure space is available to read/write one or more values.
|
||||
/// This provides a performance benefit for serializing multiple values using the
|
||||
/// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be
|
||||
/// noticeable if serializing a very large number of items.
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <returns></returns>
|
||||
public bool PreCheck(int amount)
|
||||
{
|
||||
return m_Implementation.PreCheck(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a string, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a byte, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref byte value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a primitive, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of primitives, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable types in an array</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of primitives</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an enum, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of enums</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of enums, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable types in an array</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of enums</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a struct, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of structs</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of structs, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable types in an array</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of structs</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4Array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color32 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a FixedString, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution for fixed strings</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -33,12 +34,20 @@ namespace Unity.Netcode
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializableInPlace(ref value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValueSafe(out value);
|
||||
|
||||
public void SerializeValue(ref Vector2 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Reader.ReadValueSafe(out value);
|
||||
@@ -67,10 +76,16 @@ namespace Unity.Netcode
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Reader.ReadValue(out value);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -35,10 +36,17 @@ namespace Unity.Netcode
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValueSafe(value);
|
||||
|
||||
public void SerializeValue(ref Vector2 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Writer.WriteValueSafe(value);
|
||||
@@ -71,10 +79,17 @@ namespace Unity.Netcode
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValue(value);
|
||||
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Writer.WriteValue(value);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for packing values in serialization.
|
||||
/// <seealso cref="ByteUnpacker"/> to unpack packed values.
|
||||
/// </summary>
|
||||
public static class BytePacker
|
||||
{
|
||||
@@ -282,14 +283,49 @@ namespace Unity.Netcode
|
||||
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0)
|
||||
/// </summary>
|
||||
public const ushort BitPackedUshortMax = (1 << 15) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked short
|
||||
/// </summary>
|
||||
public const short BitPackedShortMax = (1 << 14) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked ushort
|
||||
/// </summary>
|
||||
public const short BitPackedShortMin = -(1 << 14);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0)
|
||||
/// </summary>
|
||||
public const uint BitPackedUintMax = (1 << 30) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked int
|
||||
/// </summary>
|
||||
public const int BitPackedIntMax = (1 << 29) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked int
|
||||
/// </summary>
|
||||
public const int BitPackedIntMin = -(1 << 29);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0)
|
||||
/// </summary>
|
||||
public const ulong BitPackedULongMax = (1L << 61) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked long
|
||||
/// </summary>
|
||||
public const long BitPackedLongMax = (1L << 60) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked long
|
||||
/// </summary>
|
||||
public const long BitPackedLongMin = -(1L << 60);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,11 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Byte Unpacking Helper Class
|
||||
/// Use this class to unpack values during deserialization for values that were packed.
|
||||
/// <seealso cref="BytePacker"/> to pack unpacked values
|
||||
/// </summary>
|
||||
public static class ByteUnpacker
|
||||
{
|
||||
|
||||
@@ -12,6 +17,13 @@ namespace Unity.Netcode
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValuePacked<T>(FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value);
|
||||
#else
|
||||
/// <summary>
|
||||
/// Read a packed enum value
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">The value that's read</param>
|
||||
/// <typeparam name="TEnum">Type of enum to read</typeparam>
|
||||
/// <exception cref="InvalidOperationException">Throws InvalidOperationException if an enum somehow ends up not being the size of a byte, short, int, or long (which should be impossible)</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe void ReadValuePacked<TEnum>(FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum
|
||||
{
|
||||
|
||||
@@ -6,6 +6,12 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Optimized class used for reading values from a byte stream
|
||||
/// <seealso cref="FastBufferWriter"/>
|
||||
/// <seealso cref="BytePacker"/>
|
||||
/// <seealso cref="ByteUnpacker"/>
|
||||
/// </summary>
|
||||
public struct FastBufferReader : IDisposable
|
||||
{
|
||||
internal struct ReaderHandle
|
||||
@@ -54,25 +60,29 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator allocator)
|
||||
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator copyAllocator, Allocator internalAllocator)
|
||||
{
|
||||
ReaderHandle* readerHandle = null;
|
||||
if (allocator == Allocator.None)
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), Allocator.Temp);
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), internalAllocator);
|
||||
readerHandle->BufferPointer = buffer;
|
||||
readerHandle->Position = offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), allocator);
|
||||
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), copyAllocator);
|
||||
UnsafeUtility.MemCpy(readerHandle + 1, buffer + offset, length);
|
||||
readerHandle->BufferPointer = (byte*)(readerHandle + 1);
|
||||
readerHandle->Position = 0;
|
||||
}
|
||||
|
||||
readerHandle->Length = length;
|
||||
readerHandle->Allocator = allocator;
|
||||
|
||||
// If the copyAllocator provided is Allocator.None, there is a chance that the internalAllocator was provided
|
||||
// When we dispose, we are really only interested in disposing Allocator.Persistent and Allocator.TempJob
|
||||
// as disposing Allocator.Temp and Allocator.None would do nothing. Therefore, make sure we dispose the readerHandle with the right Allocator label
|
||||
readerHandle->Allocator = copyAllocator == Allocator.None ? internalAllocator : copyAllocator;
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
readerHandle->AllowedReadMark = 0;
|
||||
readerHandle->InBitwiseContext = false;
|
||||
@@ -83,23 +93,26 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from a NativeArray.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// Allocator.Temp so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"></param> param is explicitly set
|
||||
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||
/// should manually call Dispose() when it is no longer needed.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="allocator"></param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length"></param>
|
||||
/// <param name="offset"></param>
|
||||
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, allocator);
|
||||
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,18 +125,18 @@ namespace Unity.Netcode
|
||||
/// and ensure the FastBufferReader isn't used outside that block.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
|
||||
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator copyAllocator, int length = -1, int offset = 0)
|
||||
{
|
||||
if (allocator == Allocator.None)
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
||||
}
|
||||
fixed (byte* data = buffer.Array)
|
||||
{
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, allocator);
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, copyAllocator, Allocator.Temp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,74 +150,80 @@ namespace Unity.Netcode
|
||||
/// and ensure the FastBufferReader isn't used outside that block.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(byte[] buffer, Allocator allocator, int length = -1, int offset = 0)
|
||||
public unsafe FastBufferReader(byte[] buffer, Allocator copyAllocator, int length = -1, int offset = 0)
|
||||
{
|
||||
if (allocator == Allocator.None)
|
||||
if (copyAllocator == Allocator.None)
|
||||
{
|
||||
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
|
||||
}
|
||||
fixed (byte* data = buffer)
|
||||
{
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, allocator);
|
||||
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, copyAllocator, Allocator.Temp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from an existing byte buffer.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"></param> param is explicitly set
|
||||
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||
/// should manually call Dispose() when it is no longer needed.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(byte* buffer, Allocator copyAllocator, int length, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle(buffer, length, offset, allocator);
|
||||
Handle = CreateHandle(buffer, length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from a FastBufferWriter.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// stored anywhere in heap memory). This is true, unless the <param name="internalAllocator"></param> param is explicitly set
|
||||
/// to i.e.: Allocator.Persistent in which case it would allow the internal data to Persist for longer, but the caller
|
||||
/// should manually call Dispose() when it is no longer needed.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(FastBufferWriter writer, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator);
|
||||
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from another existing FastBufferReader. This is typically used when you
|
||||
/// want to change the allocator that a reader is allocated to - for example, upgrading a Temp reader to
|
||||
/// want to change the copyAllocator that a reader is allocated to - for example, upgrading a Temp reader to
|
||||
/// a Persistent one to be processed later.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// A new buffer will be created using the given <param name="copyAllocator"></param> and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// The exception to this is when the <param name="copyAllocator"></param> passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
@@ -212,16 +231,17 @@ namespace Unity.Netcode
|
||||
/// stored anywhere in heap memory).
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="copyAllocator">The allocator type used for internal data when copying an existing buffer if other than Allocator.None is specified, that memory will be owned by this FastBufferReader instance</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(FastBufferReader reader, Allocator allocator, int length = -1, int offset = 0)
|
||||
/// <param name="internalAllocator">The allocator type used for internal data when this reader points directly at a buffer owned by someone else</param>
|
||||
public unsafe FastBufferReader(FastBufferReader reader, Allocator copyAllocator, int length = -1, int offset = 0, Allocator internalAllocator = Allocator.Temp)
|
||||
{
|
||||
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, allocator);
|
||||
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, copyAllocator, internalAllocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated buffer
|
||||
/// <see cref="IDisposable"/> implementation that frees the allocated buffer
|
||||
/// </summary>
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
@@ -321,6 +341,7 @@ namespace Unity.Netcode
|
||||
/// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
|
||||
/// operations in release builds.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the type `T` of the value you are trying to read</typeparam>
|
||||
/// <param name="value">The value you want to read</param>
|
||||
/// <returns>True if the read is allowed, false otherwise</returns>
|
||||
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
|
||||
@@ -350,7 +371,7 @@ namespace Unity.Netcode
|
||||
/// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward.
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>true upon success</returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe bool TryBeginReadInternal(int bytes)
|
||||
@@ -379,7 +400,7 @@ namespace Unity.Netcode
|
||||
/// Returns an array representation of the underlying byte buffer.
|
||||
/// !!Allocates a new array!!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns>byte array</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe byte[] ToArray()
|
||||
{
|
||||
@@ -394,7 +415,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets a direct pointer to the underlying buffer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns><see cref="byte"/> pointer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe byte* GetUnsafePtr()
|
||||
{
|
||||
@@ -404,7 +425,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets a direct pointer to the underlying buffer at the current read position
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns><see cref="byte"/> pointer</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe byte* GetUnsafePtrAtCurrentPosition()
|
||||
{
|
||||
@@ -414,8 +435,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Read an INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">INetworkSerializable instance</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value">INetworkSerializable instance</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public void ReadNetworkSerializable<T>(out T value) where T : INetworkSerializable, new()
|
||||
{
|
||||
@@ -428,7 +449,7 @@ namespace Unity.Netcode
|
||||
/// Read an array of INetworkSerializables
|
||||
/// </summary>
|
||||
/// <param name="value">INetworkSerializable instance</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T">the array to read the values of type `T` into</typeparam>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public void ReadNetworkSerializable<T>(out T[] value) where T : INetworkSerializable, new()
|
||||
{
|
||||
@@ -440,6 +461,19 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an INetworkSerializable in-place, without constructing a new one
|
||||
/// Note that this will NOT check for null before calling NetworkSerialize
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value">INetworkSerializable instance</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public void ReadNetworkSerializableInPlace<T>(ref T value) where T : INetworkSerializable
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(this));
|
||||
value.NetworkSerialize(bufferSerializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string
|
||||
/// NOTE: ALLOCATES
|
||||
@@ -523,7 +557,7 @@ namespace Unity.Netcode
|
||||
/// <param name="value">Value to read</param>
|
||||
/// <param name="bytesToRead">Number of bytes</param>
|
||||
/// <param name="offsetBytes">Offset into the value to write the bytes</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T">the type value to read the value into</typeparam>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
/// <exception cref="OverflowException"></exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -682,7 +716,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanaged<T>(out T value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanaged<T>(out T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -691,7 +725,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanagedSafe<T>(out T value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanagedSafe<T>(out T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -700,7 +734,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanaged<T>(out T[] value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanaged<T>(out T[] value) where T : unmanaged
|
||||
{
|
||||
ReadUnmanaged(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
@@ -712,7 +746,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanagedSafe<T>(out T[] value) where T : unmanaged
|
||||
internal unsafe void ReadUnmanagedSafe<T>(out T[] value) where T : unmanaged
|
||||
{
|
||||
ReadUnmanagedSafe(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
@@ -724,112 +758,589 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a NetworkSerializable value
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a NetworkSerializable array
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a NetworkSerializable value
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a NetworkSerializable array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a struct
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a struct array
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a struct
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a struct array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a primitive value array (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read an enum value
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read an enum array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read an enum value
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read an enum array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2 value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3 value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2Int
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2Int value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2Int array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2Int[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3Int
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3Int value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3Int array
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3Int[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector4
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector4 value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector4
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Quaternion
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Quaternion value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Quaternion array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Quaternion[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color32
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color32 value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color32 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color32[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray[] value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray2D
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray2D value) => ReadUnmanaged(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray2D array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray2D[] value) => ReadUnmanaged(out value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2 value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3 value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2Int
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2Int value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector2Int array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2Int[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3Int
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3Int value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector3Int array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3Int[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector4
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Vector4 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Quaternion
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Quaternion value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Quaternion array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Quaternion[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Collor array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color32
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color32 value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Color32 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color32[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray2D
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
/// <summary>
|
||||
/// Read a Ray2D array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
|
||||
/// <summary>
|
||||
/// Read a FixedString value.
|
||||
/// This method is a little difficult to use, since you have to know the size of the string before
|
||||
/// reading it, but is useful when the string is a known, fixed size. Note that the size of the
|
||||
/// string is also encoded, so the size to call TryBeginRead on is actually the fixed size (in bytes)
|
||||
/// plus sizeof(int)
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValue<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
ReadUnmanaged(out int length);
|
||||
value = new T();
|
||||
value.Length = length;
|
||||
ReadBytes(value.GetUnsafePtr(), length);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a FixedString value.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValueSafe<T>(out T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
ReadUnmanagedSafe(out int length);
|
||||
value = new T();
|
||||
value.Length = length;
|
||||
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Read a FixedString value.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to read</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValueSafeInPlace<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
ReadUnmanagedSafe(out int length);
|
||||
value.Length = length;
|
||||
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Optimized class used for writing values into a byte stream
|
||||
/// <seealso cref="FastBufferReader"/>
|
||||
/// <seealso cref="BytePacker"/>
|
||||
/// <seealso cref="ByteUnpacker"/>
|
||||
/// </summary>
|
||||
public struct FastBufferWriter : IDisposable
|
||||
{
|
||||
internal struct WriterHandle
|
||||
@@ -108,7 +114,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated buffer
|
||||
/// <see cref="IDisposable"/> implementation that frees the allocated buffer
|
||||
/// </summary>
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
@@ -267,7 +273,8 @@ namespace Unity.Netcode
|
||||
/// operations in release builds. Instead, attempting to write past the marked position in release builds
|
||||
/// will write to random memory and cause undefined behavior, likely including instability and crashes.
|
||||
/// </summary>
|
||||
/// <param name="value">The value you want to write</param>
|
||||
/// <typeparam name="T">The value type to write</typeparam>
|
||||
/// <param name="value">The value of the type `T` you want to write</param>
|
||||
/// <returns>True if the write is allowed, false otherwise</returns>
|
||||
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -716,15 +723,30 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the size required to write an unmanaged value
|
||||
/// Get the write size for any general unmanaged value
|
||||
/// The ForStructs value here makes this the lowest-priority overload so other versions
|
||||
/// will be prioritized over this if they match
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="unused"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetWriteSize<T>(in T value, ForStructs unused = default) where T : unmanaged
|
||||
{
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the write size for a FixedString
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetWriteSize<T>(in T value) where T : unmanaged
|
||||
public static int GetWriteSize<T>(in T value)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
return sizeof(T);
|
||||
return value.Length + sizeof(int);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -738,7 +760,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanaged<T>(in T value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanaged<T>(in T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -747,7 +769,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanagedSafe<T>(in T value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanagedSafe<T>(in T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
@@ -757,7 +779,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
|
||||
{
|
||||
WriteUnmanaged(value.Length);
|
||||
fixed (T* ptr = value)
|
||||
@@ -767,7 +789,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void WriteUnmanagedSafe<T>(T[] value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanagedSafe<T>(T[] value) where T : unmanaged
|
||||
{
|
||||
WriteUnmanagedSafe(value.Length);
|
||||
fixed (T* ptr = value)
|
||||
@@ -777,130 +799,641 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// These structs enable overloading of WriteValue with different generic constraints.
|
||||
// The compiler's actually able to distinguish between overloads based on generic constraints.
|
||||
// But at the bytecode level, the constraints aren't included in the method signature.
|
||||
// By adding a second parameter with a defaulted value, the signatures of each generic are different,
|
||||
// thus allowing overloads of methods based on the first parameter meeting constraints.
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForPrimitives
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForEnums
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForStructs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForNetworkSerializable
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForFixedStrings
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable value
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value array (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum value
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector2[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector3 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2Int value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector3Int value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int array
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector4 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector4[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Quaternion value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Quaternion[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Color value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Color[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Color32 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Color32[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Ray value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Ray2D value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray2D[] value) => WriteUnmanaged(value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Collor array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
|
||||
/// <summary>
|
||||
/// Write a FixedString value. Writes only the part of the string that's actually used.
|
||||
/// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling
|
||||
/// FastBufferWriter.GetWriteSize())
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValue<T>(in T value, ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
WriteUnmanaged(value.Length);
|
||||
// This avoids a copy on the string, which could be costly for FixedString4096Bytes
|
||||
// Otherwise, GetUnsafePtr() is an impure function call and will result in a copy
|
||||
// for `in` parameters.
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
WriteBytes(ptr->GetUnsafePtr(), value.Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write a FixedString value. Writes only the part of the string that's actually used.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
if (!TryBeginWriteInternal(sizeof(int) + value.Length))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
WriteValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,58 @@ namespace Unity.Netcode
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public struct ForceNetworkSerializeByMemcpy<T> : INetworkSerializeByMemcpy, IEquatable<ForceNetworkSerializeByMemcpy<T>> where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The wrapped value
|
||||
/// </summary>
|
||||
public T Value;
|
||||
|
||||
/// <summary>
|
||||
/// The default constructor for <see cref="ForceNetworkSerializeByMemcpy{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="value">sets the initial value of type `T`</param>
|
||||
public ForceNetworkSerializeByMemcpy(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert implicitly from the ForceNetworkSerializeByMemcpy wrapper to the underlying value
|
||||
/// </summary>
|
||||
/// <param name="container">The wrapper</param>
|
||||
/// <returns>The underlying value</returns>
|
||||
public static implicit operator T(ForceNetworkSerializeByMemcpy<T> container) => container.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Convert implicitly from a T value to a ForceNetworkSerializeByMemcpy wrapper
|
||||
/// </summary>
|
||||
/// <param name="underlyingValue">the value</param>
|
||||
/// <returns>a new wrapper</returns>
|
||||
public static implicit operator ForceNetworkSerializeByMemcpy<T>(T underlyingValue) => new ForceNetworkSerializeByMemcpy<T> { Value = underlyingValue };
|
||||
|
||||
/// <summary>
|
||||
/// Check if wrapped values are equal
|
||||
/// </summary>
|
||||
/// <param name="other">Other wrapper</param>
|
||||
/// <returns>true if equal</returns>
|
||||
public bool Equals(ForceNetworkSerializeByMemcpy<T> other)
|
||||
{
|
||||
return Value.Equals(other.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this value is equal to a boxed object value
|
||||
/// </summary>
|
||||
/// <param name="obj">The boxed value to check against</param>
|
||||
/// <returns>true if equal</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ForceNetworkSerializeByMemcpy<T> other && Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the wrapped value's hash code
|
||||
/// </summary>
|
||||
/// <returns>Wrapped value's hash code</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Unity.Netcode
|
||||
/// by memcpy. It's up to the developer of the struct to analyze the struct's contents and ensure it
|
||||
/// is actually serializable by memcpy. This requires all of the members of the struct to be
|
||||
/// `unmanaged` Plain-Old-Data values - if your struct contains a pointer (or a type that contains a pointer,
|
||||
/// like `NativeList<T>`), it should be serialized via `INetworkSerializable` or via
|
||||
/// like `NativeList<T>`), it should be serialized via `INetworkSerializable` or via
|
||||
/// `FastBufferReader`/`FastBufferWriter` extension methods.
|
||||
/// </summary>
|
||||
public interface INetworkSerializeByMemcpy
|
||||
|
||||
@@ -1,71 +1,539 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an implementation of one side of a two-way serializer
|
||||
/// </summary>
|
||||
public interface IReaderWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Check whether this implementation is a "reader" - if it's been constructed to deserialize data
|
||||
/// </summary>
|
||||
bool IsReader { get; }
|
||||
/// <summary>
|
||||
/// Check whether this implementation is a "writer" - if it's been constructed to serialize data
|
||||
/// </summary>
|
||||
bool IsWriter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the underlying FastBufferReader struct.
|
||||
/// Only valid when IsReader == true
|
||||
/// </summary>
|
||||
/// <returns>underlying FastBufferReader</returns>
|
||||
FastBufferReader GetFastBufferReader();
|
||||
/// <summary>
|
||||
/// Get the underlying FastBufferWriter struct.
|
||||
/// Only valid when IsWriter == true
|
||||
/// </summary>
|
||||
/// <returns>underlying FastBufferWriter</returns>
|
||||
FastBufferWriter GetFastBufferWriter();
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a string
|
||||
/// </summary>
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
void SerializeValue(ref string s, bool oneByteChars = false);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a single byte
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of primitive values (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an enum value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of enum values
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct value implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct values implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct or class value implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct or class values implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a FixedString value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector2 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector2[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector3 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector3[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector2Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector2Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector3Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector3Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector4 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector4 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector4 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector4[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Quaternion value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Quaternion value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Quaternion values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Quaternion[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Color value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Color[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color32 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Color32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color32 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Color32[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Ray value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Ray[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray2D value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Ray2D value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray2D values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Ray2D[] value);
|
||||
|
||||
// Has to have a different name to avoid conflicting with "where T: unmananged"
|
||||
/// <summary>
|
||||
/// Read or write a NetworkSerializable value.
|
||||
/// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
|
||||
|
||||
/// <summary>
|
||||
/// Performs an advance check to ensure space is available to read/write one or more values.
|
||||
/// This provides a performance benefit for serializing multiple values using the
|
||||
/// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be
|
||||
/// noticeable if serializing a very large number of items.
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <returns></returns>
|
||||
bool PreCheck(int amount);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a string, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a byte, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a primitive, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of primitives, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an enum, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of enums, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a struct, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of structs, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a FixedString, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector4 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4Array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector4[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Quaternion value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Quaternion[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color32[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray2D value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray2D[] value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Unity.Netcode
|
||||
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
|
||||
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
|
||||
{
|
||||
networkBehaviour = (T)GetInternal(this, null);
|
||||
networkBehaviour = GetInternal(this, null) as T;
|
||||
return networkBehaviour != null;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,18 @@ namespace Unity.Netcode
|
||||
serializer.SerializeValue(ref m_NetworkBehaviourId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkBehaviourReference"/> to <see cref="NetworkBehaviour"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkBehaviourRef">The <see cref="NetworkBehaviourReference"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkBehaviour"/> this class is holding a reference to</returns>
|
||||
public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkBehaviour"/> to <see cref="NetworkBehaviourReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkBehaviourReference"/> created from the <see cref="NetworkBehaviour"/> passed in as a parameter</returns>
|
||||
public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,12 +120,41 @@ namespace Unity.Netcode
|
||||
serializer.SerializeValue(ref m_NetworkObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkObjectReference"/> to <see cref="NetworkObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkObject"/> the <see cref="NetworkObjectReference"/> is referencing</returns>
|
||||
public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkObject"/> to <see cref="NetworkObjectReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkObject">The <see cref="NetworkObject"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkObjectReference"/> created from the <see cref="NetworkObject"/> parameter</returns>
|
||||
public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject);
|
||||
|
||||
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject;
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkObjectReference"/> to <see cref="GameObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
|
||||
/// <returns>This returns the <see cref="GameObject"/> that the <see cref="NetworkObject"/> is attached to and is referenced by the <see cref="NetworkObjectReference"/> passed in as a parameter</returns>
|
||||
public static implicit operator GameObject(NetworkObjectReference networkObjectRef)
|
||||
{
|
||||
var networkObject = Resolve(networkObjectRef);
|
||||
if (networkObject != null)
|
||||
{
|
||||
return networkObject.gameObject;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="GameObject"/> to <see cref="NetworkObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The <see cref="GameObject"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkObjectReference"/> created from the <see cref="GameObject"/> parameter that has a <see cref="NetworkObject"/> component attached to it</returns>
|
||||
public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,7 @@ namespace Unity.Netcode
|
||||
// Now we register all
|
||||
foreach (var gameObject in networkPrefabOverrides)
|
||||
{
|
||||
var targetNetworkObject = gameObject.GetComponent<NetworkObject>();
|
||||
if (targetNetworkObject != null)
|
||||
if (gameObject.TryGetComponent<NetworkObject>(out var targetNetworkObject))
|
||||
{
|
||||
if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash))
|
||||
{
|
||||
|
||||
@@ -126,6 +126,7 @@ namespace Unity.Netcode
|
||||
/// Returns a list of all NetworkObjects that belong to a client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param>
|
||||
/// <returns>returns the list of <see cref="NetworkObject"/>s owned by the client</returns>
|
||||
public List<NetworkObject> GetClientOwnedObjects(ulong clientId)
|
||||
{
|
||||
if (!OwnershipToObjectsTable.ContainsKey(clientId))
|
||||
@@ -172,9 +173,11 @@ namespace Unity.Netcode
|
||||
return GetPlayerNetworkObject(NetworkManager.LocalClientId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the player object with a given clientId or null if one does not exist. This is only valid server side.
|
||||
/// </summary>
|
||||
/// <param name="clientId">the client identifier of the player</param>
|
||||
/// <returns>The player object with a given clientId or null if one does not exist</returns>
|
||||
public NetworkObject GetPlayerNetworkObject(ulong clientId)
|
||||
{
|
||||
@@ -210,11 +213,11 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
networkObject.OwnerClientId = NetworkManager.ServerClientId;
|
||||
|
||||
// Server removes the entry and takes over ownership before notifying
|
||||
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
|
||||
|
||||
networkObject.OwnerClientId = NetworkManager.ServerClientId;
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
@@ -275,23 +278,26 @@ namespace Unity.Netcode
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
OwnerClientId = networkObject.OwnerClientId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
||||
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
if (networkObject.IsNetworkVisibleTo(client.Value.ClientId))
|
||||
{
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, client.Value.ClientId);
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasPrefab(bool isSceneObject, uint globalObjectIdHash)
|
||||
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, out var networkPrefab))
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab))
|
||||
{
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
@@ -306,53 +312,38 @@ namespace Unity.Netcode
|
||||
|
||||
return false;
|
||||
}
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle);
|
||||
return networkObject != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should only run on the client
|
||||
/// Creates a local NetowrkObject to be spawned.
|
||||
/// </summary>
|
||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||
/// <remarks>
|
||||
/// For most cases this is client-side only, with the exception of when the server
|
||||
/// is spawning a player.
|
||||
/// </remarks>
|
||||
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
|
||||
{
|
||||
NetworkObject parentNetworkObject = null;
|
||||
NetworkObject networkObject = null;
|
||||
var globalObjectIdHash = sceneObject.Header.Hash;
|
||||
var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default;
|
||||
var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default;
|
||||
var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default;
|
||||
var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default;
|
||||
var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true;
|
||||
var isSpawnedByPrefabHandler = false;
|
||||
|
||||
if (parentNetworkId != null && !isReparented)
|
||||
{
|
||||
if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject))
|
||||
{
|
||||
parentNetworkObject = networkObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
|
||||
// If scene management is disabled or the NetworkObject was dynamically spawned
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
{
|
||||
// Let the handler spawn the NetworkObject
|
||||
var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity));
|
||||
|
||||
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation);
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
{
|
||||
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
||||
}
|
||||
|
||||
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||
}
|
||||
|
||||
return networkObject;
|
||||
isSpawnedByPrefabHandler = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -380,31 +371,18 @@ namespace Unity.Netcode
|
||||
{
|
||||
NetworkLog.LogError($"Failed to create object locally. [{nameof(globalObjectIdHash)}={globalObjectIdHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash
|
||||
var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent<NetworkObject>();
|
||||
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
else
|
||||
{
|
||||
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
||||
// Create prefab instance
|
||||
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
}
|
||||
|
||||
if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||
}
|
||||
|
||||
return networkObject;
|
||||
}
|
||||
}
|
||||
else
|
||||
else // Get the in-scene placed NetworkObject
|
||||
{
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
||||
networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, sceneObject.NetworkSceneHandle);
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -412,17 +390,78 @@ namespace Unity.Netcode
|
||||
{
|
||||
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (parentNetworkObject != null)
|
||||
// Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
|
||||
// NetworkBehaviours will have their OnNetworkSpawn method invoked
|
||||
if (networkObject != null && !networkObject.gameObject.activeInHierarchy)
|
||||
{
|
||||
networkObject.transform.SetParent(parentNetworkObject.transform, true);
|
||||
networkObject.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (networkObject != null)
|
||||
{
|
||||
// SPECIAL CASE:
|
||||
// This is a special case scenario where a late joining client has joined and loaded one or
|
||||
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
|
||||
// synchronization information does not indicate the NetworkObject in question has a parent.
|
||||
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
|
||||
if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null)
|
||||
{
|
||||
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
|
||||
// include parenting, then we need to force the removal of that parent
|
||||
if (networkObject.transform.parent.GetComponent<NetworkObject>() != null)
|
||||
{
|
||||
// remove the parent
|
||||
networkObject.ApplyNetworkParenting(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return networkObject;
|
||||
// Set the transform unless we were spawned by a prefab handler
|
||||
// Note: prefab handlers are provided the position and rotation
|
||||
// but it is up to the user to set those values
|
||||
if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler)
|
||||
{
|
||||
if (worldPositionStays)
|
||||
{
|
||||
networkObject.transform.position = position;
|
||||
networkObject.transform.rotation = rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject.transform.localPosition = position;
|
||||
networkObject.transform.localRotation = rotation;
|
||||
}
|
||||
|
||||
// SPECIAL CASE:
|
||||
// Since players are created uniquely we don't apply scale because
|
||||
// the ConnectionApprovalResponse does not currently provide the
|
||||
// ability to specify scale. So, we just use the default scale of
|
||||
// the network prefab used to represent the player.
|
||||
// Note: not doing this would set the player's scale to zero since
|
||||
// that is the default value of Vector3.
|
||||
if (!sceneObject.Header.IsPlayerObject)
|
||||
{
|
||||
networkObject.transform.localScale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
if (sceneObject.Header.HasParent)
|
||||
{
|
||||
// Go ahead and set network parenting properties
|
||||
networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays);
|
||||
}
|
||||
|
||||
|
||||
// Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL
|
||||
// until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
|
||||
if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
|
||||
{
|
||||
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||
}
|
||||
}
|
||||
return networkObject;
|
||||
}
|
||||
|
||||
// Ran on both server and client
|
||||
@@ -477,15 +516,23 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// this initialization really should be at the bottom of the function
|
||||
networkObject.IsSpawned = true;
|
||||
|
||||
// this initialization really should be at the top of this function. If and when we break the
|
||||
// NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
|
||||
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there;
|
||||
// the current design banks on getting the network behaviour set and then only reading from it after the
|
||||
// below initialization code. However cowardice compels me to hold off on moving this until that commit
|
||||
networkObject.IsSceneObject = sceneObject;
|
||||
|
||||
// Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects
|
||||
// Note: Always check SceneOriginHandle directly at this specific location.
|
||||
if (networkObject.IsSceneObject != false && networkObject.SceneOriginHandle == 0)
|
||||
{
|
||||
networkObject.SceneOrigin = networkObject.gameObject.scene;
|
||||
}
|
||||
|
||||
// For integration testing, this makes sure that the appropriate NetworkManager is assigned to
|
||||
// the NetworkObject since it uses the NetworkManager.Singleton when not set
|
||||
if (networkObject.NetworkManagerOwner != NetworkManager)
|
||||
{
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
}
|
||||
|
||||
networkObject.NetworkObjectId = networkId;
|
||||
|
||||
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
||||
@@ -534,7 +581,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
networkObject.SetCachedParent(networkObject.transform.parent);
|
||||
networkObject.ApplyNetworkParenting();
|
||||
NetworkObject.CheckOrphanChildren();
|
||||
|
||||
@@ -552,9 +598,8 @@ namespace Unity.Netcode
|
||||
|
||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||
{
|
||||
//Currently, if this is called and the clientId (destination) is the server's client Id, this case will be checked
|
||||
// within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer placing this check here. [NSS]
|
||||
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
||||
// If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message.
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -565,8 +610,6 @@ namespace Unity.Netcode
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
||||
|
||||
networkObject.MarkVariablesDirty();
|
||||
}
|
||||
|
||||
internal ulong? GetSpawnParentId(NetworkObject networkObject)
|
||||
@@ -735,15 +778,26 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// If we are shutting down the NetworkManager, then ignore resetting the parent
|
||||
if (!NetworkManager.ShutdownInProgress)
|
||||
// and only attempt to remove the child's parent on the server-side
|
||||
if (!NetworkManager.ShutdownInProgress && NetworkManager.IsServer)
|
||||
{
|
||||
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
|
||||
foreach (var spawnedNetObj in SpawnedObjectsList)
|
||||
{
|
||||
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
|
||||
if (isReparented && latestParent == networkObject.NetworkObjectId)
|
||||
var latestParent = spawnedNetObj.GetNetworkParenting();
|
||||
if (latestParent.HasValue && latestParent.Value == networkObject.NetworkObjectId)
|
||||
{
|
||||
spawnedNetObj.gameObject.transform.parent = null;
|
||||
// Try to remove the parent using the cached WorldPositioNStays value
|
||||
// Note: WorldPositionStays will still default to true if this was an
|
||||
// in-scene placed NetworkObject and parenting was predefined in the
|
||||
// scene via the editor.
|
||||
if (!spawnedNetObj.TryRemoveParentCachedWorldPositionStays())
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed");
|
||||
}
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -785,7 +839,8 @@ namespace Unity.Netcode
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||
foreach (var targetClientId in m_TargetClientIds)
|
||||
@@ -803,6 +858,9 @@ namespace Unity.Netcode
|
||||
SpawnedObjectsList.Remove(networkObject);
|
||||
}
|
||||
|
||||
// Always clear out the observers list when despawned
|
||||
networkObject.Observers.Clear();
|
||||
|
||||
var gobj = networkObject.gameObject;
|
||||
if (destroyGameObject && gobj != null)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,10 @@ using Unity.Profiling;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides discretized time.
|
||||
/// This is useful for games that require ticks happening at regular interval on the server and clients.
|
||||
/// </summary>
|
||||
public class NetworkTickSystem
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
@@ -69,6 +73,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Called after advancing the time system to run ticks based on the difference in time.
|
||||
/// </summary>
|
||||
/// <param name="localTimeSec">The local time in seconds</param>
|
||||
/// <param name="serverTimeSec">The server time in seconds</param>
|
||||
public void UpdateTick(double localTimeSec, double serverTimeSec)
|
||||
{
|
||||
// store old local tick to know how many fixed ticks passed
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Unity.Netcode
|
||||
public double TickOffset => m_CachedTickOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>
|
||||
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>.
|
||||
/// </summary>
|
||||
public double Time => m_TimeSec;
|
||||
|
||||
@@ -35,13 +35,13 @@ namespace Unity.Netcode
|
||||
public float TimeAsFloat => (float)m_TimeSec;
|
||||
|
||||
/// <summary>
|
||||
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedTime"/>
|
||||
/// Gets he current fixed network time. This is the time value of the last network tick. Similar to <see cref="Time.fixedUnscaledTime"/>.
|
||||
/// </summary>
|
||||
public double FixedTime => m_CachedTick * m_TickInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fixed delta time. This value is based on the <see cref="TickRate"/> and stays constant.
|
||||
/// Similar to <see cref="Time.fixedDeltaTime"/> There is no equivalent to <see cref="Time.deltaTime"/>
|
||||
/// Similar to <see cref="Time.fixedUnscaledTime"/> There is no equivalent to <see cref="Time.deltaTime"/>.
|
||||
/// </summary>
|
||||
public float FixedDeltaTime => (float)m_TickInterval;
|
||||
|
||||
@@ -108,6 +108,11 @@ namespace Unity.Netcode
|
||||
return new NetworkTime(m_TickRate, m_CachedTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the time a number of ticks in the past.
|
||||
/// </summary>
|
||||
/// <param name="ticks">The number of ticks ago we're querying the time</param>
|
||||
/// <returns></returns>
|
||||
public NetworkTime TimeTicksAgo(int ticks)
|
||||
{
|
||||
return this - new NetworkTime(TickRate, ticks);
|
||||
@@ -132,16 +137,34 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the time difference between two ticks
|
||||
/// </summary>
|
||||
/// <param name="a">End time</param>
|
||||
/// <param name="b">Start time</param>
|
||||
/// <returns>The time difference between start and end</returns>
|
||||
public static NetworkTime operator -(NetworkTime a, NetworkTime b)
|
||||
{
|
||||
return new NetworkTime(a.TickRate, a.Time - b.Time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the sum of two times
|
||||
/// </summary>
|
||||
/// <param name="a">First time</param>
|
||||
/// <param name="b">Second time</param>
|
||||
/// <returns>The sum of the two times passed in</returns>
|
||||
public static NetworkTime operator +(NetworkTime a, NetworkTime b)
|
||||
{
|
||||
return new NetworkTime(a.TickRate, a.Time + b.Time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the time a number of seconds later
|
||||
/// </summary>
|
||||
/// <param name="a">The start time</param>
|
||||
/// <param name="b">The number of seconds to add</param>
|
||||
/// <returns>The resulting time</returns>
|
||||
public static NetworkTime operator +(NetworkTime a, double b)
|
||||
{
|
||||
a.m_TimeSec += b;
|
||||
@@ -149,6 +172,12 @@ namespace Unity.Netcode
|
||||
return a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the time a number of seconds before
|
||||
/// </summary>
|
||||
/// <param name="a">The start time</param>
|
||||
/// <param name="b">The number of seconds to remove</param>
|
||||
/// <returns>The resulting time</returns>
|
||||
public static NetworkTime operator -(NetworkTime a, double b)
|
||||
{
|
||||
return a + -b;
|
||||
|
||||
@@ -36,12 +36,27 @@ namespace Unity.Netcode
|
||||
/// Gets or sets the ratio at which the NetworkTimeSystem speeds up or slows down time.
|
||||
/// </summary>
|
||||
public double AdjustmentRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current local time with the local time offset applied
|
||||
/// </summary>
|
||||
public double LocalTime => m_TimeSec + m_CurrentLocalTimeOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The current server time with the server time offset applied
|
||||
/// </summary>
|
||||
public double ServerTime => m_TimeSec + m_CurrentServerTimeOffset;
|
||||
|
||||
internal double LastSyncedServerTimeSec { get; private set; }
|
||||
internal double LastSyncedRttSec { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The constructor class for <see cref="NetworkTickSystem"/>
|
||||
/// </summary>
|
||||
/// <param name="localBufferSec">The amount of time, in seconds, the server should buffer incoming client messages.</param>
|
||||
/// <param name="serverBufferSec">The amount of the time in seconds the client should buffer incoming messages from the server.</param>
|
||||
/// <param name="hardResetThresholdSec">The threshold, in seconds, used to force a hard catchup of network time.</param>
|
||||
/// <param name="adjustmentRatio">The ratio at which the NetworkTimeSystem speeds up or slows down time.</param>
|
||||
public NetworkTimeSystem(double localBufferSec, double serverBufferSec, double hardResetThresholdSec, double adjustmentRatio = 0.01d)
|
||||
{
|
||||
LocalBufferSec = localBufferSec;
|
||||
@@ -61,7 +76,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.deltaTime or similar.
|
||||
/// Advances the time system by a certain amount of time. Should be called once per frame with Time.unscaledDeltaTime or similar.
|
||||
/// </summary>
|
||||
/// <param name="deltaTimeSec">The amount of time to advance. The delta time which passed since Advance was last called.</param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
Disconnect,
|
||||
|
||||
/// <summary>
|
||||
/// Transport has encountered an unrecoverable failure
|
||||
/// </summary>
|
||||
TransportFailure,
|
||||
|
||||
/// <summary>
|
||||
/// No new event
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,11 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// The generic transport class all Netcode for GameObjects network transport implementations
|
||||
/// derive from. Use this class to add a custom transport.
|
||||
/// <seealso cref="Transports.UTP.UnityTransport"> for an example of how a transport is integrated</seealso>
|
||||
/// </summary>
|
||||
public abstract class NetworkTransport : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
@@ -45,7 +50,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a payload to the specified clientId, data and channelName.
|
||||
/// Send a payload to the specified clientId, data and networkDelivery.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to send to</param>
|
||||
/// <param name="payload">The data to send</param>
|
||||
@@ -64,11 +69,13 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Connects client to the server
|
||||
/// </summary>
|
||||
/// <returns>Returns success or failure</returns>
|
||||
public abstract bool StartClient();
|
||||
|
||||
/// <summary>
|
||||
/// Starts to listening for incoming clients
|
||||
/// </summary>
|
||||
/// <returns>Returns success or failure</returns>
|
||||
public abstract bool StartServer();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using UnityEngine.Networking;
|
||||
|
||||
namespace Unity.Netcode.Transports.UNET
|
||||
{
|
||||
[AddComponentMenu("Netcode/UNet Transport")]
|
||||
public class UNetTransport : NetworkTransport
|
||||
{
|
||||
public enum SendMode
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using Unity.Networking.Transport;
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
#endif
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
@@ -25,7 +29,11 @@ namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
fixed (byte* dataPtr = m_Data)
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
reader.ReadBytesUnsafe(dataPtr, reader.Length);
|
||||
#else
|
||||
reader.ReadBytes(dataPtr, reader.Length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +70,11 @@ namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
fixed (byte* dataPtr = m_Data)
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
reader.ReadBytesUnsafe(dataPtr + m_Offset + m_Length, reader.Length);
|
||||
#else
|
||||
reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,30 @@ namespace Unity.Netcode.Transports.UTP
|
||||
/// <summary>Queue for batched messages meant to be sent through UTP.</summary>
|
||||
/// <remarks>
|
||||
/// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched
|
||||
/// messages, call <see cref="FillWriter"> with the <see cref="DataStreamWriter"/> obtained from
|
||||
/// <see cref="NetworkDriver.BeginSend"/>. This will fill the writer with as many messages as
|
||||
/// possible. If the send is successful, call <see cref="Consume"/> to remove the data from the
|
||||
/// queue.
|
||||
/// messages, call <see cref="FillWriterWithMessages"/> or <see cref="FillWriterWithBytes"/>
|
||||
/// with the <see cref="DataStreamWriter"/> obtained from <see cref="NetworkDriver.BeginSend"/>.
|
||||
/// This will fill the writer with as many messages/bytes as possible. If the send is
|
||||
/// successful, call <see cref="Consume"/> to remove the data from the queue.
|
||||
///
|
||||
/// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to
|
||||
/// read messages sent with this queue.
|
||||
/// </remarks>
|
||||
internal struct BatchedSendQueue : IDisposable
|
||||
{
|
||||
private NativeArray<byte> m_Data;
|
||||
// Note that we're using NativeList basically like a growable NativeArray, where the length
|
||||
// of the list is the capacity of our array. (We can't use the capacity of the list as our
|
||||
// queue capacity because NativeList may elect to set it higher than what we'd set it to
|
||||
// with SetCapacity, which breaks the logic of our code.)
|
||||
private NativeList<byte> m_Data;
|
||||
private NativeArray<int> m_HeadTailIndices;
|
||||
private int m_MaximumCapacity;
|
||||
private int m_MinimumCapacity;
|
||||
|
||||
/// <summary>Overhead that is added to each message in the queue.</summary>
|
||||
public const int PerMessageOverhead = sizeof(int);
|
||||
|
||||
internal const int MinimumMinimumCapacity = 4096;
|
||||
|
||||
// Indices into m_HeadTailIndicies.
|
||||
private const int k_HeadInternalIndex = 0;
|
||||
private const int k_TailInternalIndex = 1;
|
||||
@@ -43,18 +51,33 @@ namespace Unity.Netcode.Transports.UTP
|
||||
}
|
||||
|
||||
public int Length => TailIndex - HeadIndex;
|
||||
|
||||
public int Capacity => m_Data.Length;
|
||||
public bool IsEmpty => HeadIndex == TailIndex;
|
||||
|
||||
public bool IsCreated => m_Data.IsCreated;
|
||||
|
||||
/// <summary>Construct a new empty send queue.</summary>
|
||||
/// <param name="capacity">Maximum capacity of the send queue.</param>
|
||||
public BatchedSendQueue(int capacity)
|
||||
{
|
||||
m_Data = new NativeArray<byte>(capacity, Allocator.Persistent);
|
||||
// Make sure the maximum capacity will be even.
|
||||
m_MaximumCapacity = capacity + (capacity & 1);
|
||||
|
||||
// We pick the minimum capacity such that if we keep doubling it, we'll eventually hit
|
||||
// the maximum capacity exactly. The alternative would be to use capacities that are
|
||||
// powers of 2, but this can lead to over-allocating quite a bit of memory (especially
|
||||
// since we expect maximum capacities to be in the megabytes range). The approach taken
|
||||
// here avoids this issue, at the cost of not having allocations of nice round sizes.
|
||||
m_MinimumCapacity = m_MaximumCapacity;
|
||||
while (m_MinimumCapacity / 2 >= MinimumMinimumCapacity)
|
||||
{
|
||||
m_MinimumCapacity /= 2;
|
||||
}
|
||||
|
||||
m_Data = new NativeList<byte>(m_MinimumCapacity, Allocator.Persistent);
|
||||
m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent);
|
||||
|
||||
m_Data.ResizeUninitialized(m_MinimumCapacity);
|
||||
|
||||
HeadIndex = 0;
|
||||
TailIndex = 0;
|
||||
}
|
||||
@@ -68,18 +91,28 @@ namespace Unity.Netcode.Transports.UTP
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Write a raw buffer to a DataStreamWriter.</summary>
|
||||
private unsafe void WriteBytes(ref DataStreamWriter writer, byte* data, int length)
|
||||
{
|
||||
#if UTP_TRANSPORT_2_0_ABOVE
|
||||
writer.WriteBytesUnsafe(data, length);
|
||||
#else
|
||||
writer.WriteBytes(data, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Append data at the tail of the queue. No safety checks.</summary>
|
||||
private void AppendDataAtTail(ArraySegment<byte> data)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex);
|
||||
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, Capacity - TailIndex);
|
||||
|
||||
writer.WriteInt(data.Count);
|
||||
|
||||
fixed (byte* dataPtr = data.Array)
|
||||
{
|
||||
writer.WriteBytes(dataPtr + data.Offset, data.Count);
|
||||
WriteBytes(ref writer, dataPtr + data.Offset, data.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,16 +133,16 @@ namespace Unity.Netcode.Transports.UTP
|
||||
}
|
||||
|
||||
// Check if there's enough room after the current tail index.
|
||||
if (m_Data.Length - TailIndex >= sizeof(int) + message.Count)
|
||||
if (Capacity - TailIndex >= sizeof(int) + message.Count)
|
||||
{
|
||||
AppendDataAtTail(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if there would be enough room if we moved data at the beginning of m_Data.
|
||||
if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count)
|
||||
// Move the data at the beginning of of m_Data. Either it will leave enough space for
|
||||
// the message, or we'll grow m_Data and will want the data at the beginning anyway.
|
||||
if (HeadIndex > 0 && Length > 0)
|
||||
{
|
||||
// Move the data back at the beginning of m_Data.
|
||||
unsafe
|
||||
{
|
||||
UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
|
||||
@@ -117,12 +150,38 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
TailIndex = Length;
|
||||
HeadIndex = 0;
|
||||
}
|
||||
|
||||
// If there's enough space left at the end for the message, now is a good time to trim
|
||||
// the capacity of m_Data if it got very large. We define "very large" here as having
|
||||
// more than 75% of m_Data unused after adding the new message.
|
||||
if (Capacity - TailIndex >= sizeof(int) + message.Count)
|
||||
{
|
||||
AppendDataAtTail(message);
|
||||
|
||||
while (TailIndex < Capacity / 4 && Capacity > m_MinimumCapacity)
|
||||
{
|
||||
m_Data.ResizeUninitialized(Capacity / 2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// If we get here we need to grow m_Data until the data fits (or it's too large).
|
||||
while (Capacity - TailIndex < sizeof(int) + message.Count)
|
||||
{
|
||||
// Can't grow m_Data anymore. Message simply won't fit.
|
||||
if (Capacity * 2 > m_MaximumCapacity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Data.ResizeUninitialized(Capacity * 2);
|
||||
}
|
||||
|
||||
// If we get here we know there's now enough room for the message.
|
||||
AppendDataAtTail(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -149,12 +208,12 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
unsafe
|
||||
{
|
||||
var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
|
||||
var reader = new DataStreamReader(m_Data.AsArray());
|
||||
|
||||
var writerAvailable = writer.Capacity;
|
||||
var readerOffset = 0;
|
||||
var readerOffset = HeadIndex;
|
||||
|
||||
while (readerOffset < Length)
|
||||
while (readerOffset < TailIndex)
|
||||
{
|
||||
reader.SeekSet(readerOffset);
|
||||
var messageLength = reader.ReadInt();
|
||||
@@ -168,7 +227,7 @@ namespace Unity.Netcode.Transports.UTP
|
||||
writer.WriteInt(messageLength);
|
||||
|
||||
var messageOffset = HeadIndex + reader.GetBytesRead();
|
||||
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
|
||||
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
|
||||
|
||||
writerAvailable -= sizeof(int) + messageLength;
|
||||
readerOffset += sizeof(int) + messageLength;
|
||||
@@ -205,7 +264,7 @@ namespace Unity.Netcode.Transports.UTP
|
||||
|
||||
unsafe
|
||||
{
|
||||
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
|
||||
WriteBytes(ref writer, (byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
|
||||
}
|
||||
|
||||
return copyLength;
|
||||
@@ -219,10 +278,14 @@ namespace Unity.Netcode.Transports.UTP
|
||||
/// <param name="size">Number of bytes to consume from the queue.</param>
|
||||
public void Consume(int size)
|
||||
{
|
||||
// Adjust the head/tail indices such that we consume the given size.
|
||||
if (size >= Length)
|
||||
{
|
||||
HeadIndex = 0;
|
||||
TailIndex = 0;
|
||||
|
||||
// This is a no-op if m_Data is already at minimum capacity.
|
||||
m_Data.ResizeUninitialized(m_MinimumCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
/// <summary>
|
||||
/// Caching structure to track network metrics related information.
|
||||
/// </summary>
|
||||
public struct NetworkMetricsContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of packet sent.
|
||||
/// </summary>
|
||||
public uint PacketSentCount;
|
||||
/// <summary>
|
||||
/// The number of packet received.
|
||||
/// </summary>
|
||||
public uint PacketReceivedCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,24 @@ using AOT;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Networking.Transport;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
[BurstCompile]
|
||||
internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage
|
||||
{
|
||||
static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
|
||||
static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
|
||||
static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
|
||||
private static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> s_ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
|
||||
private static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> s_SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
|
||||
private static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> s_InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
|
||||
|
||||
public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer,
|
||||
int staticInstanceBufferLength,
|
||||
NetworkSettings settings)
|
||||
{
|
||||
return new NetworkPipelineStage(
|
||||
ReceiveFunction,
|
||||
SendFunction,
|
||||
InitializeConnectionFunction,
|
||||
s_ReceiveFunction,
|
||||
s_SendFunction,
|
||||
s_InitializeConnectionFunction,
|
||||
ReceiveCapacity: 0,
|
||||
SendCapacity: 0,
|
||||
HeaderCapacity: 0,
|
||||
|
||||
188
Runtime/Transports/UTP/SecretsLoaderHelper.cs
Normal file
188
Runtime/Transports/UTP/SecretsLoaderHelper.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
/// <summary>
|
||||
/// Component to add to a NetworkManager if you want the certificates to be loaded from files.
|
||||
/// Mostly helpful to ease development and testing, especially with self-signed certificates
|
||||
///
|
||||
/// Shipping code should make the calls to
|
||||
/// - SetServerSecrets
|
||||
/// - SetClientSecrets
|
||||
/// directly, instead of relying on this.
|
||||
/// </summary>
|
||||
public class SecretsLoaderHelper : MonoBehaviour
|
||||
{
|
||||
internal struct ServerSecrets
|
||||
{
|
||||
public string ServerPrivate;
|
||||
public string ServerCertificate;
|
||||
};
|
||||
|
||||
internal struct ClientSecrets
|
||||
{
|
||||
public string ServerCommonName;
|
||||
public string ClientCertificate;
|
||||
};
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var serverSecrets = new ServerSecrets();
|
||||
|
||||
try
|
||||
{
|
||||
serverSecrets.ServerCertificate = ServerCertificate;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
serverSecrets.ServerPrivate = ServerPrivate;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
var clientSecrets = new ClientSecrets();
|
||||
try
|
||||
{
|
||||
clientSecrets.ClientCertificate = ClientCA;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clientSecrets.ServerCommonName = ServerCommonName;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Log(exception);
|
||||
}
|
||||
|
||||
var unityTransportComponent = GetComponent<UnityTransport>();
|
||||
|
||||
if (unityTransportComponent == null)
|
||||
{
|
||||
Debug.LogError($"You need to select the UnityTransport protocol, in the NetworkManager, in order for the SecretsLoaderHelper component to be useful.");
|
||||
return;
|
||||
}
|
||||
|
||||
unityTransportComponent.SetServerSecrets(serverSecrets.ServerCertificate, serverSecrets.ServerPrivate);
|
||||
unityTransportComponent.SetClientSecrets(clientSecrets.ServerCommonName, clientSecrets.ClientCertificate);
|
||||
}
|
||||
|
||||
[Tooltip("Hostname")]
|
||||
[SerializeField]
|
||||
private string m_ServerCommonName = "localhost";
|
||||
|
||||
/// <summary>Common name of the server (typically its hostname).</summary>
|
||||
public string ServerCommonName
|
||||
{
|
||||
get => m_ServerCommonName;
|
||||
set => m_ServerCommonName = value;
|
||||
}
|
||||
|
||||
[Tooltip("Client CA filepath. Useful with self-signed certificates")]
|
||||
[SerializeField]
|
||||
private string m_ClientCAFilePath = ""; // "Assets/Secure/myGameClientCA.pem"
|
||||
|
||||
/// <summary>Client CA filepath. Useful with self-signed certificates</summary>
|
||||
public string ClientCAFilePath
|
||||
{
|
||||
get => m_ClientCAFilePath;
|
||||
set => m_ClientCAFilePath = value;
|
||||
}
|
||||
|
||||
[Tooltip("Client CA Override. Only useful for development with self-signed certificates. Certificate content, for platforms that lack file access (WebGL)")]
|
||||
[SerializeField]
|
||||
private string m_ClientCAOverride = "";
|
||||
|
||||
/// <summary>
|
||||
/// Client CA Override. Only useful for development with self-signed certificates.
|
||||
/// Certificate content, for platforms that lack file access (WebGL)
|
||||
/// </summary>
|
||||
public string ClientCAOverride
|
||||
{
|
||||
get => m_ClientCAOverride;
|
||||
set => m_ClientCAOverride = value;
|
||||
}
|
||||
|
||||
[Tooltip("Server Certificate filepath")]
|
||||
[SerializeField]
|
||||
private string m_ServerCertificateFilePath = ""; // "Assets/Secure/myGameServerCertificate.pem"
|
||||
|
||||
/// <summary>Server Certificate filepath</summary>
|
||||
public string ServerCertificateFilePath
|
||||
{
|
||||
get => m_ServerCertificateFilePath;
|
||||
set => m_ServerCertificateFilePath = value;
|
||||
}
|
||||
|
||||
[Tooltip("Server Private Key filepath")]
|
||||
[SerializeField]
|
||||
private string m_ServerPrivateFilePath = ""; // "Assets/Secure/myGameServerPrivate.pem"
|
||||
|
||||
/// <summary>Server Private Key filepath</summary>
|
||||
public string ServerPrivateFilePath
|
||||
{
|
||||
get => m_ServerPrivateFilePath;
|
||||
set => m_ServerPrivate = value;
|
||||
}
|
||||
|
||||
private string m_ClientCA;
|
||||
|
||||
/// <summary>CA certificate used by the client.</summary>
|
||||
public string ClientCA
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ClientCAOverride != "")
|
||||
{
|
||||
return m_ClientCAOverride;
|
||||
}
|
||||
return ReadFile(m_ClientCAFilePath, "Client Certificate");
|
||||
}
|
||||
set => m_ClientCA = value;
|
||||
}
|
||||
|
||||
private string m_ServerCertificate;
|
||||
|
||||
/// <summary>Certificate used by the server.</summary>
|
||||
public string ServerCertificate
|
||||
{
|
||||
get => ReadFile(m_ServerCertificateFilePath, "Server Certificate");
|
||||
set => m_ServerCertificate = value;
|
||||
}
|
||||
|
||||
private string m_ServerPrivate;
|
||||
|
||||
/// <summary>Private key used by the server.</summary>
|
||||
public string ServerPrivate
|
||||
{
|
||||
get => ReadFile(m_ServerPrivateFilePath, "Server Key");
|
||||
set => m_ServerPrivate = value;
|
||||
}
|
||||
|
||||
private static string ReadFile(string path, string label)
|
||||
{
|
||||
if (path == null || path == "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var reader = new StreamReader(path);
|
||||
string fileContent = reader.ReadToEnd();
|
||||
Debug.Log((fileContent.Length > 1) ? ("Successfully loaded " + fileContent.Length + " byte(s) from " + label) : ("Could not read " + label + " file"));
|
||||
return fileContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e54b65208bd3bbe4eaf62ca0384ae21f
|
||||
guid: dc1e7a8dc597cf24c95e4acf92c0edf5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,11 @@
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
"expression": "1.0.0-pre.7",
|
||||
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.transport",
|
||||
"expression": "2.0.0-exp",
|
||||
"define": "UTP_TRANSPORT_2_0_ABOVE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
using System;
|
||||
namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
public class ObjectNameIdentifier : NetworkBehaviour
|
||||
@@ -7,36 +7,46 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
private ulong m_CurrentNetworkObjectId;
|
||||
private bool m_IsRegistered;
|
||||
|
||||
private const char k_TagInfoStart = '{';
|
||||
private const char k_TagInfoStop = '}';
|
||||
|
||||
/// <summary>
|
||||
/// Keep a reference to the assigned NetworkObject
|
||||
/// <see cref="OnDestroy"/>
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
private NetworkObject m_NetworkObject;
|
||||
private string m_OriginalName;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
RegisterAndLabelNetworkObject();
|
||||
}
|
||||
|
||||
|
||||
protected void RegisterAndLabelNetworkObject()
|
||||
{
|
||||
if (!m_IsRegistered)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_OriginalName))
|
||||
{
|
||||
m_OriginalName = gameObject.name.Replace("(Clone)", "");
|
||||
}
|
||||
// This is required otherwise it will try to continue to update the NetworkBehaviour even if
|
||||
// it has been destroyed.
|
||||
m_NetworkObject = NetworkObject;
|
||||
m_CurrentOwner = OwnerClientId;
|
||||
m_CurrentNetworkObjectId = NetworkObjectId;
|
||||
var objectOriginalName = gameObject.name.Replace("(Clone)", "");
|
||||
|
||||
var serverOrClient = IsServer ? "Server" : "Client";
|
||||
if (NetworkObject.IsPlayerObject)
|
||||
{
|
||||
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{objectOriginalName}({OwnerClientId})-Local{objectOriginalName}" :
|
||||
$"{objectOriginalName}({OwnerClientId})-On{serverOrClient}({NetworkManager.LocalClientId})";
|
||||
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{m_OriginalName}-{k_TagInfoStart}{OwnerClientId}{k_TagInfoStop}-Local{m_OriginalName}" :
|
||||
$"{m_OriginalName}-{k_TagInfoStart}{OwnerClientId}{k_TagInfoStop}- On{serverOrClient}{k_TagInfoStart}{NetworkManager.LocalClientId}{k_TagInfoStop}";
|
||||
}
|
||||
else
|
||||
{
|
||||
gameObject.name = $"{objectOriginalName}({NetworkObjectId})-On{serverOrClient}({NetworkManager.LocalClientId})";
|
||||
gameObject.name = $"{m_OriginalName}{k_TagInfoStart}{NetworkObjectId}{k_TagInfoStop}-On{serverOrClient}{k_TagInfoStart}{NetworkManager.LocalClientId}{k_TagInfoStop}";
|
||||
}
|
||||
|
||||
// Don't add the player objects to the global list of NetworkObjects
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
@@ -9,22 +10,51 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// The default SceneManagerHandler used for all NetcodeIntegrationTest derived children.
|
||||
/// This enables clients to load scenes within the same scene hierarchy during integration
|
||||
/// testing.
|
||||
/// </summary>
|
||||
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
|
||||
{
|
||||
internal CoroutineRunner CoroutineRunner;
|
||||
// All IntegrationTestSceneHandler instances register their associated NetworkManager
|
||||
internal static List<NetworkManager> NetworkManagers = new List<NetworkManager>();
|
||||
|
||||
// Default client simulated delay time
|
||||
protected const float k_ClientLoadingSimulatedDelay = 0.02f;
|
||||
internal static CoroutineRunner CoroutineRunner;
|
||||
|
||||
internal static Queue<QueuedSceneJob> QueuedSceneJobs = new Queue<QueuedSceneJob>();
|
||||
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
|
||||
internal static Coroutine SceneJobProcessor;
|
||||
internal static QueuedSceneJob CurrentQueuedSceneJob;
|
||||
protected static WaitForSeconds s_WaitForSeconds;
|
||||
|
||||
// Controls the client simulated delay time
|
||||
protected float m_ClientLoadingSimulatedDelay = k_ClientLoadingSimulatedDelay;
|
||||
|
||||
public delegate bool CanClientsLoadUnloadDelegateHandler();
|
||||
public event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
|
||||
public event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
|
||||
public static event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
|
||||
public static event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
|
||||
|
||||
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
|
||||
|
||||
public static bool VerboseDebugMode;
|
||||
/// <summary>
|
||||
/// Used for loading scenes on the client-side during
|
||||
/// an integration test
|
||||
/// </summary>
|
||||
internal class QueuedSceneJob
|
||||
{
|
||||
public enum JobTypes
|
||||
{
|
||||
Loading,
|
||||
Unloading,
|
||||
Completed
|
||||
}
|
||||
public JobTypes JobType;
|
||||
public string SceneName;
|
||||
public Scene Scene;
|
||||
public SceneEventProgress SceneEventProgress;
|
||||
public IntegrationTestSceneHandler IntegrationTestSceneHandler;
|
||||
}
|
||||
|
||||
internal NetworkManager NetworkManager;
|
||||
|
||||
internal string NetworkManagerName;
|
||||
|
||||
/// <summary>
|
||||
/// Used to control when clients should attempt to fake-load a scene
|
||||
@@ -44,19 +74,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fake-Loads a scene for a client
|
||||
/// </summary>
|
||||
internal IEnumerator ClientLoadSceneCoroutine(string sceneName, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
{
|
||||
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
|
||||
while (!OnCanClientsLoad())
|
||||
{
|
||||
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
|
||||
}
|
||||
sceneEventAction.Invoke();
|
||||
}
|
||||
|
||||
protected bool OnCanClientsUnload()
|
||||
{
|
||||
if (CanClientsUnload != null)
|
||||
@@ -66,35 +83,297 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fake-Unloads a scene for a client
|
||||
/// </summary>
|
||||
internal IEnumerator ClientUnloadSceneCoroutine(ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
|
||||
internal static void VerboseDebug(string message)
|
||||
{
|
||||
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
|
||||
while (!OnCanClientsUnload())
|
||||
if (VerboseDebugMode)
|
||||
{
|
||||
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
|
||||
Debug.Log(message);
|
||||
}
|
||||
sceneEventAction.Invoke();
|
||||
}
|
||||
|
||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
/// <summary>
|
||||
/// Processes scene loading jobs
|
||||
/// </summary>
|
||||
/// <param name="queuedSceneJob">job to process</param>
|
||||
static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
|
||||
{
|
||||
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientLoadSceneCoroutine(sceneName, sceneEventAction)));
|
||||
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
||||
while (!itegrationTestSceneHandler.OnCanClientsLoad())
|
||||
{
|
||||
yield return s_WaitForSeconds;
|
||||
}
|
||||
|
||||
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
|
||||
// We always load additively for all scenes during integration tests
|
||||
var asyncOperation = SceneManager.LoadSceneAsync(queuedSceneJob.SceneName, LoadSceneMode.Additive);
|
||||
queuedSceneJob.SceneEventProgress.SetAsyncOperation(asyncOperation);
|
||||
|
||||
// Wait for it to finish
|
||||
while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed)
|
||||
{
|
||||
yield return s_WaitForSeconds;
|
||||
}
|
||||
yield return s_WaitForSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles scene loading and assists with making sure the right NetworkManagerOwner
|
||||
/// is assigned to newly instantiated NetworkObjects.
|
||||
///
|
||||
/// Note: Static property usage is OK since jobs are processed one at a time
|
||||
/// </summary>
|
||||
private static void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
|
||||
{
|
||||
if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.SceneName == scene.name)
|
||||
{
|
||||
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||
|
||||
ProcessInSceneObjects(scene, CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManager);
|
||||
|
||||
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles some pre-spawn processing of in-scene placed NetworkObjects
|
||||
/// to make sure the appropriate NetworkManagerOwner is assigned. It
|
||||
/// also makes sure that each in-scene placed NetworkObject has an
|
||||
/// ObjectIdentifier component if one is not assigned to it or its
|
||||
/// children.
|
||||
/// </summary>
|
||||
/// <param name="scene">the scenes that was just loaded</param>
|
||||
/// <param name="networkManager">the relative NetworkManager</param>
|
||||
private static void ProcessInSceneObjects(Scene scene, NetworkManager networkManager)
|
||||
{
|
||||
// Get all in-scene placed NeworkObjects that were instantiated when this scene loaded
|
||||
var inSceneNetworkObjects = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != false && c.GetSceneOriginHandle() == scene.handle);
|
||||
foreach (var sobj in inSceneNetworkObjects)
|
||||
{
|
||||
if (sobj.NetworkManagerOwner != networkManager)
|
||||
{
|
||||
sobj.NetworkManagerOwner = networkManager;
|
||||
}
|
||||
if (sobj.GetComponent<ObjectNameIdentifier>() == null && sobj.GetComponentInChildren<ObjectNameIdentifier>() == null)
|
||||
{
|
||||
sobj.gameObject.AddComponent<ObjectNameIdentifier>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes scene unloading jobs
|
||||
/// </summary>
|
||||
/// <param name="queuedSceneJob">job to process</param>
|
||||
static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
|
||||
{
|
||||
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
||||
while (!itegrationTestSceneHandler.OnCanClientsUnload())
|
||||
{
|
||||
yield return s_WaitForSeconds;
|
||||
}
|
||||
|
||||
SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;
|
||||
if (queuedSceneJob.Scene.IsValid() && queuedSceneJob.Scene.isLoaded && !queuedSceneJob.Scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName))
|
||||
{
|
||||
var asyncOperation = SceneManager.UnloadSceneAsync(queuedSceneJob.Scene);
|
||||
queuedSceneJob.SceneEventProgress.SetAsyncOperation(asyncOperation);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
while (queuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed)
|
||||
{
|
||||
yield return s_WaitForSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles closing out scene unloading jobs
|
||||
/// </summary>
|
||||
private static void SceneManager_sceneUnloaded(Scene scene)
|
||||
{
|
||||
if (CurrentQueuedSceneJob.JobType != QueuedSceneJob.JobTypes.Completed && CurrentQueuedSceneJob.Scene.name == scene.name)
|
||||
{
|
||||
SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
|
||||
|
||||
CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all jobs within the queue.
|
||||
/// When all jobs are finished, the coroutine stops.
|
||||
/// </summary>
|
||||
static internal IEnumerator JobQueueProcessor()
|
||||
{
|
||||
while (QueuedSceneJobs.Count != 0)
|
||||
{
|
||||
CurrentQueuedSceneJob = QueuedSceneJobs.Dequeue();
|
||||
VerboseDebug($"[ITSH-START] {CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManagerName} processing {CurrentQueuedSceneJob.JobType} for scene {CurrentQueuedSceneJob.SceneName}.");
|
||||
if (CurrentQueuedSceneJob.JobType == QueuedSceneJob.JobTypes.Loading)
|
||||
{
|
||||
yield return ProcessLoadingSceneJob(CurrentQueuedSceneJob);
|
||||
}
|
||||
else if (CurrentQueuedSceneJob.JobType == QueuedSceneJob.JobTypes.Unloading)
|
||||
{
|
||||
yield return ProcessUnloadingSceneJob(CurrentQueuedSceneJob);
|
||||
}
|
||||
VerboseDebug($"[ITSH-STOP] {CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManagerName} processing {CurrentQueuedSceneJob.JobType} for scene {CurrentQueuedSceneJob.SceneName}.");
|
||||
}
|
||||
SceneJobProcessor = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a job to the job queue, and if the JobQueueProcessor coroutine
|
||||
/// is not running then it will be started as well.
|
||||
/// </summary>
|
||||
/// <param name="queuedSceneJob">job to add to the queue</param>
|
||||
private void AddJobToQueue(QueuedSceneJob queuedSceneJob)
|
||||
{
|
||||
QueuedSceneJobs.Enqueue(queuedSceneJob);
|
||||
if (SceneJobProcessor == null)
|
||||
{
|
||||
SceneJobProcessor = CoroutineRunner.StartCoroutine(JobQueueProcessor());
|
||||
}
|
||||
}
|
||||
|
||||
private string m_ServerSceneBeingLoaded;
|
||||
/// <summary>
|
||||
/// Server always loads like it normally would
|
||||
/// </summary>
|
||||
public AsyncOperation GenericLoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
m_ServerSceneBeingLoaded = sceneName;
|
||||
if (NetcodeIntegrationTest.IsRunning)
|
||||
{
|
||||
SceneManager.sceneLoaded += Sever_SceneLoaded;
|
||||
}
|
||||
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
|
||||
sceneEventProgress.SetAsyncOperation(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
private void Sever_SceneLoaded(Scene scene, LoadSceneMode arg1)
|
||||
{
|
||||
if (m_ServerSceneBeingLoaded == scene.name)
|
||||
{
|
||||
ProcessInSceneObjects(scene, NetworkManager);
|
||||
SceneManager.sceneLoaded -= Sever_SceneLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server always unloads like it normally would
|
||||
/// </summary>
|
||||
public AsyncOperation GenericUnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var operation = SceneManager.UnloadSceneAsync(scene);
|
||||
sceneEventProgress.SetAsyncOperation(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
|
||||
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
// Server and non NetcodeIntegrationTest tests use the generic load scene method
|
||||
if (!NetcodeIntegrationTest.IsRunning)
|
||||
{
|
||||
return GenericLoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
||||
}
|
||||
else // NetcodeIntegrationTest Clients always get added to the jobs queue
|
||||
{
|
||||
AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, SceneName = sceneName, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Loading });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
// Server and non NetcodeIntegrationTest tests use the generic unload scene method
|
||||
if (!NetcodeIntegrationTest.IsRunning)
|
||||
{
|
||||
return GenericUnloadSceneAsync(scene, sceneEventProgress);
|
||||
}
|
||||
else // NetcodeIntegrationTest Clients always get added to the jobs queue
|
||||
{
|
||||
AddJobToQueue(new QueuedSceneJob() { IntegrationTestSceneHandler = this, Scene = scene, SceneEventProgress = sceneEventProgress, JobType = QueuedSceneJob.JobTypes.Unloading });
|
||||
}
|
||||
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
|
||||
return new AsyncOperation();
|
||||
return null;
|
||||
}
|
||||
|
||||
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
/// <summary>
|
||||
/// Replacement callback takes other NetworkManagers into consideration
|
||||
/// </summary>
|
||||
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
||||
{
|
||||
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientUnloadSceneCoroutine(sceneEventAction)));
|
||||
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
|
||||
return new AsyncOperation();
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
{
|
||||
var skip = false;
|
||||
foreach (var networkManager in NetworkManagers)
|
||||
{
|
||||
if (NetworkManager.LocalClientId == networkManager.LocalClientId || !networkManager.IsListening)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (networkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
{
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"{NetworkManager.name}'s ScenesLoaded contains {sceneLoaded.name} with a handle of {sceneLoaded.handle}. Skipping over scene.");
|
||||
}
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip && !NetworkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
{
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"{NetworkManager.name} adding {sceneLoaded.name} with a handle of {sceneLoaded.handle} to its ScenesLoaded.");
|
||||
}
|
||||
NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
}
|
||||
|
||||
public IntegrationTestSceneHandler()
|
||||
private bool ExcludeSceneFromSynchronizationCheck(Scene scene)
|
||||
{
|
||||
if (!NetworkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle) && SceneManager.GetActiveScene().handle != scene.handle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor now must take NetworkManager
|
||||
/// </summary>
|
||||
public IntegrationTestSceneHandler(NetworkManager networkManager)
|
||||
{
|
||||
networkManager.SceneManager.OverrideGetAndAddNewlyLoadedSceneByName = GetAndAddNewlyLoadedSceneByName;
|
||||
networkManager.SceneManager.ExcludeSceneFromSychronization = ExcludeSceneFromSynchronizationCheck;
|
||||
NetworkManagers.Add(networkManager);
|
||||
NetworkManagerName = networkManager.name;
|
||||
if (s_WaitForSeconds == null)
|
||||
{
|
||||
s_WaitForSeconds = new WaitForSeconds(1.0f / networkManager.NetworkConfig.TickRate);
|
||||
}
|
||||
NetworkManager = networkManager;
|
||||
if (CoroutineRunner == null)
|
||||
{
|
||||
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
|
||||
@@ -103,12 +382,29 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var coroutine in CoroutinesRunning)
|
||||
NetworkManagers.Clear();
|
||||
if (SceneJobProcessor != null)
|
||||
{
|
||||
CoroutineRunner.StopCoroutine(coroutine);
|
||||
CoroutineRunner.StopCoroutine(SceneJobProcessor);
|
||||
SceneJobProcessor = null;
|
||||
}
|
||||
CoroutineRunner.StopAllCoroutines();
|
||||
|
||||
foreach (var job in QueuedSceneJobs)
|
||||
{
|
||||
if (job.JobType != QueuedSceneJob.JobTypes.Completed)
|
||||
{
|
||||
if (job.JobType == QueuedSceneJob.JobTypes.Loading)
|
||||
{
|
||||
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
|
||||
}
|
||||
job.JobType = QueuedSceneJob.JobTypes.Completed;
|
||||
}
|
||||
}
|
||||
QueuedSceneJobs.Clear();
|
||||
Object.Destroy(CoroutineRunner.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,22 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
internal class MessageHooks : INetworkHooks
|
||||
{
|
||||
public bool IsWaiting;
|
||||
public delegate bool MessageReceiptCheck(object receivedMessage);
|
||||
public bool IsWaiting = true;
|
||||
public delegate bool MessageReceiptCheck(Type receivedMessageType);
|
||||
public MessageReceiptCheck ReceiptCheck;
|
||||
public delegate bool MessageHandleCheck(object receivedMessage);
|
||||
public MessageHandleCheck HandleCheck;
|
||||
|
||||
public static bool CheckForMessageOfType<T>(object receivedMessage) where T : INetworkMessage
|
||||
public static bool CurrentMessageHasTriggerdAHook = false;
|
||||
|
||||
public static bool CheckForMessageOfTypeHandled<T>(object receivedMessage) where T : INetworkMessage
|
||||
{
|
||||
return receivedMessage is T;
|
||||
}
|
||||
public static bool CheckForMessageOfTypeReceived<T>(Type receivedMessageType) where T : INetworkMessage
|
||||
{
|
||||
return receivedMessageType == typeof(T);
|
||||
}
|
||||
|
||||
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
|
||||
{
|
||||
@@ -23,10 +31,24 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
|
||||
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||
{
|
||||
// The way the system works, it goes through all hooks and calls OnBeforeHandleMessage, then handles the message,
|
||||
// then goes thorugh all hooks and calls OnAfterHandleMessage.
|
||||
// This ensures each message only manages to activate a single message hook - because we know that only
|
||||
// one message will ever be handled between OnBeforeHandleMessage and OnAfterHandleMessage,
|
||||
// we can reset the flag here, and then in OnAfterHandleMessage, the moment the message matches a hook,
|
||||
// it'll flip this flag back on, and then other hooks will stop checking that message.
|
||||
// Without this flag, waiting for 10 messages of the same type isn't possible - all 10 hooks would get
|
||||
// tripped by the first message.
|
||||
CurrentMessageHasTriggerdAHook = false;
|
||||
}
|
||||
|
||||
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
|
||||
{
|
||||
if (!CurrentMessageHasTriggerdAHook && IsWaiting && (HandleCheck == null || HandleCheck.Invoke(messageType)))
|
||||
{
|
||||
IsWaiting = false;
|
||||
CurrentMessageHasTriggerdAHook = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
|
||||
@@ -57,13 +79,23 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
|
||||
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
// The way the system works, it goes through all hooks and calls OnBeforeHandleMessage, then handles the message,
|
||||
// then goes thorugh all hooks and calls OnAfterHandleMessage.
|
||||
// This ensures each message only manages to activate a single message hook - because we know that only
|
||||
// one message will ever be handled between OnBeforeHandleMessage and OnAfterHandleMessage,
|
||||
// we can reset the flag here, and then in OnAfterHandleMessage, the moment the message matches a hook,
|
||||
// it'll flip this flag back on, and then other hooks will stop checking that message.
|
||||
// Without this flag, waiting for 10 messages of the same type isn't possible - all 10 hooks would get
|
||||
// tripped by the first message.
|
||||
CurrentMessageHasTriggerdAHook = false;
|
||||
}
|
||||
|
||||
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
|
||||
{
|
||||
if (IsWaiting && (ReceiptCheck == null || ReceiptCheck.Invoke(message)))
|
||||
if (!CurrentMessageHasTriggerdAHook && IsWaiting && (HandleCheck == null || HandleCheck.Invoke(message)))
|
||||
{
|
||||
IsWaiting = false;
|
||||
CurrentMessageHasTriggerdAHook = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@@ -63,18 +64,34 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
public enum ReceiptType
|
||||
{
|
||||
Received,
|
||||
Handled
|
||||
}
|
||||
|
||||
public class MessageHookEntry
|
||||
{
|
||||
internal MessageHooks MessageHooks;
|
||||
protected NetworkManager m_NetworkManager;
|
||||
private MessageHooks.MessageReceiptCheck m_MessageReceiptCheck;
|
||||
private MessageHooks.MessageHandleCheck m_MessageHandleCheck;
|
||||
internal string MessageType;
|
||||
private ReceiptType m_ReceiptType;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Assert.IsNotNull(m_MessageReceiptCheck, $"{nameof(m_MessageReceiptCheck)} is null, did you forget to initialize?");
|
||||
MessageHooks = new MessageHooks();
|
||||
MessageHooks.ReceiptCheck = m_MessageReceiptCheck;
|
||||
if (m_ReceiptType == ReceiptType.Handled)
|
||||
{
|
||||
Assert.IsNotNull(m_MessageHandleCheck, $"{nameof(m_MessageHandleCheck)} is null, did you forget to initialize?");
|
||||
MessageHooks.HandleCheck = m_MessageHandleCheck;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(m_MessageReceiptCheck, $"{nameof(m_MessageReceiptCheck)} is null, did you forget to initialize?");
|
||||
MessageHooks.ReceiptCheck = m_MessageReceiptCheck;
|
||||
}
|
||||
Assert.IsNotNull(m_NetworkManager.MessagingSystem, $"{nameof(NetworkManager.MessagingSystem)} is null! Did you forget to start first?");
|
||||
m_NetworkManager.MessagingSystem.Hook(MessageHooks);
|
||||
}
|
||||
@@ -82,14 +99,41 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
internal void AssignMessageType<T>() where T : INetworkMessage
|
||||
{
|
||||
MessageType = typeof(T).Name;
|
||||
m_MessageReceiptCheck = MessageHooks.CheckForMessageOfType<T>;
|
||||
if (m_ReceiptType == ReceiptType.Handled)
|
||||
{
|
||||
m_MessageHandleCheck = MessageHooks.CheckForMessageOfTypeHandled<T>;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_MessageReceiptCheck = MessageHooks.CheckForMessageOfTypeReceived<T>;
|
||||
}
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public MessageHookEntry(NetworkManager networkManager)
|
||||
internal void AssignMessageType(Type type)
|
||||
{
|
||||
MessageType = type.Name;
|
||||
if (m_ReceiptType == ReceiptType.Handled)
|
||||
{
|
||||
m_MessageHandleCheck = (message) =>
|
||||
{
|
||||
return message.GetType() == type;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
m_MessageReceiptCheck = (messageType) =>
|
||||
{
|
||||
return messageType == type;
|
||||
};
|
||||
}
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public MessageHookEntry(NetworkManager networkManager, ReceiptType type = ReceiptType.Handled)
|
||||
{
|
||||
m_NetworkManager = networkManager;
|
||||
m_ReceiptType = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Unity.Netcode.TestHelpers.Runtime.Metrics
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
public void MyClientRpc()
|
||||
public void MyClientRpc(ClientRpcParams rpcParams = default)
|
||||
{
|
||||
OnClientRpcAction?.Invoke();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#if MULTIPLAYER_TOOLS
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Tools.MetricTypes;
|
||||
using Unity.Multiplayer.Tools.NetStats;
|
||||
|
||||
@@ -12,10 +8,11 @@ namespace Unity.Netcode.TestHelpers.Runtime.Metrics
|
||||
{
|
||||
internal class WaitForEventMetricValues<TMetric> : WaitForMetricValues<TMetric>
|
||||
{
|
||||
IReadOnlyCollection<TMetric> m_EventValues;
|
||||
private IReadOnlyCollection<TMetric> m_EventValues;
|
||||
|
||||
public delegate bool EventFilter(TMetric metric);
|
||||
EventFilter m_EventFilterDelegate;
|
||||
|
||||
private EventFilter m_EventFilterDelegate;
|
||||
|
||||
public WaitForEventMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName)
|
||||
: base(dispatcher, directionalMetricName)
|
||||
|
||||
@@ -4,9 +4,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Unity.Netcode.RuntimeTests;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Unity.Netcode.TestHelpers.Runtime
|
||||
@@ -16,8 +17,15 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// </summary>
|
||||
public abstract class NetcodeIntegrationTest
|
||||
{
|
||||
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(4.0f);
|
||||
protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
|
||||
/// <summary>
|
||||
/// Used to determine if a NetcodeIntegrationTest is currently running to
|
||||
/// determine how clients will load scenes
|
||||
/// </summary>
|
||||
internal static bool IsRunning { get; private set; }
|
||||
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
|
||||
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
|
||||
|
||||
public NetcodeLogAssert NetcodeLogAssert;
|
||||
|
||||
/// <summary>
|
||||
/// Registered list of all NetworkObjects spawned.
|
||||
@@ -74,9 +82,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
protected int TotalClients => m_UseHost ? NumberOfClients + 1 : NumberOfClients;
|
||||
protected int TotalClients => m_UseHost ? m_NumberOfClients + 1 : m_NumberOfClients;
|
||||
|
||||
protected const uint k_DefaultTickRate = 30;
|
||||
|
||||
private int m_NumberOfClients;
|
||||
protected abstract int NumberOfClients { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -119,7 +129,19 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
|
||||
private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode;
|
||||
|
||||
private bool m_EnableVerboseDebug;
|
||||
protected bool m_EnableVerboseDebug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When set to true, this will bypass the entire
|
||||
/// wait for clients to connect process.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// CAUTION:
|
||||
/// Setting this to true will bypass other helper
|
||||
/// identification related code, so this should only
|
||||
/// be used for connection failure oriented testing
|
||||
/// </remarks>
|
||||
protected bool m_BypassConnectionTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to display the various integration test
|
||||
@@ -165,8 +187,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
{
|
||||
Application.runInBackground = true;
|
||||
m_NumberOfClients = NumberOfClients;
|
||||
IsRunning = true;
|
||||
m_EnableVerboseDebug = OnSetVerboseDebug();
|
||||
|
||||
IntegrationTestSceneHandler.VerboseDebugMode = m_EnableVerboseDebug;
|
||||
VerboseDebug($"Entering {nameof(OneTimeSetup)}");
|
||||
|
||||
m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode();
|
||||
@@ -196,6 +221,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
VerboseDebug($"Entering {nameof(SetUp)}");
|
||||
|
||||
NetcodeLogAssert = new NetcodeLogAssert();
|
||||
yield return OnSetup();
|
||||
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null ||
|
||||
m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
|
||||
@@ -248,6 +274,61 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
CreateServerAndClients(NumberOfClients);
|
||||
}
|
||||
|
||||
protected virtual void OnNewClientCreated(NetworkManager networkManager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void AddRemoveNetworkManager(NetworkManager networkManager, bool addNetworkManager)
|
||||
{
|
||||
var clientNetworkManagersList = new List<NetworkManager>(m_ClientNetworkManagers);
|
||||
if (addNetworkManager)
|
||||
{
|
||||
clientNetworkManagersList.Add(networkManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
clientNetworkManagersList.Remove(networkManager);
|
||||
}
|
||||
m_ClientNetworkManagers = clientNetworkManagersList.ToArray();
|
||||
m_NumberOfClients = clientNetworkManagersList.Count;
|
||||
}
|
||||
|
||||
protected IEnumerator CreateAndStartNewClient()
|
||||
{
|
||||
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length);
|
||||
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
|
||||
|
||||
// Notification that the new client (NetworkManager) has been created
|
||||
// in the event any modifications need to be made before starting the client
|
||||
OnNewClientCreated(networkManager);
|
||||
|
||||
NetcodeIntegrationTestHelpers.StartOneClient(networkManager);
|
||||
AddRemoveNetworkManager(networkManager, true);
|
||||
// Wait for the new client to connect
|
||||
yield return WaitForClientsConnectedOrTimeOut();
|
||||
if (s_GlobalTimeoutHelper.TimedOut)
|
||||
{
|
||||
AddRemoveNetworkManager(networkManager, false);
|
||||
Object.Destroy(networkManager.gameObject);
|
||||
}
|
||||
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
|
||||
ClientNetworkManagerPostStart(networkManager);
|
||||
VerboseDebug($"[{networkManager.name}] Created and connected!");
|
||||
}
|
||||
|
||||
protected IEnumerator StopOneClient(NetworkManager networkManager, bool destroy = false)
|
||||
{
|
||||
NetcodeIntegrationTestHelpers.StopOneClient(networkManager, destroy);
|
||||
AddRemoveNetworkManager(networkManager, false);
|
||||
yield return WaitForConditionOrTimeOut(() => !networkManager.IsConnectedClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the server and clients
|
||||
/// </summary>
|
||||
@@ -270,7 +351,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
|
||||
if (m_ServerNetworkManager != null)
|
||||
{
|
||||
s_DefaultWaitForTick = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate);
|
||||
s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate);
|
||||
}
|
||||
|
||||
// Set the player prefab for the server and clients
|
||||
@@ -315,6 +396,54 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private void ClientNetworkManagerPostStart(NetworkManager networkManager)
|
||||
{
|
||||
networkManager.name = $"NetworkManager - Client - {networkManager.LocalClientId}";
|
||||
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
|
||||
|
||||
// Get all player instances for the current client NetworkManager instance
|
||||
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
|
||||
// Add this player instance to each client player entry
|
||||
foreach (var playerNetworkObject in clientPlayerClones)
|
||||
{
|
||||
// When the server is not the host this needs to be done
|
||||
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||
{
|
||||
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(networkManager.LocalClientId))
|
||||
{
|
||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ClientNetworkManagerPostStartInit()
|
||||
{
|
||||
// Creates a dictionary for all player instances client and server relative
|
||||
// This provides a simpler way to get a specific player instance relative to a client instance
|
||||
foreach (var networkManager in m_ClientNetworkManagers)
|
||||
{
|
||||
ClientNetworkManagerPostStart(networkManager);
|
||||
}
|
||||
if (m_UseHost)
|
||||
{
|
||||
var clientSideServerPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
|
||||
foreach (var playerNetworkObject in clientSideServerPlayerClones)
|
||||
{
|
||||
// When the server is not the host this needs to be done
|
||||
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||
{
|
||||
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(m_ServerNetworkManager.LocalClientId))
|
||||
{
|
||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
|
||||
/// returns true.
|
||||
@@ -338,50 +467,36 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
// Notification that the server and clients have been started
|
||||
yield return OnStartedServerAndClients();
|
||||
|
||||
// Wait for all clients to connect
|
||||
yield return WaitForClientsConnectedOrTimeOut();
|
||||
|
||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
|
||||
|
||||
if (m_UseHost || m_ServerNetworkManager.IsHost)
|
||||
// When true, we skip everything else (most likely a connection oriented test)
|
||||
if (!m_BypassConnectionTimeout)
|
||||
{
|
||||
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
|
||||
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
|
||||
foreach (var playerNetworkObject in serverPlayerClones)
|
||||
// Wait for all clients to connect
|
||||
yield return WaitForClientsConnectedOrTimeOut();
|
||||
|
||||
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
|
||||
|
||||
if (m_UseHost || m_ServerNetworkManager.IsHost)
|
||||
{
|
||||
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
|
||||
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
|
||||
foreach (var playerNetworkObject in serverPlayerClones)
|
||||
{
|
||||
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||
{
|
||||
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
|
||||
}
|
||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
|
||||
}
|
||||
|
||||
ClientNetworkManagerPostStartInit();
|
||||
|
||||
// Notification that at this time the server and client(s) are instantiated,
|
||||
// started, and connected on both sides.
|
||||
yield return OnServerAndClientsConnected();
|
||||
|
||||
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
|
||||
}
|
||||
|
||||
// Creates a dictionary for all player instances client and server relative
|
||||
// This provides a simpler way to get a specific player instance relative to a client instance
|
||||
foreach (var networkManager in m_ClientNetworkManagers)
|
||||
{
|
||||
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
|
||||
|
||||
// Get all player instances for the current client NetworkManager instance
|
||||
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
|
||||
// Add this player instance to each client player entry
|
||||
foreach (var playerNetworkObject in clientPlayerClones)
|
||||
{
|
||||
// When the server is not the host this needs to be done
|
||||
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
|
||||
{
|
||||
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Notification that at this time the server and client(s) are instantiated,
|
||||
// started, and connected on both sides.
|
||||
yield return OnServerAndClientsConnected();
|
||||
|
||||
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,11 +524,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// </summary>
|
||||
protected void DeRegisterSceneManagerHandler()
|
||||
{
|
||||
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
|
||||
{
|
||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
|
||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
|
||||
}
|
||||
IntegrationTestSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
|
||||
IntegrationTestSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
|
||||
IntegrationTestSceneHandler.NetworkManagers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -423,11 +536,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// </summary>
|
||||
protected void RegisterSceneManagerHandler()
|
||||
{
|
||||
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
|
||||
{
|
||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
|
||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
|
||||
}
|
||||
IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
|
||||
IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
|
||||
NetcodeIntegrationTestHelpers.RegisterSceneManagerHandler(m_ServerNetworkManager, true);
|
||||
}
|
||||
|
||||
private bool ClientSceneHandler_CanClientsUnload()
|
||||
@@ -440,6 +551,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
return CanClientsLoad();
|
||||
}
|
||||
|
||||
protected bool OnCanSceneCleanUpUnload(Scene scene)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This shuts down all NetworkManager instances registered via the
|
||||
/// <see cref="NetcodeIntegrationTestHelpers"/> class and cleans up
|
||||
@@ -452,11 +568,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
// Shutdown and clean up both of our NetworkManager instances
|
||||
try
|
||||
{
|
||||
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
|
||||
{
|
||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
|
||||
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
|
||||
}
|
||||
DeRegisterSceneManagerHandler();
|
||||
|
||||
NetcodeIntegrationTestHelpers.Destroy();
|
||||
|
||||
@@ -476,8 +588,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
// Cleanup any remaining NetworkObjects
|
||||
DestroySceneNetworkObjects();
|
||||
|
||||
UnloadRemainingScenes();
|
||||
|
||||
// reset the m_ServerWaitForTick for the next test to initialize
|
||||
s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
|
||||
s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
|
||||
VerboseDebug($"Exiting {nameof(ShutdownAndCleanUp)}");
|
||||
}
|
||||
|
||||
@@ -502,6 +616,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
}
|
||||
|
||||
VerboseDebug($"Exiting {nameof(TearDown)}");
|
||||
NetcodeLogAssert.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -517,6 +632,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
IntegrationTestSceneHandler.VerboseDebugMode = false;
|
||||
VerboseDebug($"Entering {nameof(OneTimeTearDown)}");
|
||||
OnOneTimeTearDown();
|
||||
|
||||
@@ -528,7 +644,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
// Disable NetcodeIntegrationTest auto-label feature
|
||||
NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(false);
|
||||
|
||||
UnloadRemainingScenes();
|
||||
|
||||
VerboseDebug($"Exiting {nameof(OneTimeTearDown)}");
|
||||
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -659,6 +779,41 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers);
|
||||
}
|
||||
|
||||
internal IEnumerator WaitForMessageReceived<T>(List<NetworkManager> wiatForReceivedBy, ReceiptType type = ReceiptType.Handled) where T : INetworkMessage
|
||||
{
|
||||
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
|
||||
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
|
||||
foreach (var clientNetworkManager in wiatForReceivedBy)
|
||||
{
|
||||
var messageHook = new MessageHookEntry(clientNetworkManager, type);
|
||||
messageHook.AssignMessageType<T>();
|
||||
messageHookEntriesForSpawn.Add(messageHook);
|
||||
}
|
||||
// Used to determine if all clients received the CreateObjectMessage
|
||||
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
|
||||
yield return WaitForConditionOrTimeOut(hooks);
|
||||
Assert.False(s_GlobalTimeoutHelper.TimedOut);
|
||||
}
|
||||
|
||||
internal IEnumerator WaitForMessagesReceived(List<Type> messagesInOrder, List<NetworkManager> wiatForReceivedBy, ReceiptType type = ReceiptType.Handled)
|
||||
{
|
||||
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
|
||||
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
|
||||
foreach (var clientNetworkManager in wiatForReceivedBy)
|
||||
{
|
||||
foreach (var message in messagesInOrder)
|
||||
{
|
||||
var messageHook = new MessageHookEntry(clientNetworkManager, type);
|
||||
messageHook.AssignMessageType(message);
|
||||
messageHookEntriesForSpawn.Add(messageHook);
|
||||
}
|
||||
}
|
||||
// Used to determine if all clients received the CreateObjectMessage
|
||||
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
|
||||
yield return WaitForConditionOrTimeOut(hooks);
|
||||
Assert.False(s_GlobalTimeoutHelper.TimedOut);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a basic NetworkObject test prefab, assigns it to a new
|
||||
/// NetworkPrefab entry, and then adds it to the server and client(s)
|
||||
@@ -770,12 +925,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// Constructor that allows you To break tests up as a host
|
||||
/// and a server.
|
||||
/// Example: Decorate your child derived class with TestFixture
|
||||
/// and then create a constructor at the child level
|
||||
/// and then create a constructor at the child level.
|
||||
/// Don't forget to set your constructor public, else Unity will
|
||||
/// give you a hard to decipher error
|
||||
/// [TestFixture(HostOrServer.Host)]
|
||||
/// [TestFixture(HostOrServer.Server)]
|
||||
/// public class MyChildClass : NetcodeIntegrationTest
|
||||
/// {
|
||||
/// MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { }
|
||||
/// public MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { }
|
||||
/// }
|
||||
/// </summary>
|
||||
/// <param name="hostOrServer"></param>
|
||||
@@ -783,5 +940,32 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
m_UseHost = hostOrServer == HostOrServer.Host ? true : false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just a helper function to avoid having to write the entire assert just to check if you
|
||||
/// timed out.
|
||||
/// </summary>
|
||||
protected void AssertOnTimeout(string timeOutErrorMessage, TimeoutHelper assignedTimeoutHelper = null)
|
||||
{
|
||||
var timeoutHelper = assignedTimeoutHelper != null ? assignedTimeoutHelper : s_GlobalTimeoutHelper;
|
||||
Assert.False(timeoutHelper.TimedOut, timeOutErrorMessage);
|
||||
}
|
||||
|
||||
private void UnloadRemainingScenes()
|
||||
{
|
||||
// Unload any remaining scenes loaded but the test runner scene
|
||||
// Note: Some tests only unload the server-side instance, and this
|
||||
// just assures no currently loaded scenes will impact the next test
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
if (!scene.IsValid() || !scene.isLoaded || scene.name.Contains(NetcodeIntegrationTestHelpers.FirstPartOfTestRunnerSceneName) || !OnCanSceneCleanUpUnload(scene))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
VerboseDebug($"Unloading scene {scene.name}-{scene.handle}");
|
||||
var asyncOperation = SceneManager.UnloadSceneAsync(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
public static class NetcodeIntegrationTestHelpers
|
||||
{
|
||||
public const int DefaultMinFrames = 1;
|
||||
public const float DefaultTimeout = 1f;
|
||||
public const float DefaultTimeout = 4f;
|
||||
private static List<NetworkManager> s_NetworkManagerInstances = new List<NetworkManager>();
|
||||
private static Dictionary<NetworkManager, MultiInstanceHooks> s_Hooks = new Dictionary<NetworkManager, MultiInstanceHooks>();
|
||||
private static bool s_IsStarted;
|
||||
@@ -118,25 +118,23 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
private const string k_FirstPartOfTestRunnerSceneName = "InitTestScene";
|
||||
internal const string FirstPartOfTestRunnerSceneName = "InitTestScene";
|
||||
|
||||
public static List<NetworkManager> NetworkManagerInstances => s_NetworkManagerInstances;
|
||||
|
||||
internal static IntegrationTestSceneHandler ClientSceneHandler = null;
|
||||
internal static List<IntegrationTestSceneHandler> ClientSceneHandlers = new List<IntegrationTestSceneHandler>();
|
||||
|
||||
/// <summary>
|
||||
/// Registers the IntegrationTestSceneHandler for integration tests.
|
||||
/// The default client behavior is to not load scenes on the client side.
|
||||
/// </summary>
|
||||
private static void RegisterSceneManagerHandler(NetworkManager networkManager)
|
||||
internal static void RegisterSceneManagerHandler(NetworkManager networkManager, bool allowServer = false)
|
||||
{
|
||||
if (!networkManager.IsServer)
|
||||
if (!networkManager.IsServer || networkManager.IsServer && allowServer)
|
||||
{
|
||||
if (ClientSceneHandler == null)
|
||||
{
|
||||
ClientSceneHandler = new IntegrationTestSceneHandler();
|
||||
}
|
||||
networkManager.SceneManager.SceneManagerHandler = ClientSceneHandler;
|
||||
var handler = new IntegrationTestSceneHandler(networkManager);
|
||||
ClientSceneHandlers.Add(handler);
|
||||
networkManager.SceneManager.SceneManagerHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,11 +146,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// </summary>
|
||||
public static void CleanUpHandlers()
|
||||
{
|
||||
if (ClientSceneHandler != null)
|
||||
foreach (var handler in ClientSceneHandlers)
|
||||
{
|
||||
ClientSceneHandler.Dispose();
|
||||
ClientSceneHandler = null;
|
||||
handler.Dispose();
|
||||
}
|
||||
ClientSceneHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -171,17 +169,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
public static NetworkManager CreateServer()
|
||||
private static void AddUnityTransport(NetworkManager networkManager)
|
||||
{
|
||||
// Create gameObject
|
||||
var go = new GameObject("NetworkManager - Server");
|
||||
|
||||
// Create networkManager component
|
||||
var server = go.AddComponent<NetworkManager>();
|
||||
NetworkManagerInstances.Insert(0, server);
|
||||
|
||||
// Create transport
|
||||
var unityTransport = go.AddComponent<UnityTransport>();
|
||||
var unityTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
||||
// We need to increase this buffer size for tests that spawn a bunch of things
|
||||
unityTransport.MaxPayloadSize = 256000;
|
||||
unityTransport.MaxSendQueueSize = 1024 * 1024;
|
||||
@@ -191,11 +182,22 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
unityTransport.ConnectTimeoutMS = 500;
|
||||
|
||||
// Set the NetworkConfig
|
||||
server.NetworkConfig = new NetworkConfig()
|
||||
networkManager.NetworkConfig = new NetworkConfig()
|
||||
{
|
||||
// Set transport
|
||||
NetworkTransport = unityTransport
|
||||
};
|
||||
}
|
||||
|
||||
public static NetworkManager CreateServer()
|
||||
{
|
||||
// Create gameObject
|
||||
var go = new GameObject("NetworkManager - Server");
|
||||
|
||||
// Create networkManager component
|
||||
var server = go.AddComponent<NetworkManager>();
|
||||
NetworkManagerInstances.Insert(0, server);
|
||||
AddUnityTransport(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
@@ -229,6 +231,17 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static NetworkManager CreateNewClient(int identifier)
|
||||
{
|
||||
// Create gameObject
|
||||
var go = new GameObject("NetworkManager - Client - " + identifier);
|
||||
// Create networkManager component
|
||||
var networkManager = go.AddComponent<NetworkManager>();
|
||||
AddUnityTransport(networkManager);
|
||||
return networkManager;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to add a client to the already existing list of clients
|
||||
/// </summary>
|
||||
@@ -237,23 +250,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
|
||||
{
|
||||
clients = new NetworkManager[clientCount];
|
||||
var activeSceneName = SceneManager.GetActiveScene().name;
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
// Create gameObject
|
||||
var go = new GameObject("NetworkManager - Client - " + i);
|
||||
// Create networkManager component
|
||||
clients[i] = go.AddComponent<NetworkManager>();
|
||||
|
||||
// Create transport
|
||||
var unityTransport = go.AddComponent<UnityTransport>();
|
||||
|
||||
// Set the NetworkConfig
|
||||
clients[i].NetworkConfig = new NetworkConfig()
|
||||
{
|
||||
// Set transport
|
||||
NetworkTransport = unityTransport
|
||||
};
|
||||
clients[i] = CreateNewClient(i);
|
||||
}
|
||||
|
||||
NetworkManagerInstances.AddRange(clients);
|
||||
@@ -264,12 +264,32 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// Stops one single client and makes sure to cleanup any static variables in this helper
|
||||
/// </summary>
|
||||
/// <param name="clientToStop"></param>
|
||||
public static void StopOneClient(NetworkManager clientToStop)
|
||||
public static void StopOneClient(NetworkManager clientToStop, bool destroy = true)
|
||||
{
|
||||
clientToStop.Shutdown();
|
||||
s_Hooks.Remove(clientToStop);
|
||||
Object.Destroy(clientToStop.gameObject);
|
||||
NetworkManagerInstances.Remove(clientToStop);
|
||||
if (destroy)
|
||||
{
|
||||
Object.Destroy(clientToStop.gameObject);
|
||||
NetworkManagerInstances.Remove(clientToStop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts one single client and makes sure to register the required hooks and handlers
|
||||
/// </summary>
|
||||
/// <param name="clientToStart"></param>
|
||||
public static void StartOneClient(NetworkManager clientToStart)
|
||||
{
|
||||
clientToStart.StartClient();
|
||||
s_Hooks[clientToStart] = new MultiInstanceHooks();
|
||||
clientToStart.MessagingSystem.Hook(s_Hooks[clientToStart]);
|
||||
if (!NetworkManagerInstances.Contains(clientToStart))
|
||||
{
|
||||
NetworkManagerInstances.Add(clientToStart);
|
||||
}
|
||||
// if set, then invoke this for the client
|
||||
RegisterHandlers(clientToStart);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -315,7 +335,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
|
||||
{
|
||||
// exclude test runner scene
|
||||
if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName))
|
||||
if (sceneName.StartsWith(FirstPartOfTestRunnerSceneName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -342,7 +362,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
// warning about using the currently active scene
|
||||
var scene = SceneManager.GetActiveScene();
|
||||
// As long as this is a test runner scene (or most likely a test runner scene)
|
||||
if (scene.name.StartsWith(k_FirstPartOfTestRunnerSceneName))
|
||||
if (scene.name.StartsWith(FirstPartOfTestRunnerSceneName))
|
||||
{
|
||||
// Register the test runner scene just so we avoid another warning about not being able to find the
|
||||
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
|
||||
@@ -458,8 +478,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
// this feature only works with NetcodeIntegrationTest derived classes
|
||||
if (IsNetcodeIntegrationTestRunning)
|
||||
{
|
||||
// Add the object identifier component
|
||||
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
|
||||
if (networkObject.GetComponent<ObjectNameIdentifier>() == null && networkObject.GetComponentInChildren<ObjectNameIdentifier>() == null)
|
||||
{
|
||||
// Add the object identifier component
|
||||
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,7 +745,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// </summary>
|
||||
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
||||
/// <param name="timeout">The max time in seconds to wait for</param>
|
||||
internal static IEnumerator WaitForMessageOfTypeReceived<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = 0.5f) where T : INetworkMessage
|
||||
internal static IEnumerator WaitForMessageOfTypeReceived<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) where T : INetworkMessage
|
||||
{
|
||||
var hooks = s_Hooks[toBeReceivedBy];
|
||||
var check = new MessageReceiveCheckWithResult { CheckType = typeof(T) };
|
||||
@@ -750,7 +773,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// </summary>
|
||||
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
|
||||
/// <param name="timeout">The max time in seconds to wait for</param>
|
||||
internal static IEnumerator WaitForMessageOfTypeHandled<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = 0.5f) where T : INetworkMessage
|
||||
internal static IEnumerator WaitForMessageOfTypeHandled<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = DefaultTimeout) where T : INetworkMessage
|
||||
{
|
||||
var hooks = s_Hooks[toBeReceivedBy];
|
||||
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
|
||||
|
||||
152
TestHelpers/Runtime/NetcodeLogAssert.cs
Normal file
152
TestHelpers/Runtime/NetcodeLogAssert.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
public class NetcodeLogAssert
|
||||
{
|
||||
private struct LogData
|
||||
{
|
||||
public LogType LogType;
|
||||
public string Message;
|
||||
public string StackTrace;
|
||||
}
|
||||
|
||||
private readonly object m_Lock = new object();
|
||||
private bool m_Disposed;
|
||||
|
||||
private List<LogData> AllLogs { get; }
|
||||
|
||||
public NetcodeLogAssert()
|
||||
{
|
||||
AllLogs = new List<LogData>();
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void Activate()
|
||||
{
|
||||
Application.logMessageReceivedThreaded += AddLog;
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
{
|
||||
Application.logMessageReceivedThreaded -= AddLog;
|
||||
}
|
||||
|
||||
public void AddLog(string message, string stacktrace, LogType type)
|
||||
{
|
||||
lock (m_Lock)
|
||||
{
|
||||
var log = new LogData
|
||||
{
|
||||
LogType = type,
|
||||
Message = message,
|
||||
StackTrace = stacktrace,
|
||||
};
|
||||
|
||||
AllLogs.Add(log);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (m_Disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Disposed = true;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWasNotReceived(LogType type, string message)
|
||||
{
|
||||
lock (m_Lock)
|
||||
{
|
||||
foreach (var logEvent in AllLogs)
|
||||
{
|
||||
if (logEvent.LogType == type && message.Equals(logEvent.Message))
|
||||
{
|
||||
Assert.Fail($"Unexpected log: [{logEvent.LogType}] {logEvent.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWasNotReceived(LogType type, Regex messageRegex)
|
||||
{
|
||||
lock (m_Lock)
|
||||
{
|
||||
foreach (var logEvent in AllLogs)
|
||||
{
|
||||
if (logEvent.LogType == type && messageRegex.IsMatch(logEvent.Message))
|
||||
{
|
||||
Assert.Fail($"Unexpected log: [{logEvent.LogType}] {logEvent.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWasReceived(LogType type, string message)
|
||||
{
|
||||
lock (m_Lock)
|
||||
{
|
||||
var found = false;
|
||||
foreach (var logEvent in AllLogs)
|
||||
{
|
||||
if (logEvent.LogType == type && message.Equals(logEvent.Message))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Assert.Fail($"Expected log was not received: [{type}] {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LogWasReceived(LogType type, Regex messageRegex)
|
||||
{
|
||||
lock (m_Lock)
|
||||
{
|
||||
var found = false;
|
||||
foreach (var logEvent in AllLogs)
|
||||
{
|
||||
if (logEvent.LogType == type && messageRegex.IsMatch(logEvent.Message))
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Assert.Fail($"Expected log was not received: [{type}] {messageRegex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (m_Lock)
|
||||
{
|
||||
AllLogs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
TestHelpers/Runtime/NetcodeLogAssert.cs.meta
Normal file
3
TestHelpers/Runtime/NetcodeLogAssert.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61774c54cd14423ca4de6d56c9fd0fe8
|
||||
timeCreated: 1661800793
|
||||
@@ -13,7 +13,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
/// From both we can then at least determine if the value indeed changed
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class NetworkVariableHelper<T> : NetworkVariableBaseHelper where T : unmanaged
|
||||
public class NetworkVariableHelper<T> : NetworkVariableBaseHelper
|
||||
{
|
||||
private readonly NetworkVariable<T> m_NetworkVariable;
|
||||
public delegate void OnMyValueChangedDelegateHandler(T previous, T next);
|
||||
@@ -50,7 +50,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
{
|
||||
if (previous is ValueType testValueType)
|
||||
{
|
||||
CheckVariableChanged(previous, next);
|
||||
CheckVariableChanged(previous as ValueType, next as ValueType);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
|
||||
@@ -4,7 +4,6 @@ using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
@@ -21,20 +20,20 @@ namespace Unity.Netcode.EditorTests
|
||||
var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
|
||||
var buildTargetSupported = BuildPipeline.IsBuildTargetSupported(buildTargetGroup, buildTarget);
|
||||
|
||||
var buildReport = BuildPipeline.BuildPlayer(
|
||||
new[] { Path.Combine(packagePath, DefaultBuildScenePath) },
|
||||
Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)),
|
||||
buildTarget,
|
||||
BuildOptions.None
|
||||
);
|
||||
|
||||
if (buildTargetSupported)
|
||||
{
|
||||
var buildReport = BuildPipeline.BuildPlayer(
|
||||
new[] { Path.Combine(packagePath, DefaultBuildScenePath) },
|
||||
Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)),
|
||||
buildTarget,
|
||||
BuildOptions.None
|
||||
);
|
||||
|
||||
Assert.AreEqual(BuildResult.Succeeded, buildReport.summary.result);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogAssert.Expect(LogType.Error, "Error building player because build target was unsupported");
|
||||
Debug.Log($"Skipped building player due to Unsupported Build Target");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Unity.Netcode.EditorTests
|
||||
Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f).Within(k_Precision));
|
||||
}
|
||||
|
||||
[Ignore("TODO: Fix this test to still handle testing message loss without extrapolation")]
|
||||
[Test]
|
||||
public void MessageLoss()
|
||||
{
|
||||
@@ -305,6 +306,7 @@ namespace Unity.Netcode.EditorTests
|
||||
Assert.Throws<InvalidOperationException>(() => interpolator.Update(1f, serverTime));
|
||||
}
|
||||
|
||||
[Ignore("TODO: Fix this test to still test duplicated values without extrapolation")]
|
||||
[Test]
|
||||
public void TestDuplicatedValues()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
|
||||
@@ -136,9 +137,9 @@ namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
var sender = new NopMessageSender();
|
||||
|
||||
var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne());
|
||||
var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo());
|
||||
var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree());
|
||||
using var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne());
|
||||
using var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo());
|
||||
using var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree());
|
||||
|
||||
using (systemOne)
|
||||
using (systemTwo)
|
||||
@@ -160,9 +161,9 @@ namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
var sender = new NopMessageSender();
|
||||
|
||||
var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne());
|
||||
var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo());
|
||||
var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree());
|
||||
using var systemOne = new MessagingSystem(sender, null, new TestMessageProviderOne());
|
||||
using var systemTwo = new MessagingSystem(sender, null, new TestMessageProviderTwo());
|
||||
using var systemThree = new MessagingSystem(sender, null, new TestMessageProviderThree());
|
||||
|
||||
using (systemOne)
|
||||
using (systemTwo)
|
||||
@@ -179,5 +180,122 @@ namespace Unity.Netcode.EditorTests
|
||||
Assert.AreEqual(handlerFour, systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]);
|
||||
}
|
||||
}
|
||||
|
||||
internal class AAAEarlyLexicographicNetworkMessage : INetworkMessage
|
||||
{
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006
|
||||
internal class zzzLateLexicographicNetworkMessage : AAAEarlyLexicographicNetworkMessage
|
||||
{
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
|
||||
internal class OrderingMessageProvider : IMessageProvider
|
||||
{
|
||||
public List<MessagingSystem.MessageWithHandler> GetMessages()
|
||||
{
|
||||
var listMessages = new List<MessagingSystem.MessageWithHandler>();
|
||||
|
||||
var messageWithHandler = new MessagingSystem.MessageWithHandler();
|
||||
|
||||
messageWithHandler.MessageType = typeof(zzzLateLexicographicNetworkMessage);
|
||||
listMessages.Add(messageWithHandler);
|
||||
|
||||
messageWithHandler.MessageType = typeof(ConnectionRequestMessage);
|
||||
listMessages.Add(messageWithHandler);
|
||||
|
||||
messageWithHandler.MessageType = typeof(ConnectionApprovedMessage);
|
||||
listMessages.Add(messageWithHandler);
|
||||
|
||||
messageWithHandler.MessageType = typeof(OrderingMessage);
|
||||
listMessages.Add(messageWithHandler);
|
||||
|
||||
messageWithHandler.MessageType = typeof(AAAEarlyLexicographicNetworkMessage);
|
||||
listMessages.Add(messageWithHandler);
|
||||
|
||||
return listMessages;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MessagesGetPrioritizedCorrectly()
|
||||
{
|
||||
var sender = new NopMessageSender();
|
||||
var provider = new OrderingMessageProvider();
|
||||
using var messagingSystem = new MessagingSystem(sender, null, provider);
|
||||
|
||||
// the 3 priority messages should appear first, in lexicographic order
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage));
|
||||
|
||||
// the other should follow after
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(AAAEarlyLexicographicNetworkMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage));
|
||||
|
||||
// there should not be any extras
|
||||
Assert.AreEqual(messagingSystem.MessageHandlerCount, 5);
|
||||
|
||||
// reorder the zzz one to position 3
|
||||
messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName));
|
||||
|
||||
// the 3 priority messages should still appear first, in lexicographic order
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage));
|
||||
|
||||
// the other should follow after, but reordered
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage));
|
||||
|
||||
// there should still not be any extras
|
||||
Assert.AreEqual(messagingSystem.MessageHandlerCount, 5);
|
||||
|
||||
// verify we get an exception when asking for an invalid position
|
||||
try
|
||||
{
|
||||
messagingSystem.ReorderMessage(-1, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName));
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
|
||||
// reorder the zzz one to position 3, again, to check nothing bad happens
|
||||
messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName));
|
||||
|
||||
// the two non-priority should not have moved
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage));
|
||||
|
||||
// there should still not be any extras
|
||||
Assert.AreEqual(messagingSystem.MessageHandlerCount, 5);
|
||||
|
||||
// 4242 is a random hash that should not match anything
|
||||
messagingSystem.ReorderMessage(3, 4242);
|
||||
|
||||
// that should result in an extra entry
|
||||
Assert.AreEqual(messagingSystem.MessageHandlerCount, 6);
|
||||
|
||||
// with a null handler
|
||||
Assert.AreEqual(messagingSystem.MessageHandlers[3], null);
|
||||
|
||||
// and it should have bumped the previous messages down
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage));
|
||||
Assert.AreEqual(messagingSystem.MessageTypes[5], typeof(AAAEarlyLexicographicNetworkMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using NUnit.Framework;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
@@ -40,11 +44,24 @@ namespace Unity.Netcode.EditorTests
|
||||
}
|
||||
}
|
||||
|
||||
private class TestMessageProvider : IMessageProvider
|
||||
private class TestMessageProvider : IMessageProvider, IDisposable
|
||||
{
|
||||
// Keep track of what we sent
|
||||
private List<List<MessagingSystem.MessageWithHandler>> m_CachedMessages = new List<List<MessagingSystem.MessageWithHandler>>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cachedItem in m_CachedMessages)
|
||||
{
|
||||
// Clear out any references to MessagingSystem.MessageWithHandlers
|
||||
cachedItem.Clear();
|
||||
}
|
||||
m_CachedMessages.Clear();
|
||||
}
|
||||
|
||||
public List<MessagingSystem.MessageWithHandler> GetMessages()
|
||||
{
|
||||
return new List<MessagingSystem.MessageWithHandler>
|
||||
var messageList = new List<MessagingSystem.MessageWithHandler>
|
||||
{
|
||||
new MessagingSystem.MessageWithHandler
|
||||
{
|
||||
@@ -52,9 +69,13 @@ namespace Unity.Netcode.EditorTests
|
||||
Handler = MessagingSystem.ReceiveMessage<TestMessage>
|
||||
}
|
||||
};
|
||||
// Track messages sent
|
||||
m_CachedMessages.Add(messageList);
|
||||
return messageList;
|
||||
}
|
||||
}
|
||||
|
||||
private TestMessageProvider m_TestMessageProvider;
|
||||
private TestMessageSender m_MessageSender;
|
||||
private MessagingSystem m_MessagingSystem;
|
||||
private ulong[] m_Clients = { 0 };
|
||||
@@ -63,15 +84,16 @@ namespace Unity.Netcode.EditorTests
|
||||
public void SetUp()
|
||||
{
|
||||
TestMessage.Serialized = false;
|
||||
|
||||
m_MessageSender = new TestMessageSender();
|
||||
m_MessagingSystem = new MessagingSystem(m_MessageSender, this, new TestMessageProvider());
|
||||
m_TestMessageProvider = new TestMessageProvider();
|
||||
m_MessagingSystem = new MessagingSystem(m_MessageSender, this, m_TestMessageProvider);
|
||||
m_MessagingSystem.ClientConnected(0);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
m_TestMessageProvider.Dispose();
|
||||
m_MessagingSystem.Dispose();
|
||||
}
|
||||
|
||||
@@ -224,5 +246,56 @@ namespace Unity.Netcode.EditorTests
|
||||
Assert.AreEqual(message2, receivedMessage2);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestNoHandlerMessageProvider : IMessageProvider
|
||||
{
|
||||
public List<MessagingSystem.MessageWithHandler> GetMessages()
|
||||
{
|
||||
return new List<MessagingSystem.MessageWithHandler>
|
||||
{
|
||||
new MessagingSystem.MessageWithHandler
|
||||
{
|
||||
MessageType = typeof(TestMessage),
|
||||
Handler = null
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenReceivingAMessageWithoutAHandler_ExceptionIsLogged()
|
||||
{
|
||||
// If a MessagingSystem already exists then dispose of it before creating a new MessagingSystem (otherwise memory leak)
|
||||
if (m_MessagingSystem != null)
|
||||
{
|
||||
m_MessagingSystem.Dispose();
|
||||
m_MessagingSystem = null;
|
||||
}
|
||||
|
||||
// Since m_MessagingSystem is disposed during teardown we don't need to worry about that here.
|
||||
m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this, new TestNoHandlerMessageProvider());
|
||||
m_MessagingSystem.ClientConnected(0);
|
||||
|
||||
var messageHeader = new MessageHeader
|
||||
{
|
||||
MessageSize = (ushort)UnsafeUtility.SizeOf<TestMessage>(),
|
||||
MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)),
|
||||
};
|
||||
var message = GetMessage();
|
||||
|
||||
var writer = new FastBufferWriter(1300, Allocator.Temp);
|
||||
using (writer)
|
||||
{
|
||||
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(message));
|
||||
writer.WriteValue(message);
|
||||
|
||||
var reader = new FastBufferReader(writer, Allocator.Temp);
|
||||
using (reader)
|
||||
{
|
||||
m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0, 0);
|
||||
LogAssert.Expect(LogType.Exception, new Regex(".*HandlerNotRegisteredException.*"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user