com.unity.netcode.gameobjects@1.0.0-pre.2
# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.0.0-pre.2] - 2020-12-20 ### Added - Associated Known Issues for the 1.0.0-pre.1 release in the changelog ### Changed - Updated label for `1.0.0-pre.1` changelog section ## [1.0.0-pre.1] - 2020-12-20 ### Added - Added `ClientNetworkTransform` sample to the SDK package (#1168) - Added `Bootstrap` sample to the SDK package (#1140) - Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913) - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons - Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101) - Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060) - Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727) - Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855) - Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670) - When a client tries to spawn a `NetworkObject` an exception is thrown to indicate unsupported behavior. (#981) - Added a `NetworkTime` and `NetworkTickSystem` which allows for improved control over time and ticks. (#845) - Added a `OnNetworkDespawn` function to `NetworkObject` which gets called when a `NetworkObject` gets despawned and can be overriden. (#865) - Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192) - Disabled by default for now, except spawn/despawn messages - Will leverage unreliable messages with eventual consistency - `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762) - Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878) - `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190) - Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175) - Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173) - Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872) ### Changed - Bumped minimum Unity version, renamed package as "Unity Netcode for GameObjects", replaced `MLAPI` namespace and its variants with `Unity.Netcode` namespace and per asm-def variants (#1007, #1009, #1015, #1017, #1019, #1025, #1026, #1065) - Minimum Unity version: - 2019.4 → 2020.3+ - Package rename: - Display name: `MLAPI Networking Library` → `Netcode for GameObjects` - Name: `com.unity.multiplayer.mlapi` → `com.unity.netcode.gameobjects` - Updated package description - All `MLAPI.x` namespaces are replaced with `Unity.Netcode` - `MLAPI.Messaging` → `Unity.Netcode` - `MLAPI.Connection` → `Unity.Netcode` - `MLAPI.Logging` → `Unity.Netcode` - `MLAPI.SceneManagement` → `Unity.Netcode` - and other `MLAPI.x` variants to `Unity.Netcode` - All assembly definitions are renamed with `Unity.Netcode.x` variants - `Unity.Multiplayer.MLAPI.Runtime` → `Unity.Netcode.Runtime` - `Unity.Multiplayer.MLAPI.Editor` → `Unity.Netcode.Editor` - and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants - Renamed `Prototyping` namespace and assembly definition to `Components` (#1145) - Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217) - Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080) - `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955) - `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080) - `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698) - `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865) - Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074) - `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788) - Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer<T>` instead of `NetworkSerializer` (#1187) - Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187) - Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196) ### Removed - Removed `NetworkNavMeshAgent` (#1150) - 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 `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) - Scene Registrations no longer exists - Allow Runtime Scene Changes was no longer needed and was removed - Removed the NetworkObject.Spawn payload parameter (#1005) - Removed `ProfilerCounter`, the original MLAPI network profiler, and the built-in network profiler module (2020.3). A replacement can now be found in the Multiplayer Tools package. (#1048) - Removed UNet RelayTransport and related relay functionality in UNetTransport (#1081) - Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187) - Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187) - Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179) - Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101) - Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108) ### Fixed - Fixed ServerRpc ownership check to `Debug.LogError` instead of `Debug.LogWarning` (#1126) - Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092) - Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803) - Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678) - Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011) - Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s - Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676) - Fixed connection approval not being triggered for host (#675) - Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218) - Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266) - Improved runtime performance and reduced GC pressure (#1187) - Fixed #915 - clients are receiving data from objects not visible to them (#1099) - Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826) - Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844) ### Known Issues - `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation - `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions - `NetworkTransform` interpolates from the origin when switching Local Space synchronization - Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects - Cannot send an array of `INetworkSerializable` in RPCs - ILPP generation fails with special characters in project path ## [0.2.0] - 2021-06-03 WIP version increment to pass package validation checks. Changelog & final version number TBD. ## [0.1.1] - 2021-06-01 This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package. ### Changed - Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release. ## [0.1.0] - 2021-03-23 This is the initial experimental Unity MLAPI Package, v0.1.0. ### Added - Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)). - Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details. - `INetworkSerializable` interface replaces `IBitWritable`. - Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally. - Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details: - It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution. - You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation. - `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs. - Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately. - [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute. - Integrated MLAPI with the Unity Profiler for versions 2020.2 and later: - Added new profiler modules for MLAPI that report important network data. - Attached the profiler to a remote player to view network data over the wire. - A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject). - Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`. ### Changed - [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management. - Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s. - [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects. - GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings. For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514). | Previous MLAPI Versions | V 0.1.0 Name | | -- | -- | | `NetworkingManager` | `NetworkManager` | | `NetworkedObject` | `NetworkObject` | | `NetworkedBehaviour` | `NetworkBehaviour` | | `NetworkedClient` | `NetworkClient` | | `NetworkedPrefab` | `NetworkPrefab` | | `NetworkedVar` | `NetworkVariable` | | `NetworkedTransform` | `NetworkTransform` | | `NetworkedAnimator` | `NetworkAnimator` | | `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` | | `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` | | `SpawnManager` | `NetworkSpawnManager` | | `BitStream` | `NetworkBuffer` | | `BitReader` | `NetworkReader` | | `BitWriter` | `NetworkWriter` | | `NetEventType` | `NetworkEventType` | | `ChannelType` | `NetworkDelivery` | | `Channel` | `NetworkChannel` | | `Transport` | `NetworkTransport` | | `NetworkedDictionary` | `NetworkDictionary` | | `NetworkedList` | `NetworkList` | | `NetworkedSet` | `NetworkSet` | | `MLAPIConstants` | `NetworkConstants` | | `UnetTransport` | `UNetTransport` | ### Fixed - [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`. - Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame. - Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method. - [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode. - [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions. - [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits. - [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`. ### Removed 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 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. ### Known Issues - `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination. - For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error. - For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work. - For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel. - `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them - `NetworkTransform` have the following issues: - Replicated objects may have jitter. - The owner is always authoritative about the object's position. - Scale is not synchronized. - Connection Approval is not called on the host client. - For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages. - For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly. ## [0.0.1-preview.1] - 2020-12-20 This was an internally-only-used version of the Unity MLAPI Package
This commit is contained in:
235
CHANGELOG.md
Normal file
235
CHANGELOG.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||
|
||||
## [1.0.0-pre.2] - 2020-12-20
|
||||
|
||||
### Added
|
||||
|
||||
- Associated Known Issues for the 1.0.0-pre.1 release in the changelog
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated label for `1.0.0-pre.1` changelog section
|
||||
|
||||
## [1.0.0-pre.1] - 2020-12-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added `ClientNetworkTransform` sample to the SDK package (#1168)
|
||||
- Added `Bootstrap` sample to the SDK package (#1140)
|
||||
- Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913)
|
||||
- `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons
|
||||
- Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101)
|
||||
- Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060)
|
||||
- Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727)
|
||||
- Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855)
|
||||
- Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670)
|
||||
- When a client tries to spawn a `NetworkObject` an exception is thrown to indicate unsupported behavior. (#981)
|
||||
- Added a `NetworkTime` and `NetworkTickSystem` which allows for improved control over time and ticks. (#845)
|
||||
- Added a `OnNetworkDespawn` function to `NetworkObject` which gets called when a `NetworkObject` gets despawned and can be overriden. (#865)
|
||||
- Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192)
|
||||
- Disabled by default for now, except spawn/despawn messages
|
||||
- Will leverage unreliable messages with eventual consistency
|
||||
- `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762)
|
||||
- Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878)
|
||||
- `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190)
|
||||
- Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175)
|
||||
- Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173)
|
||||
- Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872)
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped minimum Unity version, renamed package as "Unity Netcode for GameObjects", replaced `MLAPI` namespace and its variants with `Unity.Netcode` namespace and per asm-def variants (#1007, #1009, #1015, #1017, #1019, #1025, #1026, #1065)
|
||||
- Minimum Unity version:
|
||||
- 2019.4 → 2020.3+
|
||||
- Package rename:
|
||||
- Display name: `MLAPI Networking Library` → `Netcode for GameObjects`
|
||||
- Name: `com.unity.multiplayer.mlapi` → `com.unity.netcode.gameobjects`
|
||||
- Updated package description
|
||||
- All `MLAPI.x` namespaces are replaced with `Unity.Netcode`
|
||||
- `MLAPI.Messaging` → `Unity.Netcode`
|
||||
- `MLAPI.Connection` → `Unity.Netcode`
|
||||
- `MLAPI.Logging` → `Unity.Netcode`
|
||||
- `MLAPI.SceneManagement` → `Unity.Netcode`
|
||||
- and other `MLAPI.x` variants to `Unity.Netcode`
|
||||
- All assembly definitions are renamed with `Unity.Netcode.x` variants
|
||||
- `Unity.Multiplayer.MLAPI.Runtime` → `Unity.Netcode.Runtime`
|
||||
- `Unity.Multiplayer.MLAPI.Editor` → `Unity.Netcode.Editor`
|
||||
- and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants
|
||||
- Renamed `Prototyping` namespace and assembly definition to `Components` (#1145)
|
||||
- Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217)
|
||||
- Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080)
|
||||
- `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955)
|
||||
- `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080)
|
||||
- `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698)
|
||||
- `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865)
|
||||
- Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074)
|
||||
- `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788)
|
||||
- Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer<T>` instead of `NetworkSerializer` (#1187)
|
||||
- Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187)
|
||||
- Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `NetworkNavMeshAgent` (#1150)
|
||||
- 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 `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)
|
||||
- Scene Registrations no longer exists
|
||||
- Allow Runtime Scene Changes was no longer needed and was removed
|
||||
- Removed the NetworkObject.Spawn payload parameter (#1005)
|
||||
- Removed `ProfilerCounter`, the original MLAPI network profiler, and the built-in network profiler module (2020.3). A replacement can now be found in the Multiplayer Tools package. (#1048)
|
||||
- Removed UNet RelayTransport and related relay functionality in UNetTransport (#1081)
|
||||
- Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187)
|
||||
- Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187)
|
||||
- Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179)
|
||||
- Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101)
|
||||
- Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed ServerRpc ownership check to `Debug.LogError` instead of `Debug.LogWarning` (#1126)
|
||||
- Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092)
|
||||
- Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803)
|
||||
- Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678)
|
||||
- Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011)
|
||||
- Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s
|
||||
- Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676)
|
||||
- Fixed connection approval not being triggered for host (#675)
|
||||
- Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218)
|
||||
- Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266)
|
||||
- Improved runtime performance and reduced GC pressure (#1187)
|
||||
- Fixed #915 - clients are receiving data from objects not visible to them (#1099)
|
||||
- Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826)
|
||||
- Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844)
|
||||
|
||||
### Known Issues
|
||||
|
||||
- `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation
|
||||
- `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions
|
||||
- `NetworkTransform` interpolates from the origin when switching Local Space synchronization
|
||||
- Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects
|
||||
- Cannot send an array of `INetworkSerializable` in RPCs
|
||||
- ILPP generation fails with special characters in project path
|
||||
|
||||
## [0.2.0] - 2021-06-03
|
||||
|
||||
WIP version increment to pass package validation checks. Changelog & final version number TBD.
|
||||
|
||||
## [0.1.1] - 2021-06-01
|
||||
|
||||
This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package.
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release.
|
||||
|
||||
## [0.1.0] - 2021-03-23
|
||||
|
||||
This is the initial experimental Unity MLAPI Package, v0.1.0.
|
||||
|
||||
### Added
|
||||
|
||||
- Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)).
|
||||
- Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details.
|
||||
- `INetworkSerializable` interface replaces `IBitWritable`.
|
||||
- Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally.
|
||||
- Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details:
|
||||
- It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution.
|
||||
- You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation.
|
||||
- `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs.
|
||||
- Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately.
|
||||
- [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute.
|
||||
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later:
|
||||
- Added new profiler modules for MLAPI that report important network data.
|
||||
- Attached the profiler to a remote player to view network data over the wire.
|
||||
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
|
||||
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`.
|
||||
|
||||
### Changed
|
||||
|
||||
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management.
|
||||
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
|
||||
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
|
||||
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings.
|
||||
|
||||
For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514).
|
||||
|
||||
| Previous MLAPI Versions | V 0.1.0 Name |
|
||||
| -- | -- |
|
||||
| `NetworkingManager` | `NetworkManager` |
|
||||
| `NetworkedObject` | `NetworkObject` |
|
||||
| `NetworkedBehaviour` | `NetworkBehaviour` |
|
||||
| `NetworkedClient` | `NetworkClient` |
|
||||
| `NetworkedPrefab` | `NetworkPrefab` |
|
||||
| `NetworkedVar` | `NetworkVariable` |
|
||||
| `NetworkedTransform` | `NetworkTransform` |
|
||||
| `NetworkedAnimator` | `NetworkAnimator` |
|
||||
| `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` |
|
||||
| `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` |
|
||||
| `SpawnManager` | `NetworkSpawnManager` |
|
||||
| `BitStream` | `NetworkBuffer` |
|
||||
| `BitReader` | `NetworkReader` |
|
||||
| `BitWriter` | `NetworkWriter` |
|
||||
| `NetEventType` | `NetworkEventType` |
|
||||
| `ChannelType` | `NetworkDelivery` |
|
||||
| `Channel` | `NetworkChannel` |
|
||||
| `Transport` | `NetworkTransport` |
|
||||
| `NetworkedDictionary` | `NetworkDictionary` |
|
||||
| `NetworkedList` | `NetworkList` |
|
||||
| `NetworkedSet` | `NetworkSet` |
|
||||
| `MLAPIConstants` | `NetworkConstants` |
|
||||
| `UnetTransport` | `UNetTransport` |
|
||||
|
||||
### Fixed
|
||||
|
||||
- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
|
||||
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame.
|
||||
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method.
|
||||
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode.
|
||||
- [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions.
|
||||
- [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits.
|
||||
- [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`.
|
||||
|
||||
### Removed
|
||||
|
||||
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 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.
|
||||
|
||||
### Known Issues
|
||||
|
||||
- `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination.
|
||||
- For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error.
|
||||
- For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work.
|
||||
- For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel.
|
||||
- `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them
|
||||
- `NetworkTransform` have the following issues:
|
||||
- Replicated objects may have jitter.
|
||||
- The owner is always authoritative about the object's position.
|
||||
- Scale is not synchronized.
|
||||
- Connection Approval is not called on the host client.
|
||||
- For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages.
|
||||
- For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly.
|
||||
|
||||
## [0.0.1-preview.1] - 2020-12-20
|
||||
|
||||
This was an internally-only-used version of the Unity MLAPI Package
|
||||
7
CHANGELOG.md.meta
Normal file
7
CHANGELOG.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8f80d04925759d41a7bbee0259b3c39
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Components.meta
Normal file
8
Components.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b267eb841a574dc083ac248a95d4443
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
Components/AssemblyInfo.cs
Normal file
10
Components/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
#endif
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
11
Components/AssemblyInfo.cs.meta
Normal file
11
Components/AssemblyInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b8086dc75d86473f9e3c928dd773733
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Components/Interpolator.meta
Normal file
8
Components/Interpolator.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8eb56856ab05d41fa9e422a92acbc109
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
261
Components/Interpolator/BufferedLinearInterpolator.cs
Normal file
261
Components/Interpolator/BufferedLinearInterpolator.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Solves for incoming values that are jittered
|
||||
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal abstract class BufferedLinearInterpolator<T> where T : struct
|
||||
{
|
||||
private struct BufferedItem
|
||||
{
|
||||
public T Item;
|
||||
public double TimeSent;
|
||||
|
||||
public BufferedItem(T item, double timeSent)
|
||||
{
|
||||
Item = item;
|
||||
TimeSent = timeSent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
|
||||
|
||||
private T m_InterpStartValue;
|
||||
private T m_CurrentInterpValue;
|
||||
private T m_InterpEndValue;
|
||||
|
||||
private double m_EndTimeConsumed;
|
||||
private double m_StartTimeConsumed;
|
||||
|
||||
private readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
|
||||
|
||||
// Buffer consumption scenarios
|
||||
// Perfect case consumption
|
||||
// | 1 | 2 | 3 |
|
||||
// | 2 | 3 | 4 | consume 1
|
||||
// | 3 | 4 | 5 | consume 2
|
||||
// | 4 | 5 | 6 | consume 3
|
||||
// | 5 | 6 | 7 | consume 4
|
||||
// jittered case
|
||||
// | 1 | 2 | 3 |
|
||||
// | 2 | 3 | | consume 1
|
||||
// | 3 | | | consume 2
|
||||
// | 4 | 5 | 6 | consume 3
|
||||
// | 5 | 6 | 7 | consume 4
|
||||
// bursted case (assuming max count is 5)
|
||||
// | 1 | 2 | 3 |
|
||||
// | 2 | 3 | | consume 1
|
||||
// | 3 | | | consume 2
|
||||
// | | | | consume 3
|
||||
// | | | |
|
||||
// | 4 | 5 | 6 | 7 | 8 | --> consume all and teleport to last value <8> --> this is the nuclear option, ideally this example would consume 4 and 5
|
||||
// instead of jumping to 8, but since in OnValueChange we don't yet have an updated server time (updated in pre-update) to know which value
|
||||
// we should keep and which we should drop, we don't have enough information to do this. Another thing would be to not have the burst in the first place.
|
||||
|
||||
// Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so
|
||||
// that we don't have a very small buffer because of this.
|
||||
private const int k_BufferCountLimit = 100;
|
||||
private BufferedItem m_LastBufferedItemReceived;
|
||||
private int m_NbItemsReceivedThisFrame;
|
||||
|
||||
private int m_LifetimeConsumedCount;
|
||||
|
||||
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
||||
|
||||
public void ResetTo(T targetValue, double serverTime)
|
||||
{
|
||||
m_LifetimeConsumedCount = 1;
|
||||
m_InterpStartValue = targetValue;
|
||||
m_InterpEndValue = targetValue;
|
||||
m_CurrentInterpValue = targetValue;
|
||||
m_Buffer.Clear();
|
||||
m_EndTimeConsumed = 0.0d;
|
||||
m_StartTimeConsumed = 0.0d;
|
||||
|
||||
Update(0, serverTime, serverTime);
|
||||
}
|
||||
|
||||
|
||||
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
|
||||
private void TryConsumeFromBuffer(double renderTime, double serverTime)
|
||||
{
|
||||
int consumedCount = 0;
|
||||
// only consume if we're ready
|
||||
|
||||
// this operation was measured as one of our most expensive, and we should put some thought into this.
|
||||
// NetworkTransform has (currently) 7 buffered linear interpolators (3 position, 3 scale, 1 rot), and
|
||||
// each has its own independent buffer and 'm_endTimeConsume'. That means every frame I have to do 7x
|
||||
// these checks vs. if we tracked these values in a unified way
|
||||
if (renderTime >= m_EndTimeConsumed)
|
||||
{
|
||||
BufferedItem? itemToInterpolateTo = null;
|
||||
// assumes we're using sequenced messages for netvar syncing
|
||||
// buffer contains oldest values first, iterating from end to start to remove elements from list while iterating
|
||||
|
||||
// calling m_Buffer.Count shows up hot in the profiler.
|
||||
for (int i = m_Buffer.Count - 1; i >= 0; i--) // todo stretch: consume ahead if we see we're missing values due to packet loss
|
||||
{
|
||||
var bufferedValue = m_Buffer[i];
|
||||
// Consume when ready and interpolate to last value we can consume. This can consume multiple values from the buffer
|
||||
if (bufferedValue.TimeSent <= serverTime)
|
||||
{
|
||||
if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent)
|
||||
{
|
||||
if (m_LifetimeConsumedCount == 0)
|
||||
{
|
||||
// if interpolator not initialized, teleport to first value when available
|
||||
m_StartTimeConsumed = bufferedValue.TimeSent;
|
||||
m_InterpStartValue = bufferedValue.Item;
|
||||
}
|
||||
else if (consumedCount == 0)
|
||||
{
|
||||
// Interpolating to new value, end becomes start. We then look in our buffer for a new end.
|
||||
m_StartTimeConsumed = m_EndTimeConsumed;
|
||||
m_InterpStartValue = m_InterpEndValue;
|
||||
}
|
||||
|
||||
if (bufferedValue.TimeSent > m_EndTimeConsumed)
|
||||
{
|
||||
itemToInterpolateTo = bufferedValue;
|
||||
m_EndTimeConsumed = bufferedValue.TimeSent;
|
||||
m_InterpEndValue = bufferedValue.Item;
|
||||
}
|
||||
}
|
||||
|
||||
m_Buffer.RemoveAt(i);
|
||||
consumedCount++;
|
||||
m_LifetimeConsumedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience version of 'Update' mainly for testing
|
||||
/// the reason we don't want to always call this version is so that on the calling side we can compute
|
||||
/// the renderTime once for the many things being interpolated (and the many interpolators per object)
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">time since call</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
public T Update(float deltaTime, NetworkTime serverTime)
|
||||
{
|
||||
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to update the state of the interpolators before reading out
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">time since last call</param>
|
||||
/// <param name="renderTime">our current time</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
public T Update(float deltaTime, double renderTime, double serverTime)
|
||||
{
|
||||
TryConsumeFromBuffer(renderTime, serverTime);
|
||||
|
||||
if (InvalidState)
|
||||
{
|
||||
throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet");
|
||||
}
|
||||
|
||||
// Interpolation example to understand the math below
|
||||
// 4 4.5 6 6.5
|
||||
// | | | |
|
||||
// A render B Server
|
||||
|
||||
if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements
|
||||
{
|
||||
float t = 1.0f;
|
||||
double range = m_EndTimeConsumed - m_StartTimeConsumed;
|
||||
if (range > k_SmallValue)
|
||||
{
|
||||
t = (float)((renderTime - m_StartTimeConsumed) / range);
|
||||
|
||||
if (t < 0.0f)
|
||||
{
|
||||
throw new OverflowException($"t = {t} but must be >= 0. range {range}, RenderTime {renderTime}, Start time {m_StartTimeConsumed}, end time {m_EndTimeConsumed}");
|
||||
}
|
||||
|
||||
if (t > 3.0f) // max extrapolation
|
||||
{
|
||||
// TODO this causes issues with teleport, investigate
|
||||
// todo make this configurable
|
||||
t = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
|
||||
float maxInterpTime = 0.1f;
|
||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / maxInterpTime); // second interpolate to smooth out extrapolation jumps
|
||||
}
|
||||
|
||||
m_NbItemsReceivedThisFrame = 0;
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
public void AddMeasurement(T newMeasurement, double sentTime)
|
||||
{
|
||||
m_NbItemsReceivedThisFrame++;
|
||||
|
||||
// This situation can happen after a game is paused. When starting to receive again, the server will have sent a bunch of messages in the meantime
|
||||
// instead of going through thousands of value updates just to get a big teleport, we're giving up on interpolation and teleporting to the latest value
|
||||
if (m_NbItemsReceivedThisFrame > k_BufferCountLimit)
|
||||
{
|
||||
if (m_LastBufferedItemReceived.TimeSent < sentTime)
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
ResetTo(newMeasurement, sentTime);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
m_Buffer.Add(m_LastBufferedItemReceived);
|
||||
}
|
||||
}
|
||||
|
||||
public T GetInterpolatedValue()
|
||||
{
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
protected abstract T Interpolate(T start, T end, float time);
|
||||
protected abstract T InterpolateUnclamped(T start, T end, float time);
|
||||
}
|
||||
|
||||
|
||||
internal class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||
{
|
||||
protected override float InterpolateUnclamped(float start, float end, float time)
|
||||
{
|
||||
return Mathf.LerpUnclamped(start, end, time);
|
||||
}
|
||||
|
||||
protected override float Interpolate(float start, float end, float time)
|
||||
{
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
{
|
||||
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
return Quaternion.SlerpUnclamped(start, end, time);
|
||||
}
|
||||
|
||||
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
return Quaternion.SlerpUnclamped(start, end, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/Interpolator/BufferedLinearInterpolator.cs.meta
Normal file
11
Components/Interpolator/BufferedLinearInterpolator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a12ebf95bdb4445d9a16e4b6adadb6aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
454
Components/NetworkAnimator.cs
Normal file
454
Components/NetworkAnimator.cs
Normal file
@@ -0,0 +1,454 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A prototype component for syncing animations
|
||||
/// </summary>
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkAnimator))]
|
||||
[RequireComponent(typeof(Animator))]
|
||||
public class NetworkAnimator : NetworkBehaviour
|
||||
{
|
||||
internal struct AnimationMessage : INetworkSerializable
|
||||
{
|
||||
public int StateHash; // if non-zero, then Play() this animation, skipping transitions
|
||||
public float NormalizedTime;
|
||||
public byte[] Parameters;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref StateHash);
|
||||
serializer.SerializeValue(ref NormalizedTime);
|
||||
serializer.SerializeValue(ref Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct AnimationParametersMessage : INetworkSerializable
|
||||
{
|
||||
public byte[] Parameters;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct AnimationTriggerMessage : INetworkSerializable
|
||||
{
|
||||
public int Hash;
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref Hash);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private Animator m_Animator;
|
||||
[SerializeField] private uint m_ParameterSendBits;
|
||||
[SerializeField] private float m_SendRate = 0.1f;
|
||||
|
||||
public Animator Animator
|
||||
{
|
||||
get { return m_Animator; }
|
||||
set
|
||||
{
|
||||
m_Animator = value;
|
||||
ResetParameterOptions();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* AutoSend is the ability to select which parameters linked to this animator
|
||||
* get replicated on a regular basis regardless of a state change. The thinking
|
||||
* behind this is that many of the parameters people use are usually booleans
|
||||
* which result in a state change and thus would cause a full sync of state.
|
||||
* Thus if you really care about a parameter syncing then you need to be explict
|
||||
* by selecting it in the inspector when an NetworkAnimator is selected.
|
||||
*/
|
||||
public void SetParameterAutoSend(int index, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
m_ParameterSendBits |= (uint)(1 << index);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ParameterSendBits &= (uint)(~(1 << index));
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetParameterAutoSend(int index)
|
||||
{
|
||||
return (m_ParameterSendBits & (uint)(1 << index)) != 0;
|
||||
}
|
||||
|
||||
// Animators only support up to 32 params
|
||||
public static int K_MaxAnimationParams = 32;
|
||||
|
||||
private int m_TransitionHash;
|
||||
private double m_NextSendTime = 0.0f;
|
||||
|
||||
private int m_AnimationHash;
|
||||
public int AnimationHash { get => m_AnimationHash; }
|
||||
|
||||
private unsafe struct AnimatorParamCache
|
||||
{
|
||||
public int Hash;
|
||||
public int Type;
|
||||
public fixed byte Value[4]; // this is a max size of 4 bytes
|
||||
}
|
||||
|
||||
// 128bytes per Animator
|
||||
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(K_MaxAnimationParams * sizeof(float), Allocator.Persistent);
|
||||
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
|
||||
|
||||
// We cache these values because UnsafeUtility.EnumToInt use direct IL that allows a nonboxing conversion
|
||||
private struct AnimationParamEnumWrapper
|
||||
{
|
||||
public static readonly int AnimatorControllerParameterInt;
|
||||
public static readonly int AnimatorControllerParameterFloat;
|
||||
public static readonly int AnimatorControllerParameterBool;
|
||||
|
||||
static AnimationParamEnumWrapper()
|
||||
{
|
||||
AnimatorControllerParameterInt = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Int);
|
||||
AnimatorControllerParameterFloat = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Float);
|
||||
AnimatorControllerParameterBool = UnsafeUtility.EnumToInt(AnimatorControllerParameterType.Bool);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ResetParameterOptions()
|
||||
{
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfoServer("ResetParameterOptions");
|
||||
}
|
||||
|
||||
m_ParameterSendBits = 0;
|
||||
}
|
||||
|
||||
private bool sendMessagesAllowed
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsServer && NetworkObject.IsSpawned;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (m_CachedAnimatorParameters.IsCreated)
|
||||
{
|
||||
m_CachedAnimatorParameters.Dispose();
|
||||
}
|
||||
|
||||
m_ParameterWriter.Dispose();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
var parameters = m_Animator.parameters;
|
||||
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
|
||||
|
||||
m_AnimationHash = -1;
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var parameter = parameters[i];
|
||||
|
||||
if (m_Animator.IsParameterControlledByCurve(parameter.nameHash))
|
||||
{
|
||||
//we are ignoring parameters that are controlled by animation curves - syncing the layer states indirectly syncs the values that are driven by the animation curves
|
||||
continue;
|
||||
}
|
||||
|
||||
var cacheParam = new AnimatorParamCache();
|
||||
|
||||
cacheParam.Type = UnsafeUtility.EnumToInt(parameter.type);
|
||||
cacheParam.Hash = parameter.nameHash;
|
||||
unsafe
|
||||
{
|
||||
switch (parameter.type)
|
||||
{
|
||||
case AnimatorControllerParameterType.Float:
|
||||
var value = m_Animator.GetFloat(cacheParam.Hash);
|
||||
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, value);
|
||||
break;
|
||||
case AnimatorControllerParameterType.Int:
|
||||
var valueInt = m_Animator.GetInteger(cacheParam.Hash);
|
||||
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueInt);
|
||||
|
||||
break;
|
||||
case AnimatorControllerParameterType.Bool:
|
||||
var valueBool = m_Animator.GetBool(cacheParam.Hash);
|
||||
UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueBool);
|
||||
break;
|
||||
case AnimatorControllerParameterType.Trigger:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_CachedAnimatorParameters[i] = cacheParam;
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!sendMessagesAllowed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int stateHash;
|
||||
float normalizedTime;
|
||||
if (!CheckAnimStateChanged(out stateHash, out normalizedTime))
|
||||
{
|
||||
// We only want to check and send if we don't have any other state to since
|
||||
// as we will sync all params as part of the state sync
|
||||
CheckAndSend();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var animMsg = new AnimationMessage();
|
||||
animMsg.StateHash = stateHash;
|
||||
animMsg.NormalizedTime = normalizedTime;
|
||||
|
||||
m_ParameterWriter.Seek(0);
|
||||
m_ParameterWriter.Truncate();
|
||||
|
||||
WriteParameters(m_ParameterWriter, false);
|
||||
animMsg.Parameters = m_ParameterWriter.ToArray();
|
||||
|
||||
SendAnimStateClientRpc(animMsg);
|
||||
}
|
||||
|
||||
private void CheckAndSend()
|
||||
{
|
||||
var networkTime = NetworkManager.ServerTime.Time;
|
||||
if (sendMessagesAllowed && m_SendRate != 0 && m_NextSendTime < networkTime)
|
||||
{
|
||||
m_NextSendTime = networkTime + m_SendRate;
|
||||
|
||||
m_ParameterWriter.Seek(0);
|
||||
m_ParameterWriter.Truncate();
|
||||
|
||||
if (WriteParameters(m_ParameterWriter, true))
|
||||
{
|
||||
// we then sync the params we care about
|
||||
var animMsg = new AnimationParametersMessage()
|
||||
{
|
||||
Parameters = m_ParameterWriter.ToArray()
|
||||
};
|
||||
|
||||
SendParamsClientRpc(animMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime)
|
||||
{
|
||||
stateHash = 0;
|
||||
normalizedTime = 0;
|
||||
|
||||
if (m_Animator.IsInTransition(0))
|
||||
{
|
||||
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(0);
|
||||
if (tt.fullPathHash != m_TransitionHash)
|
||||
{
|
||||
// first time in this transition
|
||||
m_TransitionHash = tt.fullPathHash;
|
||||
m_AnimationHash = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
|
||||
if (st.fullPathHash != m_AnimationHash)
|
||||
{
|
||||
// first time in this animation state
|
||||
if (m_AnimationHash != 0)
|
||||
{
|
||||
// came from another animation directly - from Play()
|
||||
stateHash = st.fullPathHash;
|
||||
normalizedTime = st.normalizedTime;
|
||||
}
|
||||
m_TransitionHash = 0;
|
||||
m_AnimationHash = st.fullPathHash;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend)
|
||||
{
|
||||
if (m_CachedAnimatorParameters == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
||||
{
|
||||
if (autoSend && !GetParameterAutoSend(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
|
||||
var hash = cacheValue.Hash;
|
||||
|
||||
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
||||
{
|
||||
var valueInt = m_Animator.GetInteger(hash);
|
||||
fixed (void* value = cacheValue.Value)
|
||||
{
|
||||
var oldValue = UnsafeUtility.AsRef<int>(value);
|
||||
if (valueInt != oldValue)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement(value, 0, valueInt);
|
||||
BytePacker.WriteValuePacked(writer, (uint)valueInt);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
||||
{
|
||||
var valueBool = m_Animator.GetBool(hash);
|
||||
fixed (void* value = cacheValue.Value)
|
||||
{
|
||||
var oldValue = UnsafeUtility.AsRef<bool>(value);
|
||||
if (valueBool != oldValue)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement(value, 0, valueBool);
|
||||
writer.WriteValueSafe(valueBool);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
||||
{
|
||||
var valueFloat = m_Animator.GetFloat(hash);
|
||||
fixed (void* value = cacheValue.Value)
|
||||
{
|
||||
var oldValue = UnsafeUtility.AsRef<float>(value);
|
||||
if (valueFloat != oldValue)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
|
||||
|
||||
writer.WriteValueSafe(valueFloat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we do not write any values to the writer then we should not send any data
|
||||
return writer.Length > 0;
|
||||
}
|
||||
|
||||
private unsafe void ReadParameters(FastBufferReader reader, bool autoSend)
|
||||
{
|
||||
if (m_CachedAnimatorParameters == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
||||
{
|
||||
if (autoSend && !GetParameterAutoSend(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
|
||||
var hash = cacheValue.Hash;
|
||||
|
||||
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
||||
{
|
||||
ByteUnpacker.ReadValuePacked(reader, out int newValue);
|
||||
m_Animator.SetInteger(hash, newValue);
|
||||
fixed (void* value = cacheValue.Value)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement(value, 0, newValue);
|
||||
}
|
||||
}
|
||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
|
||||
{
|
||||
reader.ReadValueSafe(out bool newBoolValue);
|
||||
m_Animator.SetBool(hash, newBoolValue);
|
||||
fixed (void* value = cacheValue.Value)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement(value, 0, newBoolValue);
|
||||
}
|
||||
}
|
||||
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
|
||||
{
|
||||
reader.ReadValueSafe(out float newFloatValue);
|
||||
m_Animator.SetFloat(hash, newFloatValue);
|
||||
fixed (void* value = cacheValue.Value)
|
||||
{
|
||||
UnsafeUtility.WriteArrayElement(value, 0, newFloatValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private unsafe void SendParamsClientRpc(AnimationParametersMessage animSnapshot, ClientRpcParams clientRpcParams = default)
|
||||
{
|
||||
if (animSnapshot.Parameters != null)
|
||||
{
|
||||
// We use a fixed value here to avoid the copy of data from the byte buffer since we own the data
|
||||
fixed (byte* parameters = animSnapshot.Parameters)
|
||||
{
|
||||
var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length);
|
||||
ReadParameters(reader, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
|
||||
{
|
||||
if (animSnapshot.StateHash != 0)
|
||||
{
|
||||
m_AnimationHash = animSnapshot.StateHash;
|
||||
m_Animator.Play(animSnapshot.StateHash, 0, animSnapshot.NormalizedTime);
|
||||
}
|
||||
|
||||
if (animSnapshot.Parameters != null && animSnapshot.Parameters.Length != 0)
|
||||
{
|
||||
// We use a fixed value here to avoid the copy of data from the byte buffer since we own the data
|
||||
fixed (byte* parameters = animSnapshot.Parameters)
|
||||
{
|
||||
var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length);
|
||||
ReadParameters(reader, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ClientRpc]
|
||||
private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default)
|
||||
{
|
||||
m_Animator.SetTrigger(animSnapshot.Hash);
|
||||
}
|
||||
|
||||
public void SetTrigger(string triggerName)
|
||||
{
|
||||
SetTrigger(Animator.StringToHash(triggerName));
|
||||
}
|
||||
|
||||
public void SetTrigger(int hash)
|
||||
{
|
||||
var animMsg = new AnimationTriggerMessage();
|
||||
animMsg.Hash = hash;
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
SendAnimTriggerClientRpc(animMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/NetworkAnimator.cs.meta
Normal file
11
Components/NetworkAnimator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8d0727d5ae3244e3b569694d3912374
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
80
Components/NetworkRigidbody.cs
Normal file
80
Components/NetworkRigidbody.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
|
||||
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
private Rigidbody m_Rigidbody;
|
||||
private NetworkTransform m_NetworkTransform;
|
||||
|
||||
private bool m_OriginalKinematic;
|
||||
private RigidbodyInterpolation m_OriginalInterpolation;
|
||||
|
||||
// Used to cache the authority state of this rigidbody during the last frame
|
||||
private bool m_IsAuthority;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody"/> on this peer currently holds authority.
|
||||
/// </summary>
|
||||
private bool HasAuthority => m_NetworkTransform.CanCommitToTransform;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_NetworkTransform = GetComponent<NetworkTransform>();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (NetworkManager.IsListening)
|
||||
{
|
||||
if (HasAuthority != m_IsAuthority)
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
|
||||
private void UpdateRigidbodyKinematicMode()
|
||||
{
|
||||
if (m_IsAuthority == false)
|
||||
{
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
|
||||
m_Rigidbody.isKinematic = m_OriginalKinematic;
|
||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/NetworkRigidbody.cs.meta
Normal file
11
Components/NetworkRigidbody.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6c0be61502bb534f922ebb746851216
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
81
Components/NetworkRigidbody2D.cs
Normal file
81
Components/NetworkRigidbody2D.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody2D"/> on network objects. By controlling the kinematic
|
||||
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
public class NetworkRigidbody2D : NetworkBehaviour
|
||||
{
|
||||
private Rigidbody2D m_Rigidbody;
|
||||
private NetworkTransform m_NetworkTransform;
|
||||
|
||||
private bool m_OriginalKinematic;
|
||||
private RigidbodyInterpolation2D m_OriginalInterpolation;
|
||||
|
||||
// Used to cache the authority state of this rigidbody during the last frame
|
||||
private bool m_IsAuthority;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody2D"/> on this peer currently holds authority.
|
||||
/// </summary>
|
||||
private bool HasAuthority => m_NetworkTransform.CanCommitToTransform;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody2D>();
|
||||
m_NetworkTransform = GetComponent<NetworkTransform>();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (NetworkManager.IsListening)
|
||||
{
|
||||
if (HasAuthority != m_IsAuthority)
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
|
||||
private void UpdateRigidbodyKinematicMode()
|
||||
{
|
||||
if (m_IsAuthority == false)
|
||||
{
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation2D.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
|
||||
m_Rigidbody.isKinematic = m_OriginalKinematic;
|
||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
m_IsAuthority = false;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/NetworkRigidbody2D.cs.meta
Normal file
11
Components/NetworkRigidbody2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80d7c879794dfda4687da0e400131852
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
900
Components/NetworkTransform.cs
Normal file
900
Components/NetworkTransform.cs
Normal file
@@ -0,0 +1,900 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A component for syncing transforms
|
||||
/// NetworkTransform will read the underlying transform and replicate it to clients.
|
||||
/// The replicated value will be automatically be interpolated (if active) and applied to the underlying GameObject's transform
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkTransform))]
|
||||
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
|
||||
public class NetworkTransform : NetworkBehaviour
|
||||
{
|
||||
public const float PositionThresholdDefault = .001f;
|
||||
public const float RotAngleThresholdDefault = .01f;
|
||||
public const float ScaleThresholdDefault = .01f;
|
||||
public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);
|
||||
public OnClientRequestChangeDelegate OnClientRequestChange;
|
||||
|
||||
internal struct NetworkTransformState : INetworkSerializable
|
||||
{
|
||||
private const int k_InLocalSpaceBit = 0;
|
||||
private const int k_PositionXBit = 1;
|
||||
private const int k_PositionYBit = 2;
|
||||
private const int k_PositionZBit = 3;
|
||||
private const int k_RotAngleXBit = 4;
|
||||
private const int k_RotAngleYBit = 5;
|
||||
private const int k_RotAngleZBit = 6;
|
||||
private const int k_ScaleXBit = 7;
|
||||
private const int k_ScaleYBit = 8;
|
||||
private const int k_ScaleZBit = 9;
|
||||
private const int k_TeleportingBit = 10;
|
||||
|
||||
// 11-15: <unused>
|
||||
private ushort m_Bitset;
|
||||
|
||||
public bool InLocalSpace
|
||||
{
|
||||
get => (m_Bitset & (1 << k_InLocalSpaceBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_InLocalSpaceBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_InLocalSpaceBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
// Position
|
||||
public bool HasPositionX
|
||||
{
|
||||
get => (m_Bitset & (1 << k_PositionXBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionXBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionXBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPositionY
|
||||
{
|
||||
get => (m_Bitset & (1 << k_PositionYBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionYBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionYBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPositionZ
|
||||
{
|
||||
get => (m_Bitset & (1 << k_PositionZBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionZBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionZBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
// RotAngles
|
||||
public bool HasRotAngleX
|
||||
{
|
||||
get => (m_Bitset & (1 << k_RotAngleXBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleXBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleXBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRotAngleY
|
||||
{
|
||||
get => (m_Bitset & (1 << k_RotAngleYBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleYBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleYBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRotAngleZ
|
||||
{
|
||||
get => (m_Bitset & (1 << k_RotAngleZBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleZBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleZBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
// Scale
|
||||
public bool HasScaleX
|
||||
{
|
||||
get => (m_Bitset & (1 << k_ScaleXBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleXBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleXBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasScaleY
|
||||
{
|
||||
get => (m_Bitset & (1 << k_ScaleYBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleYBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleYBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasScaleZ
|
||||
{
|
||||
get => (m_Bitset & (1 << k_ScaleZBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleZBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleZBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTeleportingNextFrame
|
||||
{
|
||||
get => (m_Bitset & (1 << k_TeleportingBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_TeleportingBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_TeleportingBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public float PositionX, PositionY, PositionZ;
|
||||
public float RotAngleX, RotAngleY, RotAngleZ;
|
||||
public float ScaleX, ScaleY, ScaleZ;
|
||||
public double SentTime;
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get { return new Vector3(PositionX, PositionY, PositionZ); }
|
||||
set
|
||||
{
|
||||
PositionX = value.x;
|
||||
PositionY = value.y;
|
||||
PositionZ = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Rotation
|
||||
{
|
||||
get { return new Vector3(RotAngleX, RotAngleY, RotAngleZ); }
|
||||
set
|
||||
{
|
||||
RotAngleX = value.x;
|
||||
RotAngleY = value.y;
|
||||
RotAngleZ = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Scale
|
||||
{
|
||||
get { return new Vector3(ScaleX, ScaleY, ScaleZ); }
|
||||
set
|
||||
{
|
||||
ScaleX = value.x;
|
||||
ScaleY = value.y;
|
||||
ScaleZ = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref SentTime);
|
||||
// InLocalSpace + HasXXX Bits
|
||||
serializer.SerializeValue(ref m_Bitset);
|
||||
// Position Values
|
||||
if (HasPositionX)
|
||||
{
|
||||
serializer.SerializeValue(ref PositionX);
|
||||
}
|
||||
|
||||
if (HasPositionY)
|
||||
{
|
||||
serializer.SerializeValue(ref PositionY);
|
||||
}
|
||||
|
||||
if (HasPositionZ)
|
||||
{
|
||||
serializer.SerializeValue(ref PositionZ);
|
||||
}
|
||||
|
||||
// RotAngle Values
|
||||
if (HasRotAngleX)
|
||||
{
|
||||
serializer.SerializeValue(ref RotAngleX);
|
||||
}
|
||||
|
||||
if (HasRotAngleY)
|
||||
{
|
||||
serializer.SerializeValue(ref RotAngleY);
|
||||
}
|
||||
|
||||
if (HasRotAngleZ)
|
||||
{
|
||||
serializer.SerializeValue(ref RotAngleZ);
|
||||
}
|
||||
|
||||
// Scale Values
|
||||
if (HasScaleX)
|
||||
{
|
||||
serializer.SerializeValue(ref ScaleX);
|
||||
}
|
||||
|
||||
if (HasScaleY)
|
||||
{
|
||||
serializer.SerializeValue(ref ScaleY);
|
||||
}
|
||||
|
||||
if (HasScaleZ)
|
||||
{
|
||||
serializer.SerializeValue(ref ScaleZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SyncPositionX = true, SyncPositionY = true, SyncPositionZ = true;
|
||||
public bool SyncRotAngleX = true, SyncRotAngleY = true, SyncRotAngleZ = true;
|
||||
public bool SyncScaleX = true, SyncScaleY = true, SyncScaleZ = true;
|
||||
|
||||
public float PositionThreshold = PositionThresholdDefault;
|
||||
public float RotAngleThreshold = RotAngleThresholdDefault;
|
||||
public float ScaleThreshold = ScaleThresholdDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether this transform should sync in local space or in world space.
|
||||
/// This is important to set since reparenting this transform could have issues,
|
||||
/// if using world position (depending on who gets synced first: the parent or the child)
|
||||
/// Having a child always at position 0,0,0 for example will have less possibilities of desync than when using world positions
|
||||
/// </summary>
|
||||
[Tooltip("Sets whether this transform should sync in local space or in world space")]
|
||||
public bool InLocalSpace = false;
|
||||
|
||||
public bool Interpolate = true;
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine who can write to this transform. Server only for this transform.
|
||||
/// Changing this value alone in a child implementation will not allow you to create a NetworkTransform which can be written to by clients. See the ClientNetworkTransform Sample
|
||||
/// in the package samples for how to implement a NetworkTransform with client write support.
|
||||
/// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing
|
||||
/// </summary>
|
||||
// This is public to make sure that users don't depend on this IsClient && IsOwner check in their code. If this logic changes in the future, we can make it invisible here
|
||||
public bool CanCommitToTransform;
|
||||
protected bool m_CachedIsServer;
|
||||
protected NetworkManager m_CachedNetworkManager;
|
||||
|
||||
private readonly NetworkVariable<NetworkTransformState> m_ReplicatedNetworkState = new NetworkVariable<NetworkTransformState>(new NetworkTransformState());
|
||||
|
||||
private NetworkTransformState m_LocalAuthoritativeNetworkState;
|
||||
|
||||
private NetworkTransformState m_PrevNetworkState;
|
||||
|
||||
private const int k_DebugDrawLineTime = 10;
|
||||
|
||||
private bool m_HasSentLastValue = false; // used to send one last value, so clients can make the difference between lost replication data (clients extrapolate) and no more data to send.
|
||||
|
||||
|
||||
private BufferedLinearInterpolator<float> m_PositionXInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_PositionYInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_PositionZInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<Quaternion> m_RotationInterpolator; // = new BufferedLinearInterpolatorQuaternion(); // rotation is a single Quaternion since each euler axis will affect the quaternion's final value
|
||||
private BufferedLinearInterpolator<float> m_ScaleXInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_ScaleYInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_ScaleZInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private readonly List<BufferedLinearInterpolator<float>> m_AllFloatInterpolators = new List<BufferedLinearInterpolator<float>>(6);
|
||||
|
||||
private Transform m_Transform; // cache the transform component to reduce unnecessary bounce between managed and native
|
||||
private int m_LastSentTick;
|
||||
private NetworkTransformState m_LastSentState;
|
||||
|
||||
/// <summary>
|
||||
/// Tries updating the server authoritative transform, only if allowed.
|
||||
/// If this called server side, this will commit directly.
|
||||
/// If no update is needed, nothing will be sent. This method should still be called every update, it'll self manage when it should and shouldn't send
|
||||
/// </summary>
|
||||
/// <param name="transformToCommit"></param>
|
||||
/// <param name="dirtyTime"></param>
|
||||
protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime)
|
||||
{
|
||||
var isDirty = ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit);
|
||||
TryCommit(isDirty);
|
||||
}
|
||||
|
||||
private void TryCommitValuesToServer(Vector3 position, Vector3 rotation, Vector3 scale, double dirtyTime)
|
||||
{
|
||||
var isDirty = ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, dirtyTime, position, rotation, scale);
|
||||
|
||||
TryCommit(isDirty.isDirty);
|
||||
}
|
||||
|
||||
private void TryCommit(bool isDirty)
|
||||
{
|
||||
void Send(NetworkTransformState stateToSend)
|
||||
{
|
||||
if (m_CachedIsServer)
|
||||
{
|
||||
// server RPC takes a few frames to execute server side, we want this to execute immediately
|
||||
CommitLocallyAndReplicate(stateToSend);
|
||||
}
|
||||
else
|
||||
{
|
||||
CommitTransformServerRpc(stateToSend);
|
||||
}
|
||||
}
|
||||
|
||||
// if dirty, send
|
||||
// if not dirty anymore, but hasn't sent last value for limiting extrapolation, still set isDirty
|
||||
// if not dirty and has already sent last value, don't do anything
|
||||
// extrapolation works by using last two values. if it doesn't receive anything anymore, it'll continue to extrapolate.
|
||||
// This is great in case there's message loss, not so great if we just don't have new values to send.
|
||||
// the following will send one last "copied" value so unclamped interpolation tries to extrapolate between two identical values, effectively
|
||||
// making it immobile.
|
||||
if (isDirty)
|
||||
{
|
||||
Send(m_LocalAuthoritativeNetworkState);
|
||||
m_HasSentLastValue = false;
|
||||
m_LastSentTick = m_CachedNetworkManager.LocalTime.Tick;
|
||||
m_LastSentState = m_LocalAuthoritativeNetworkState;
|
||||
}
|
||||
else if (!m_HasSentLastValue && m_CachedNetworkManager.LocalTime.Tick >= m_LastSentTick + 1) // check for state.IsDirty since update can happen more than once per tick. No need for client, RPCs will just queue up
|
||||
{
|
||||
m_LastSentState.SentTime = m_CachedNetworkManager.LocalTime.Time; // time 1+ tick later
|
||||
Send(m_LastSentState);
|
||||
m_HasSentLastValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
private void CommitTransformServerRpc(NetworkTransformState networkState, ServerRpcParams serverParams = default)
|
||||
{
|
||||
if (serverParams.Receive.SenderClientId == OwnerClientId) // RPC call when not authorized to write could happen during the RTT interval during which a server's ownership change hasn't reached the client yet
|
||||
{
|
||||
CommitLocallyAndReplicate(networkState);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitLocallyAndReplicate(NetworkTransformState networkState)
|
||||
{
|
||||
m_ReplicatedNetworkState.Value = networkState;
|
||||
AddInterpolatedState(networkState);
|
||||
}
|
||||
|
||||
private void ResetInterpolatedStateToCurrentAuthoritativeState()
|
||||
{
|
||||
var serverTime = NetworkManager.ServerTime.Time;
|
||||
m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime);
|
||||
m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime);
|
||||
m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime);
|
||||
|
||||
m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.Rotation), serverTime);
|
||||
|
||||
m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime);
|
||||
m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime);
|
||||
m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
|
||||
}
|
||||
|
||||
// updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made
|
||||
// returned boolean would be useful to change encapsulating `NetworkVariable<NetworkState>`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty);
|
||||
internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
|
||||
{
|
||||
return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, transformToUse).isDirty;
|
||||
}
|
||||
|
||||
private (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
|
||||
{
|
||||
var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position;
|
||||
var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles;
|
||||
var scale = transformToUse.localScale;
|
||||
return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, position, rotAngles, scale);
|
||||
}
|
||||
|
||||
private (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Vector3 position, Vector3 rotAngles, Vector3 scale)
|
||||
{
|
||||
var isDirty = false;
|
||||
var isPositionDirty = false;
|
||||
var isRotationDirty = false;
|
||||
var isScaleDirty = false;
|
||||
|
||||
// hasPositionZ set to false when it should be true?
|
||||
|
||||
if (InLocalSpace != networkState.InLocalSpace)
|
||||
{
|
||||
networkState.InLocalSpace = InLocalSpace;
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
// we assume that if x, y or z are dirty then we'll have to send all 3 anyway, so for efficiency
|
||||
// we skip doing the (quite expensive) Math.Approximately() and check against PositionThreshold
|
||||
// this still is overly costly and could use more improvements.
|
||||
//
|
||||
// (ditto for scale components)
|
||||
if (SyncPositionX &&
|
||||
Mathf.Abs(networkState.PositionX - position.x) > PositionThreshold)
|
||||
{
|
||||
networkState.PositionX = position.x;
|
||||
networkState.HasPositionX = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionY &&
|
||||
Mathf.Abs(networkState.PositionY - position.y) > PositionThreshold)
|
||||
{
|
||||
networkState.PositionY = position.y;
|
||||
networkState.HasPositionY = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionZ &&
|
||||
Mathf.Abs(networkState.PositionZ - position.z) > PositionThreshold)
|
||||
{
|
||||
networkState.PositionZ = position.z;
|
||||
networkState.HasPositionZ = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleX &&
|
||||
Mathf.Abs(networkState.RotAngleX - rotAngles.x) > RotAngleThreshold)
|
||||
{
|
||||
networkState.RotAngleX = rotAngles.x;
|
||||
networkState.HasRotAngleX = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleY &&
|
||||
Mathf.Abs(networkState.RotAngleY - rotAngles.y) > RotAngleThreshold)
|
||||
{
|
||||
networkState.RotAngleY = rotAngles.y;
|
||||
networkState.HasRotAngleY = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleZ &&
|
||||
Mathf.Abs(networkState.RotAngleZ - rotAngles.z) > RotAngleThreshold)
|
||||
{
|
||||
networkState.RotAngleZ = rotAngles.z;
|
||||
networkState.HasRotAngleZ = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleX &&
|
||||
Mathf.Abs(networkState.ScaleX - scale.x) > ScaleThreshold)
|
||||
{
|
||||
networkState.ScaleX = scale.x;
|
||||
networkState.HasScaleX = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleY &&
|
||||
Mathf.Abs(networkState.ScaleY - scale.y) > ScaleThreshold)
|
||||
{
|
||||
networkState.ScaleY = scale.y;
|
||||
networkState.HasScaleY = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleZ &&
|
||||
Mathf.Abs(networkState.ScaleZ - scale.z) > ScaleThreshold)
|
||||
{
|
||||
networkState.ScaleZ = scale.z;
|
||||
networkState.HasScaleZ = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
isDirty |= isPositionDirty || isRotationDirty || isScaleDirty;
|
||||
|
||||
if (isDirty)
|
||||
{
|
||||
networkState.SentTime = dirtyTime;
|
||||
}
|
||||
|
||||
return (isDirty, isPositionDirty, isRotationDirty, isScaleDirty);
|
||||
}
|
||||
|
||||
private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState networkState, Transform transformToUpdate)
|
||||
{
|
||||
m_PrevNetworkState = networkState;
|
||||
|
||||
var interpolatedPosition = InLocalSpace ? transformToUpdate.localPosition : transformToUpdate.position;
|
||||
|
||||
// todo: we should store network state w/ quats vs. euler angles
|
||||
var interpolatedRotAngles = InLocalSpace ? transformToUpdate.localEulerAngles : transformToUpdate.eulerAngles;
|
||||
var interpolatedScale = transformToUpdate.localScale;
|
||||
|
||||
// InLocalSpace Read
|
||||
InLocalSpace = networkState.InLocalSpace;
|
||||
// Position Read
|
||||
if (SyncPositionX)
|
||||
{
|
||||
interpolatedPosition.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.x : m_PositionXInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncPositionY)
|
||||
{
|
||||
interpolatedPosition.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.y : m_PositionYInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncPositionZ)
|
||||
{
|
||||
interpolatedPosition.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.z : m_PositionZInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
// again, we should be using quats here
|
||||
if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ)
|
||||
{
|
||||
var eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles;
|
||||
if (SyncRotAngleX)
|
||||
{
|
||||
interpolatedRotAngles.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.x : eulerAngles.x;
|
||||
}
|
||||
|
||||
if (SyncRotAngleY)
|
||||
{
|
||||
interpolatedRotAngles.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.y : eulerAngles.y;
|
||||
}
|
||||
|
||||
if (SyncRotAngleZ)
|
||||
{
|
||||
interpolatedRotAngles.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.z : eulerAngles.z;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale Read
|
||||
if (SyncScaleX)
|
||||
{
|
||||
interpolatedScale.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.x : m_ScaleXInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncScaleY)
|
||||
{
|
||||
interpolatedScale.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.y : m_ScaleYInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncScaleZ)
|
||||
{
|
||||
interpolatedScale.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.z : m_ScaleZInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
// Position Apply
|
||||
if (SyncPositionX || SyncPositionY || SyncPositionZ)
|
||||
{
|
||||
if (InLocalSpace)
|
||||
{
|
||||
transformToUpdate.localPosition = interpolatedPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
transformToUpdate.position = interpolatedPosition;
|
||||
}
|
||||
|
||||
m_PrevNetworkState.Position = interpolatedPosition;
|
||||
}
|
||||
|
||||
// RotAngles Apply
|
||||
if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ)
|
||||
{
|
||||
if (InLocalSpace)
|
||||
{
|
||||
transformToUpdate.localRotation = Quaternion.Euler(interpolatedRotAngles);
|
||||
}
|
||||
else
|
||||
{
|
||||
transformToUpdate.rotation = Quaternion.Euler(interpolatedRotAngles);
|
||||
}
|
||||
|
||||
m_PrevNetworkState.Rotation = interpolatedRotAngles;
|
||||
}
|
||||
|
||||
// Scale Apply
|
||||
if (SyncScaleX || SyncScaleY || SyncScaleZ)
|
||||
{
|
||||
transformToUpdate.localScale = interpolatedScale;
|
||||
m_PrevNetworkState.Scale = interpolatedScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInterpolatedState(NetworkTransformState newState)
|
||||
{
|
||||
var sentTime = newState.SentTime;
|
||||
|
||||
if (newState.HasPositionX)
|
||||
{
|
||||
m_PositionXInterpolator.AddMeasurement(newState.PositionX, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasPositionY)
|
||||
{
|
||||
m_PositionYInterpolator.AddMeasurement(newState.PositionY, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasPositionZ)
|
||||
{
|
||||
m_PositionZInterpolator.AddMeasurement(newState.PositionZ, sentTime);
|
||||
}
|
||||
|
||||
m_RotationInterpolator.AddMeasurement(Quaternion.Euler(newState.Rotation), sentTime);
|
||||
|
||||
if (newState.HasScaleX)
|
||||
{
|
||||
m_ScaleXInterpolator.AddMeasurement(newState.ScaleX, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasScaleY)
|
||||
{
|
||||
m_ScaleYInterpolator.AddMeasurement(newState.ScaleY, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasScaleZ)
|
||||
{
|
||||
m_ScaleZInterpolator.AddMeasurement(newState.ScaleZ, sentTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransformState newState)
|
||||
{
|
||||
if (!NetworkObject.IsSpawned)
|
||||
{
|
||||
// todo MTT-849 should never happen but yet it does! maybe revisit/dig after NetVar updates and snapshot system lands?
|
||||
return;
|
||||
}
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
// we're the authority, we ignore incoming changes
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.DrawLine(newState.Position, newState.Position + Vector3.up + Vector3.left, Color.green, 10, false);
|
||||
|
||||
AddInterpolatedState(newState);
|
||||
|
||||
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
var pos = new Vector3(newState.PositionX, newState.PositionY, newState.PositionZ);
|
||||
Debug.DrawLine(pos, pos + Vector3.up + Vector3.left * Random.Range(0.5f, 2f), Color.green, k_DebugDrawLineTime, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// we only want to create our interpolators during Awake so that, when pooled, we do not create tons
|
||||
// of gc thrash each time objects wink out and are re-used
|
||||
m_PositionXInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_PositionYInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_PositionZInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); // rotation is a single Quaternion since each euler axis will affect the quaternion's final value
|
||||
m_ScaleXInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_ScaleYInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_ScaleZInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
|
||||
if (m_AllFloatInterpolators.Count == 0)
|
||||
{
|
||||
m_AllFloatInterpolators.Add(m_PositionXInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_PositionYInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_PositionZInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_ScaleXInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_ScaleYInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_ScaleZInterpolator);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// must set up m_Transform in OnNetworkSpawn because it's possible an object spawns but is disabled
|
||||
// and thus awake won't be called.
|
||||
// TODO: investigate further on not sending data for something that is not enabled
|
||||
m_Transform = transform;
|
||||
m_ReplicatedNetworkState.OnValueChanged += OnNetworkStateChanged;
|
||||
|
||||
CanCommitToTransform = IsServer;
|
||||
m_CachedIsServer = IsServer;
|
||||
m_CachedNetworkManager = NetworkManager;
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
|
||||
}
|
||||
m_LocalAuthoritativeNetworkState = m_ReplicatedNetworkState.Value;
|
||||
|
||||
// crucial we do this to reset the interpolators so that recycled objects when using a pool will
|
||||
// not have leftover interpolator state from the previous object
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
m_ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged;
|
||||
}
|
||||
|
||||
public override void OnGainedOwnership()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override void OnLostOwnership()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
ResetInterpolatedStateToCurrentAuthoritativeState(); // useful for late joining
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
m_ReplicatedNetworkState.SetDirty(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
|
||||
}
|
||||
}
|
||||
|
||||
#region state set
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets a state on the authoritative transform.
|
||||
/// This will override any changes made previously to the transform
|
||||
/// This isn't resistant to network jitter. Server side changes due to this method won't be interpolated.
|
||||
/// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb
|
||||
/// just the desired one(s)
|
||||
/// </summary>
|
||||
/// <param name="posIn"></param> new position to move to. Can be null
|
||||
/// <param name="rotIn"></param> new rotation to rotate to. Can be null
|
||||
/// <param name="scaleIn">new scale to scale to. Can be null</param>
|
||||
/// <param name="shouldGhostsInterpolate">Should other clients interpolate this change or not. True by default</param>
|
||||
/// new scale to scale to. Can be null
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool shouldGhostsInterpolate = true)
|
||||
{
|
||||
if (!IsOwner)
|
||||
{
|
||||
throw new Exception("Trying to set a state on a not owned transform");
|
||||
}
|
||||
|
||||
if (m_CachedNetworkManager && !(m_CachedNetworkManager.IsConnectedClient || m_CachedNetworkManager.IsListening))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 pos = posIn == null ? transform.position : (Vector3)posIn;
|
||||
Quaternion rot = rotIn == null ? transform.rotation : (Quaternion)rotIn;
|
||||
Vector3 scale = scaleIn == null ? transform.localScale : (Vector3)scaleIn;
|
||||
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
if (!m_CachedIsServer)
|
||||
{
|
||||
SetStateServerRpc(pos, rot, scale, shouldGhostsInterpolate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Transform.position = pos;
|
||||
m_Transform.rotation = rot;
|
||||
m_Transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldGhostsInterpolate;
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc]
|
||||
private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport)
|
||||
{
|
||||
// server has received this RPC request to move change transform. Give the server a chance to modify or
|
||||
// even reject the move
|
||||
if (OnClientRequestChange != null)
|
||||
{
|
||||
(pos, rot, scale) = OnClientRequestChange(pos, rot, scale);
|
||||
}
|
||||
m_Transform.position = pos;
|
||||
m_Transform.rotation = rot;
|
||||
m_Transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
||||
}
|
||||
#endregion
|
||||
|
||||
// todo this is currently in update, to be able to catch any transform changes. A FixedUpdate mode could be added to be less intense, but it'd be
|
||||
// conditional to users only making transform update changes in FixedUpdate.
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (!NetworkObject.IsSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
if (m_CachedIsServer)
|
||||
{
|
||||
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
|
||||
}
|
||||
|
||||
m_PrevNetworkState = m_LocalAuthoritativeNetworkState;
|
||||
}
|
||||
|
||||
// apply interpolated value
|
||||
if (m_CachedNetworkManager.IsConnectedClient || m_CachedNetworkManager.IsListening)
|
||||
{
|
||||
// eventually, we could hoist this calculation so that it happens once for all objects, not once per object
|
||||
var cachedDeltaTime = Time.deltaTime;
|
||||
var serverTime = NetworkManager.ServerTime;
|
||||
var cachedServerTime = serverTime.Time;
|
||||
var cachedRenderTime = serverTime.TimeTicksAgo(1).Time;
|
||||
|
||||
foreach (var interpolator in m_AllFloatInterpolators)
|
||||
{
|
||||
interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
|
||||
}
|
||||
|
||||
m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
|
||||
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
#if NGO_TRANSFORM_DEBUG
|
||||
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
// TODO: This should be a component gizmo - not some debug draw based on log level
|
||||
var interpolatedPosition = new Vector3(m_PositionXInterpolator.GetInterpolatedValue(), m_PositionYInterpolator.GetInterpolatedValue(), m_PositionZInterpolator.GetInterpolatedValue());
|
||||
Debug.DrawLine(interpolatedPosition, interpolatedPosition + Vector3.up, Color.magenta, k_DebugDrawLineTime, false);
|
||||
|
||||
// try to update previously consumed NetworkState
|
||||
// if we have any changes, that means made some updates locally
|
||||
// we apply the latest ReplNetworkState again to revert our changes
|
||||
var oldStateDirtyInfo = ApplyTransformToNetworkStateWithInfo(ref m_PrevNetworkState, 0, m_Transform);
|
||||
|
||||
// there are several bugs in this code, as we the message is dumped out under odd circumstances
|
||||
// For Matt, it would trigger when an object's rotation was perturbed by colliding with another
|
||||
// object vs. explicitly rotating it
|
||||
if (oldStateDirtyInfo.isPositionDirty || oldStateDirtyInfo.isScaleDirty || (oldStateDirtyInfo.isRotationDirty && SyncRotAngleX && SyncRotAngleY && SyncRotAngleZ))
|
||||
{
|
||||
// ignoring rotation dirty since quaternions will mess with euler angles, making this impossible to determine if the change to a single axis comes
|
||||
// from an unauthorized transform change or euler to quaternion conversion artifacts.
|
||||
var dirtyField = oldStateDirtyInfo.isPositionDirty ? "position" : oldStateDirtyInfo.isRotationDirty ? "rotation" : "scale";
|
||||
Debug.LogWarning($"A local change to {dirtyField} without authority detected, reverting back to latest interpolated network state!", this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Apply updated interpolated value
|
||||
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
|
||||
}
|
||||
}
|
||||
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the transform to the given values without interpolating
|
||||
/// </summary>
|
||||
public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale)
|
||||
{
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
throw new Exception("Teleport not allowed");
|
||||
}
|
||||
|
||||
var newRotationEuler = newRotation.eulerAngles;
|
||||
var stateToSend = m_LocalAuthoritativeNetworkState;
|
||||
stateToSend.IsTeleportingNextFrame = true;
|
||||
stateToSend.Position = newPosition;
|
||||
stateToSend.Rotation = newRotationEuler;
|
||||
stateToSend.Scale = newScale;
|
||||
ApplyInterpolatedNetworkStateToTransform(stateToSend, transform);
|
||||
// set teleport flag in state to signal to ghosts not to interpolate
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = true;
|
||||
// check server side
|
||||
TryCommitValuesToServer(newPosition, newRotationEuler, newScale, m_CachedNetworkManager.LocalTime.Time);
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/NetworkTransform.cs.meta
Normal file
11
Components/NetworkTransform.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e96cb6065543e43c4a752faaa1468eb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Components/com.unity.netcode.components.asmdef
Normal file
17
Components/com.unity.netcode.components.asmdef
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Unity.Netcode.Components",
|
||||
"rootNamespace": "Unity.Netcode.Components",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Components/com.unity.netcode.components.asmdef.meta
Normal file
7
Components/com.unity.netcode.components.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b8ed52f1b5c64994af4c4e0aa4b6c4b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
Documentation~/Index.md
Normal file
31
Documentation~/Index.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# About Netcode for GameObjects
|
||||
|
||||
Unity Netcode for GameObjects is a high-level networking library built to abstract networking. This allows developers to focus on the game rather than low level protocols and networking frameworks.
|
||||
|
||||
## Guides
|
||||
|
||||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
||||
|
||||
* [Documentation](https://docs-multiplayer.unity3d.com/docs/getting-started/about-mlapi)
|
||||
* [Installation](https://docs-multiplayer.unity3d.com/docs/migration/install)
|
||||
* [First Steps](https://docs-multiplayer.unity3d.com/docs/tutorials/helloworld/helloworldintro)
|
||||
* [API Reference](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
# Technical details
|
||||
|
||||
## Requirements
|
||||
|
||||
This version of Netcode for GameObjects is compatible with the following Unity versions and platforms:
|
||||
|
||||
* 2020.3 and later
|
||||
* Windows, Mac, Linux platforms
|
||||
|
||||
## Document revision history
|
||||
|
||||
|Date|Reason|
|
||||
|---|---|
|
||||
|March 10, 2021|Document created. Matches package version 0.1.0|
|
||||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
||||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
||||
|August 5, 2021|Update product/package name|
|
||||
|September 9,2021|Updated the links and name of the file.|
|
||||
8
Editor.meta
Normal file
8
Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0388707d01c6e18409986ad2fadf6faa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Editor/AssemblyInfo.cs
Normal file
3
Editor/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
|
||||
3
Editor/AssemblyInfo.cs.meta
Normal file
3
Editor/AssemblyInfo.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b3ac10f0d62417080a83d7e17407dd3
|
||||
timeCreated: 1631656133
|
||||
8
Editor/CodeGen.meta
Normal file
8
Editor/CodeGen.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbb4974b4302f435b9f4663c64d8f803
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
326
Editor/CodeGen/CodeGenHelpers.cs
Normal file
326
Editor/CodeGen/CodeGenHelpers.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal static class CodeGenHelpers
|
||||
{
|
||||
public const string RuntimeAssemblyName = "Unity.Netcode.Runtime";
|
||||
|
||||
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName;
|
||||
public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName;
|
||||
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName;
|
||||
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
|
||||
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
|
||||
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
|
||||
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).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;
|
||||
public static readonly string UnityVector3_FullName = typeof(Vector3).FullName;
|
||||
public static readonly string UnityVector4_FullName = typeof(Vector4).FullName;
|
||||
public static readonly string UnityQuaternion_FullName = typeof(Quaternion).FullName;
|
||||
public static readonly string UnityRay_FullName = typeof(Ray).FullName;
|
||||
public static readonly string UnityRay2D_FullName = typeof(Ray2D).FullName;
|
||||
|
||||
public static uint Hash(this MethodDefinition methodDefinition)
|
||||
{
|
||||
var sigArr = Encoding.UTF8.GetBytes($"{methodDefinition.Module.Name} / {methodDefinition.FullName}");
|
||||
var sigLen = sigArr.Length;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* sigPtr = sigArr)
|
||||
{
|
||||
return XXHash.Hash32(sigPtr, sigLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSubclassOf(this TypeDefinition typeDefinition, string classTypeFullName)
|
||||
{
|
||||
if (!typeDefinition.IsClass)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baseTypeRef = typeDefinition.BaseType;
|
||||
while (baseTypeRef != null)
|
||||
{
|
||||
if (baseTypeRef.FullName == classTypeFullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
baseTypeRef = baseTypeRef.Resolve().BaseType;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
|
||||
{
|
||||
if (typeReference.IsArray)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var typeDef = typeReference.Resolve();
|
||||
var typeFaces = typeDef.Interfaces;
|
||||
return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSerializable(this TypeReference typeReference)
|
||||
{
|
||||
var typeSystem = typeReference.Module.TypeSystem;
|
||||
|
||||
// C# primitives
|
||||
if (typeReference == typeSystem.Boolean)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Char)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.SByte)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Byte)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Int16)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.UInt16)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Int32)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.UInt32)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Int64)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.UInt64)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Single)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.Double)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference == typeSystem.String)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unity primitives
|
||||
if (typeReference.FullName == UnityColor_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityColor32_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityVector2_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityVector3_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityVector4_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityQuaternion_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityRay_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeReference.FullName == UnityRay2D_FullName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enum
|
||||
if (typeReference.GetEnumAsInt() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// INetworkSerializable
|
||||
if (typeReference.HasInterface(INetworkSerializable_FullName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Static array
|
||||
if (typeReference.IsArray)
|
||||
{
|
||||
return typeReference.GetElementType().IsSerializable();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TypeReference GetEnumAsInt(this TypeReference typeReference)
|
||||
{
|
||||
if (typeReference.IsArray)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var typeDef = typeReference.Resolve();
|
||||
return typeDef.IsEnum ? typeDef.GetEnumUnderlyingType() : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddError(this List<DiagnosticMessage> diagnostics, string message)
|
||||
{
|
||||
diagnostics.AddError((SequencePoint)null, message);
|
||||
}
|
||||
|
||||
public static void AddError(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message)
|
||||
{
|
||||
diagnostics.AddError(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
|
||||
}
|
||||
|
||||
public static void AddError(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message)
|
||||
{
|
||||
diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Error,
|
||||
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
|
||||
Line = sequencePoint?.StartLine ?? 0,
|
||||
Column = sequencePoint?.StartColumn ?? 0,
|
||||
MessageData = $" - {message}"
|
||||
});
|
||||
}
|
||||
|
||||
public static void RemoveRecursiveReferences(this ModuleDefinition moduleDefinition)
|
||||
{
|
||||
// Weird behavior from Cecil: When importing a reference to a specific implementation of a generic
|
||||
// method, it's importing the main module as a reference into itself. This causes Unity to have issues
|
||||
// when attempting to iterate the assemblies to discover unit tests, as it goes into infinite recursion
|
||||
// and eventually hits a stack overflow. I wasn't able to find any way to stop Cecil from importing the module
|
||||
// into itself, so at the end of it all, we're just going to go back and remove it again.
|
||||
var moduleName = moduleDefinition.Name;
|
||||
if (moduleName.EndsWith(".dll") || moduleName.EndsWith(".exe"))
|
||||
{
|
||||
moduleName = moduleName.Substring(0, moduleName.Length - 4);
|
||||
}
|
||||
|
||||
foreach (var reference in moduleDefinition.AssemblyReferences)
|
||||
{
|
||||
var referenceName = reference.Name.Split(',')[0];
|
||||
if (referenceName.EndsWith(".dll") || referenceName.EndsWith(".exe"))
|
||||
{
|
||||
referenceName = referenceName.Substring(0, referenceName.Length - 4);
|
||||
}
|
||||
|
||||
if (moduleName == referenceName)
|
||||
{
|
||||
try
|
||||
{
|
||||
moduleDefinition.AssemblyReferences.Remove(reference);
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly, out PostProcessorAssemblyResolver assemblyResolver)
|
||||
{
|
||||
assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly);
|
||||
var readerParameters = new ReaderParameters
|
||||
{
|
||||
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData),
|
||||
SymbolReaderProvider = new PortablePdbReaderProvider(),
|
||||
AssemblyResolver = assemblyResolver,
|
||||
ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(),
|
||||
ReadingMode = ReadingMode.Immediate
|
||||
};
|
||||
|
||||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(new MemoryStream(compiledAssembly.InMemoryAssembly.PeData), readerParameters);
|
||||
|
||||
//apparently, it will happen that when we ask to resolve a type that lives inside Unity.Netcode.Runtime, and we
|
||||
//are also postprocessing Unity.Netcode.Runtime, type resolving will fail, because we do not actually try to resolve
|
||||
//inside the assembly we are processing. Let's make sure we do that, so that we can use postprocessor features inside
|
||||
//Unity.Netcode.Runtime itself as well.
|
||||
assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition);
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/CodeGen/CodeGenHelpers.cs.meta
Normal file
11
Editor/CodeGen/CodeGenHelpers.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e5541b3bca0e43b48c2e694fffef5b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
281
Editor/CodeGen/INetworkMessageILPP.cs
Normal file
281
Editor/CodeGen/INetworkMessageILPP.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
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 INetworkMessageILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
if (ImportReferences(mainModule))
|
||||
{
|
||||
var types = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkMessage_FullName) && !t.Resolve().IsAbstract)
|
||||
.ToList();
|
||||
// process `INetworkMessage` types
|
||||
if (types.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CreateModuleInitializer(assemblyDefinition, types);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}");
|
||||
}
|
||||
|
||||
mainModule.RemoveRecursiveReferences();
|
||||
|
||||
// write
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
|
||||
var writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolStream = pdb,
|
||||
WriteSymbols = true
|
||||
};
|
||||
|
||||
assemblyDefinition.Write(pe, writerParameters);
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
|
||||
private TypeReference m_FastBufferReader_TypeRef;
|
||||
private TypeReference m_NetworkContext_TypeRef;
|
||||
private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
|
||||
private MethodReference m_MessagingSystem_MessageHandler_Constructor_TypeRef;
|
||||
private FieldReference m_ILPPMessageProvider___network_message_types_FieldRef;
|
||||
private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef;
|
||||
private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef;
|
||||
private MethodReference m_Type_GetTypeFromHandle_MethodRef;
|
||||
|
||||
private MethodReference m_List_Add_MethodRef;
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(typeof(FastBufferReader));
|
||||
m_NetworkContext_TypeRef = moduleDefinition.ImportReference(typeof(NetworkContext));
|
||||
m_MessagingSystem_MessageHandler_Constructor_TypeRef =
|
||||
moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]);
|
||||
|
||||
var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler);
|
||||
m_MessagingSystem_MessageWithHandler_TypeRef =
|
||||
moduleDefinition.ImportReference(messageWithHandlerType);
|
||||
foreach (var fieldInfo in messageWithHandlerType.GetFields())
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
{
|
||||
case nameof(MessagingSystem.MessageWithHandler.MessageType):
|
||||
m_MessagingSystem_MessageWithHandler_MessageType_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
break;
|
||||
case nameof(MessagingSystem.MessageWithHandler.Handler):
|
||||
m_MessagingSystem_MessageWithHandler_Handler_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var typeType = typeof(Type);
|
||||
foreach (var methodInfo in typeType.GetMethods())
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
{
|
||||
case nameof(Type.GetTypeFromHandle):
|
||||
m_Type_GetTypeFromHandle_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ilppMessageProviderType = typeof(ILPPMessageProvider);
|
||||
foreach (var fieldInfo in ilppMessageProviderType.GetFields(BindingFlags.Static | BindingFlags.NonPublic))
|
||||
{
|
||||
switch (fieldInfo.Name)
|
||||
{
|
||||
case nameof(ILPPMessageProvider.__network_message_types):
|
||||
m_ILPPMessageProvider___network_message_types_FieldRef = moduleDefinition.ImportReference(fieldInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var listType = typeof(List<MessagingSystem.MessageWithHandler>);
|
||||
foreach (var methodInfo in listType.GetMethods())
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
{
|
||||
case nameof(List<MessagingSystem.MessageWithHandler>.Add):
|
||||
m_List_Add_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodReference GetNetworkMessageRecieveHandler(TypeDefinition typeDefinition)
|
||||
{
|
||||
SequencePoint typeSequence = null;
|
||||
foreach (var method in typeDefinition.Methods)
|
||||
{
|
||||
var resolved = method.Resolve();
|
||||
var methodSequence = resolved.DebugInformation.SequencePoints.FirstOrDefault();
|
||||
if (typeSequence == null || methodSequence.StartLine < typeSequence.StartLine)
|
||||
{
|
||||
typeSequence = methodSequence;
|
||||
}
|
||||
|
||||
if (resolved.IsStatic && resolved.IsPublic && resolved.Name == "Receive" && resolved.Parameters.Count == 2
|
||||
&& !resolved.Parameters[0].IsIn
|
||||
&& !resolved.Parameters[0].ParameterType.IsByReference
|
||||
&& resolved.Parameters[0].ParameterType.Resolve() ==
|
||||
m_FastBufferReader_TypeRef.Resolve()
|
||||
&& resolved.Parameters[1].IsIn
|
||||
&& resolved.Parameters[1].ParameterType.IsByReference
|
||||
&& resolved.Parameters[1].ParameterType.GetElementType().Resolve() == m_NetworkContext_TypeRef.Resolve()
|
||||
&& resolved.ReturnType == resolved.Module.TypeSystem.Void)
|
||||
{
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
m_Diagnostics.AddError(typeSequence, $"Class {typeDefinition.FullName} does not implement required method: `public static void Receive(FastBufferReader, in NetworkContext)`");
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
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 void CreateInstructionsToRegisterType(ILProcessor processor, List<Instruction> instructions, TypeReference type, MethodReference receiveMethod)
|
||||
{
|
||||
// MessagingSystem.__network_message_types.Add(new MessagingSystem.MessageWithHandler{MessageType=typeof(type), Handler=type.Receive});
|
||||
processor.Body.Variables.Add(new VariableDefinition(m_MessagingSystem_MessageWithHandler_TypeRef));
|
||||
int messageWithHandlerLocIdx = processor.Body.Variables.Count - 1;
|
||||
|
||||
instructions.Add(processor.Create(OpCodes.Ldsfld, m_ILPPMessageProvider___network_message_types_FieldRef));
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Initobj, m_MessagingSystem_MessageWithHandler_TypeRef));
|
||||
|
||||
// tmp.MessageType = typeof(type);
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Ldtoken, type));
|
||||
instructions.Add(processor.Create(OpCodes.Call, m_Type_GetTypeFromHandle_MethodRef));
|
||||
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_MessageType_FieldRef));
|
||||
|
||||
// tmp.Handler = type.Receive
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Ldnull));
|
||||
|
||||
instructions.Add(processor.Create(OpCodes.Ldftn, receiveMethod));
|
||||
instructions.Add(processor.Create(OpCodes.Newobj, m_MessagingSystem_MessageHandler_Constructor_TypeRef));
|
||||
instructions.Add(processor.Create(OpCodes.Stfld, m_MessagingSystem_MessageWithHandler_Handler_FieldRef));
|
||||
|
||||
// ILPPMessageProvider.__network_message_types.Add(tmp);
|
||||
instructions.Add(processor.Create(OpCodes.Ldloc, messageWithHandlerLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Callvirt, m_List_Add_MethodRef));
|
||||
}
|
||||
|
||||
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
|
||||
// message types in the assembly with MessagingSystem.
|
||||
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
|
||||
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
|
||||
// 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)
|
||||
{
|
||||
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 networkMessageTypes)
|
||||
{
|
||||
var receiveMethod = GetNetworkMessageRecieveHandler(type);
|
||||
if (receiveMethod == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod);
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/CodeGen/INetworkMessageILPP.cs.meta
Normal file
3
Editor/CodeGen/INetworkMessageILPP.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a754504752d649bb8131df8756bd764e
|
||||
timeCreated: 1631666359
|
||||
1310
Editor/CodeGen/NetworkBehaviourILPP.cs
Normal file
1310
Editor/CodeGen/NetworkBehaviourILPP.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Editor/CodeGen/NetworkBehaviourILPP.cs.meta
Normal file
11
Editor/CodeGen/NetworkBehaviourILPP.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf1c8b78182704372820a586c1c91d97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Editor/CodeGen/PostProcessorAssemblyResolver.cs
Normal file
137
Editor/CodeGen/PostProcessorAssemblyResolver.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Mono.Cecil;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal class PostProcessorAssemblyResolver : IAssemblyResolver
|
||||
{
|
||||
private readonly string[] m_AssemblyReferences;
|
||||
private readonly Dictionary<string, AssemblyDefinition> m_AssemblyCache = new Dictionary<string, AssemblyDefinition>();
|
||||
private readonly ICompiledAssembly m_CompiledAssembly;
|
||||
private AssemblyDefinition m_SelfAssembly;
|
||||
|
||||
public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
m_CompiledAssembly = compiledAssembly;
|
||||
m_AssemblyReferences = compiledAssembly.References;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name) => Resolve(name, new ReaderParameters(ReadingMode.Deferred));
|
||||
|
||||
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
|
||||
{
|
||||
lock (m_AssemblyCache)
|
||||
{
|
||||
if (name.Name == m_CompiledAssembly.Name)
|
||||
{
|
||||
return m_SelfAssembly;
|
||||
}
|
||||
|
||||
var fileName = FindFile(name);
|
||||
if (fileName == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var lastWriteTime = File.GetLastWriteTime(fileName);
|
||||
var cacheKey = $"{fileName}{lastWriteTime}";
|
||||
if (m_AssemblyCache.TryGetValue(cacheKey, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
parameters.AssemblyResolver = this;
|
||||
|
||||
var ms = MemoryStreamFor(fileName);
|
||||
var pdb = $"{fileName}.pdb";
|
||||
if (File.Exists(pdb))
|
||||
{
|
||||
parameters.SymbolStream = MemoryStreamFor(pdb);
|
||||
}
|
||||
|
||||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
|
||||
m_AssemblyCache.Add(cacheKey, assemblyDefinition);
|
||||
|
||||
return assemblyDefinition;
|
||||
}
|
||||
}
|
||||
|
||||
private string FindFile(AssemblyNameReference name)
|
||||
{
|
||||
var fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.dll");
|
||||
if (fileName != null)
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// perhaps the type comes from an exe instead
|
||||
fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.exe");
|
||||
if (fileName != null)
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
//Unfortunately the current ICompiledAssembly API only provides direct references.
|
||||
//It is very much possible that a postprocessor ends up investigating a type in a directly
|
||||
//referenced assembly, that contains a field that is not in a directly referenced assembly.
|
||||
//if we don't do anything special for that situation, it will fail to resolve. We should fix this
|
||||
//in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
|
||||
//are always located next to direct references, so we search in all directories of direct references we
|
||||
//got passed, and if we find the file in there, we resolve to it.
|
||||
return m_AssemblyReferences
|
||||
.Select(Path.GetDirectoryName)
|
||||
.Distinct()
|
||||
.Select(parentDir => Path.Combine(parentDir, $"{name.Name}.dll"))
|
||||
.FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
private static MemoryStream MemoryStreamFor(string fileName)
|
||||
{
|
||||
return Retry(10, TimeSpan.FromSeconds(1), () =>
|
||||
{
|
||||
byte[] byteArray;
|
||||
using var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
byteArray = new byte[fileStream.Length];
|
||||
var readLength = fileStream.Read(byteArray, 0, (int)fileStream.Length);
|
||||
if (readLength != fileStream.Length)
|
||||
{
|
||||
throw new InvalidOperationException("File read length is not full length of file.");
|
||||
}
|
||||
|
||||
return new MemoryStream(byteArray);
|
||||
});
|
||||
}
|
||||
|
||||
private static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
|
||||
{
|
||||
try
|
||||
{
|
||||
return func();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (retryCount == 0)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
|
||||
Thread.Sleep(waitTime);
|
||||
|
||||
return Retry(retryCount - 1, waitTime, func);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition)
|
||||
{
|
||||
m_SelfAssembly = assemblyDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/CodeGen/PostProcessorAssemblyResolver.cs.meta
Normal file
11
Editor/CodeGen/PostProcessorAssemblyResolver.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c247f4266b2864eb96e6a9ae6557d31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Editor/CodeGen/PostProcessorReflectionImporter.cs
Normal file
22
Editor/CodeGen/PostProcessorReflectionImporter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal class PostProcessorReflectionImporter : DefaultReflectionImporter
|
||||
{
|
||||
private const string k_SystemPrivateCoreLib = "System.Private.CoreLib";
|
||||
private readonly AssemblyNameReference m_CorrectCorlib;
|
||||
|
||||
public PostProcessorReflectionImporter(ModuleDefinition module) : base(module)
|
||||
{
|
||||
m_CorrectCorlib = module.AssemblyReferences.FirstOrDefault(a => a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == k_SystemPrivateCoreLib);
|
||||
}
|
||||
|
||||
public override AssemblyNameReference ImportReference(AssemblyName reference)
|
||||
{
|
||||
return m_CorrectCorlib != null && reference.Name == k_SystemPrivateCoreLib ? m_CorrectCorlib : base.ImportReference(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/CodeGen/PostProcessorReflectionImporter.cs.meta
Normal file
11
Editor/CodeGen/PostProcessorReflectionImporter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 484e8ad8c4dde382ea67036b32935ef1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
Editor/CodeGen/PostProcessorReflectionImporterProvider.cs
Normal file
12
Editor/CodeGen/PostProcessorReflectionImporterProvider.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal class PostProcessorReflectionImporterProvider : IReflectionImporterProvider
|
||||
{
|
||||
public IReflectionImporter GetReflectionImporter(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
return new PostProcessorReflectionImporter(moduleDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9273a5dad109ab0783891e36c983080
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
131
Editor/CodeGen/RuntimeAccessModifiersILPP.cs
Normal file
131
Editor/CodeGen/RuntimeAccessModifiersILPP.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||
|
||||
namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
internal sealed class RuntimeAccessModifiersILPP : ILPPInterface
|
||||
{
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var unused);
|
||||
if (assemblyDefinition == null)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot read Netcode Runtime assembly definition: {compiledAssembly.Name}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// process
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
if (mainModule != null)
|
||||
{
|
||||
foreach (var typeDefinition in mainModule.Types)
|
||||
{
|
||||
if (!typeDefinition.IsClass)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (typeDefinition.Name)
|
||||
{
|
||||
case nameof(NetworkManager):
|
||||
ProcessNetworkManager(typeDefinition, compiledAssembly.Defines);
|
||||
break;
|
||||
case nameof(NetworkBehaviour):
|
||||
ProcessNetworkBehaviour(typeDefinition);
|
||||
break;
|
||||
case nameof(__RpcParams):
|
||||
typeDefinition.IsPublic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot get main module from Netcode Runtime assembly definition: {compiledAssembly.Name}");
|
||||
}
|
||||
|
||||
// write
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
|
||||
var writerParameters = new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolStream = pdb,
|
||||
WriteSymbols = true
|
||||
};
|
||||
|
||||
assemblyDefinition.Write(pe, writerParameters);
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines)
|
||||
{
|
||||
foreach (var fieldDefinition in typeDefinition.Fields)
|
||||
{
|
||||
if (fieldDefinition.Name == nameof(NetworkManager.__rpc_func_table))
|
||||
{
|
||||
fieldDefinition.IsPublic = true;
|
||||
}
|
||||
|
||||
if (fieldDefinition.Name == nameof(NetworkManager.RpcReceiveHandler))
|
||||
{
|
||||
fieldDefinition.IsPublic = true;
|
||||
}
|
||||
|
||||
if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table))
|
||||
{
|
||||
fieldDefinition.IsPublic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var nestedType in typeDefinition.NestedTypes)
|
||||
{
|
||||
if (nestedType.Name == nameof(NetworkBehaviour.__RpcExecStage))
|
||||
{
|
||||
nestedType.IsNestedFamily = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fieldDefinition in typeDefinition.Fields)
|
||||
{
|
||||
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage))
|
||||
{
|
||||
fieldDefinition.IsFamily = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var methodDefinition in typeDefinition.Methods)
|
||||
{
|
||||
if (methodDefinition.Name == nameof(NetworkBehaviour.__sendServerRpc)
|
||||
|| methodDefinition.Name == nameof(NetworkBehaviour.__sendClientRpc))
|
||||
{
|
||||
methodDefinition.IsFamily = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/CodeGen/RuntimeAccessModifiersILPP.cs.meta
Normal file
11
Editor/CodeGen/RuntimeAccessModifiersILPP.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c9f2f4b03d774432be69d4c2f53bd2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Editor/CodeGen/com.unity.netcode.editor.codegen.asmdef
Normal file
19
Editor/CodeGen/com.unity.netcode.editor.codegen.asmdef
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Unity.Netcode.Editor.CodeGen",
|
||||
"rootNamespace": "Unity.Netcode.Editor.CodeGen",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"Mono.Cecil.dll",
|
||||
"Mono.Cecil.Mdb.dll",
|
||||
"Mono.Cecil.Pdb.dll",
|
||||
"Mono.Cecil.Rocks.dll"
|
||||
],
|
||||
"autoReferenced": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe4fa159f4a96442ba22af67ddf20c65
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Editor/DontShowInTransportDropdownAttribute.cs
Normal file
8
Editor/DontShowInTransportDropdownAttribute.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
public class DontShowInTransportDropdownAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
3
Editor/DontShowInTransportDropdownAttribute.cs.meta
Normal file
3
Editor/DontShowInTransportDropdownAttribute.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f097067d4254dc7ad018d7ad90df7c3
|
||||
timeCreated: 1620386886
|
||||
103
Editor/NetworkAnimatorEditor.cs
Normal file
103
Editor/NetworkAnimatorEditor.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using Unity.Netcode.Components;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
public static class TextUtility
|
||||
{
|
||||
public static GUIContent TextContent(string name, string tooltip)
|
||||
{
|
||||
var newContent = new GUIContent(name);
|
||||
newContent.tooltip = tooltip;
|
||||
return newContent;
|
||||
}
|
||||
|
||||
public static GUIContent TextContent(string name)
|
||||
{
|
||||
return new GUIContent(name);
|
||||
}
|
||||
}
|
||||
|
||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkAnimatorEditor : UnityEditor.Editor
|
||||
{
|
||||
private NetworkAnimator m_AnimSync;
|
||||
[NonSerialized] private bool m_Initialized;
|
||||
private SerializedProperty m_AnimatorProperty;
|
||||
private GUIContent m_AnimatorLabel;
|
||||
|
||||
private void Init()
|
||||
{
|
||||
if (m_Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Initialized = true;
|
||||
m_AnimSync = target as NetworkAnimator;
|
||||
|
||||
m_AnimatorProperty = serializedObject.FindProperty("m_Animator");
|
||||
m_AnimatorLabel = TextUtility.TextContent("Animator", "The Animator component to synchronize.");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Init();
|
||||
serializedObject.Update();
|
||||
DrawControls();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawControls()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_AnimSync.ResetParameterOptions();
|
||||
}
|
||||
|
||||
if (m_AnimSync.Animator == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controller = m_AnimSync.Animator.runtimeAnimatorController as AnimatorController;
|
||||
if (controller != null)
|
||||
{
|
||||
var showWarning = false;
|
||||
EditorGUI.indentLevel += 1;
|
||||
int i = 0;
|
||||
|
||||
foreach (var p in controller.parameters)
|
||||
{
|
||||
if (i >= NetworkAnimator.K_MaxAnimationParams)
|
||||
{
|
||||
showWarning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool oldSend = m_AnimSync.GetParameterAutoSend(i);
|
||||
bool send = EditorGUILayout.Toggle(p.name, oldSend);
|
||||
if (send != oldSend)
|
||||
{
|
||||
m_AnimSync.SetParameterAutoSend(i, send);
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (showWarning)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"NetworkAnimator can only select between the first {NetworkAnimator.K_MaxAnimationParams} parameters in a mecanim controller", MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/NetworkAnimatorEditor.cs.meta
Normal file
11
Editor/NetworkAnimatorEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a32aeecf69a2542469927066f5b88005
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
215
Editor/NetworkBehaviourEditor.cs
Normal file
215
Editor/NetworkBehaviourEditor.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkBehaviour), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkBehaviourEditor : UnityEditor.Editor
|
||||
{
|
||||
private bool m_Initialized;
|
||||
private readonly List<string> m_NetworkVariableNames = new List<string>();
|
||||
private readonly Dictionary<string, FieldInfo> m_NetworkVariableFields = new Dictionary<string, FieldInfo>();
|
||||
private readonly Dictionary<string, object> m_NetworkVariableObjects = new Dictionary<string, object>();
|
||||
|
||||
private GUIContent m_NetworkVariableLabelGuiContent;
|
||||
|
||||
private void Init(MonoScript script)
|
||||
{
|
||||
m_Initialized = true;
|
||||
|
||||
m_NetworkVariableNames.Clear();
|
||||
m_NetworkVariableFields.Clear();
|
||||
m_NetworkVariableObjects.Clear();
|
||||
|
||||
m_NetworkVariableLabelGuiContent = new GUIContent("NetworkVariable", "This variable is a NetworkVariable. It can not be serialized and can only be changed during runtime.");
|
||||
|
||||
var fields = script.GetClass().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var ft = fields[i].FieldType;
|
||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true))
|
||||
{
|
||||
m_NetworkVariableNames.Add(fields[i].Name);
|
||||
m_NetworkVariableFields.Add(fields[i].Name, fields[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderNetworkVariable(int index)
|
||||
{
|
||||
if (!m_NetworkVariableFields.ContainsKey(m_NetworkVariableNames[index]))
|
||||
{
|
||||
serializedObject.Update();
|
||||
var scriptProperty = serializedObject.FindProperty("m_Script");
|
||||
if (scriptProperty == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetScript = scriptProperty.objectReferenceValue as MonoScript;
|
||||
Init(targetScript);
|
||||
}
|
||||
|
||||
object value = m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
if (value == null)
|
||||
{
|
||||
var fieldType = m_NetworkVariableFields[m_NetworkVariableNames[index]].FieldType;
|
||||
var networkVariable = (NetworkVariableBase)Activator.CreateInstance(fieldType, true);
|
||||
m_NetworkVariableFields[m_NetworkVariableNames[index]].SetValue(target, networkVariable);
|
||||
}
|
||||
|
||||
var type = m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target).GetType();
|
||||
var genericType = type.GetGenericArguments()[0];
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (genericType.IsValueType)
|
||||
{
|
||||
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkVariableValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic);
|
||||
var genericMethod = method.MakeGenericMethod(genericType);
|
||||
genericMethod.Invoke(this, new[] { (object)index });
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("Type not renderable");
|
||||
}
|
||||
|
||||
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void RenderNetworkVariableValueType<T>(int index) where T : unmanaged
|
||||
{
|
||||
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target);
|
||||
var type = typeof(T);
|
||||
object val = networkVariable.Value;
|
||||
string name = m_NetworkVariableNames[index];
|
||||
|
||||
var behaviour = (NetworkBehaviour)target;
|
||||
|
||||
// Only server can MODIFY. So allow modification if network is either not running or we are server
|
||||
if (behaviour.IsBehaviourEditable())
|
||||
{
|
||||
if (type == typeof(int))
|
||||
{
|
||||
val = EditorGUILayout.IntField(name, (int)val);
|
||||
}
|
||||
else if (type == typeof(uint))
|
||||
{
|
||||
val = (uint)EditorGUILayout.LongField(name, (long)((uint)val));
|
||||
}
|
||||
else if (type == typeof(short))
|
||||
{
|
||||
val = (short)EditorGUILayout.IntField(name, (int)((short)val));
|
||||
}
|
||||
else if (type == typeof(ushort))
|
||||
{
|
||||
val = (ushort)EditorGUILayout.IntField(name, (int)((ushort)val));
|
||||
}
|
||||
else if (type == typeof(sbyte))
|
||||
{
|
||||
val = (sbyte)EditorGUILayout.IntField(name, (int)((sbyte)val));
|
||||
}
|
||||
else if (type == typeof(byte))
|
||||
{
|
||||
val = (byte)EditorGUILayout.IntField(name, (int)((byte)val));
|
||||
}
|
||||
else if (type == typeof(long))
|
||||
{
|
||||
val = EditorGUILayout.LongField(name, (long)val);
|
||||
}
|
||||
else if (type == typeof(ulong))
|
||||
{
|
||||
val = (ulong)EditorGUILayout.LongField(name, (long)((ulong)val));
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
val = EditorGUILayout.Toggle(name, (bool)val);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
val = EditorGUILayout.TextField(name, (string)val);
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
val = EditorGUILayout.EnumPopup(name, (Enum)val);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("Type not renderable");
|
||||
}
|
||||
|
||||
networkVariable.Value = (T)val;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(name, EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.SelectableLabel(val.ToString(), EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
{
|
||||
serializedObject.Update();
|
||||
var scriptProperty = serializedObject.FindProperty("m_Script");
|
||||
if (scriptProperty == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetScript = scriptProperty.objectReferenceValue as MonoScript;
|
||||
Init(targetScript);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
serializedObject.Update();
|
||||
|
||||
for (int i = 0; i < m_NetworkVariableNames.Count; i++)
|
||||
{
|
||||
RenderNetworkVariable(i);
|
||||
}
|
||||
|
||||
var property = serializedObject.GetIterator();
|
||||
bool expanded = true;
|
||||
while (property.NextVisible(expanded))
|
||||
{
|
||||
if (m_NetworkVariableNames.Contains(property.name))
|
||||
{
|
||||
// Skip rendering of NetworkVars, they have special rendering
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property.propertyType == SerializedPropertyType.ObjectReference)
|
||||
{
|
||||
if (property.name == "m_Script")
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(property, true);
|
||||
|
||||
if (property.name == "m_Script")
|
||||
{
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(property, true);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
expanded = false;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/NetworkBehaviourEditor.cs.meta
Normal file
11
Editor/NetworkBehaviourEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4a5c9c08a4038e449fd259764bc663f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
428
Editor/NetworkManagerEditor.cs
Normal file
428
Editor/NetworkManagerEditor.cs
Normal file
@@ -0,0 +1,428 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkManagerEditor : UnityEditor.Editor
|
||||
{
|
||||
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
|
||||
private static GUIStyle s_CenteredWordWrappedLabelStyle;
|
||||
private static GUIStyle s_HelpBoxStyle;
|
||||
|
||||
// Properties
|
||||
private SerializedProperty m_DontDestroyOnLoadProperty;
|
||||
private SerializedProperty m_RunInBackgroundProperty;
|
||||
private SerializedProperty m_LogLevelProperty;
|
||||
|
||||
// NetworkConfig
|
||||
private SerializedProperty m_NetworkConfigProperty;
|
||||
|
||||
// NetworkConfig fields
|
||||
private SerializedProperty m_PlayerPrefabProperty;
|
||||
private SerializedProperty m_ProtocolVersionProperty;
|
||||
private SerializedProperty m_NetworkTransportProperty;
|
||||
private SerializedProperty m_TickRateProperty;
|
||||
private SerializedProperty m_MaxObjectUpdatesPerTickProperty;
|
||||
private SerializedProperty m_ClientConnectionBufferTimeoutProperty;
|
||||
private SerializedProperty m_ConnectionApprovalProperty;
|
||||
private SerializedProperty m_EnsureNetworkVariableLengthSafetyProperty;
|
||||
private SerializedProperty m_ForceSamePrefabsProperty;
|
||||
private SerializedProperty m_EnableSceneManagementProperty;
|
||||
private SerializedProperty m_RecycleNetworkIdsProperty;
|
||||
private SerializedProperty m_NetworkIdRecycleDelayProperty;
|
||||
private SerializedProperty m_RpcHashSizeProperty;
|
||||
private SerializedProperty m_LoadSceneTimeOutProperty;
|
||||
|
||||
private ReorderableList m_NetworkPrefabsList;
|
||||
|
||||
private NetworkManager m_NetworkManager;
|
||||
private bool m_Initialized;
|
||||
|
||||
private readonly List<Type> m_TransportTypes = new List<Type>();
|
||||
private string[] m_TransportNames = { "Select transport..." };
|
||||
|
||||
private void ReloadTransports()
|
||||
{
|
||||
m_TransportTypes.Clear();
|
||||
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsSubclassOf(typeof(NetworkTransport)) && type.GetCustomAttributes(typeof(DontShowInTransportDropdownAttribute), true).Length == 0)
|
||||
{
|
||||
m_TransportTypes.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_TransportNames = new string[m_TransportTypes.Count + 1];
|
||||
m_TransportNames[0] = "Select transport...";
|
||||
|
||||
for (int i = 0; i < m_TransportTypes.Count; i++)
|
||||
{
|
||||
m_TransportNames[i + 1] = m_TransportTypes[i].Name;
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (m_Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Initialized = true;
|
||||
m_NetworkManager = (NetworkManager)target;
|
||||
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
|
||||
// NetworkConfig properties
|
||||
m_PlayerPrefabProperty = m_NetworkConfigProperty.FindPropertyRelative(nameof(NetworkConfig.PlayerPrefab));
|
||||
m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion");
|
||||
m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport");
|
||||
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
|
||||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
||||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
||||
m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety");
|
||||
m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs");
|
||||
m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement");
|
||||
m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds");
|
||||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
|
||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
|
||||
|
||||
|
||||
ReloadTransports();
|
||||
}
|
||||
|
||||
private void CheckNullProperties()
|
||||
{
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
|
||||
// NetworkConfig properties
|
||||
m_PlayerPrefabProperty = m_NetworkConfigProperty.FindPropertyRelative(nameof(NetworkConfig.PlayerPrefab));
|
||||
m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion");
|
||||
m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport");
|
||||
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
|
||||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
||||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
||||
m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety");
|
||||
m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs");
|
||||
m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement");
|
||||
m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds");
|
||||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
|
||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true);
|
||||
m_NetworkPrefabsList.elementHeightCallback = index =>
|
||||
{
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
var networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
|
||||
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
|
||||
};
|
||||
m_NetworkPrefabsList.drawElementCallback = (rect, index, isActive, isFocused) =>
|
||||
{
|
||||
rect.y += 5;
|
||||
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Prefab));
|
||||
var networkSourceHashProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourceHashToOverride));
|
||||
var networkSourcePrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourcePrefabToOverride));
|
||||
var networkTargetPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.OverridingTargetPrefab));
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
var networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
var networkOverrideEnum = (NetworkPrefabOverride)networkOverrideInt;
|
||||
EditorGUI.LabelField(new Rect(rect.x + rect.width - 70, rect.y, 60, EditorGUIUtility.singleLineHeight), "Override");
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.None)
|
||||
{
|
||||
if (EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), false))
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = (int)NetworkPrefabOverride.Prefab;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), true))
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = 0;
|
||||
networkOverrideEnum = NetworkPrefabOverride.None;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.None)
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 80, EditorGUIUtility.singleLineHeight), networkPrefabProp, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkOverrideProp.enumValueIndex = GUI.Toolbar(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), networkOverrideInt - 1, new[] { "Prefab", "Hash" }) + 1;
|
||||
|
||||
if (networkOverrideEnum == NetworkPrefabOverride.Prefab)
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourcePrefabProp, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourceHashProp, GUIContent.none);
|
||||
}
|
||||
|
||||
rect.y += EditorGUIUtility.singleLineHeight + 5;
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), "Overriding Prefab");
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 110, EditorGUIUtility.singleLineHeight), networkTargetPrefabProp, GUIContent.none);
|
||||
}
|
||||
};
|
||||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
CheckNullProperties();
|
||||
|
||||
#if !MULTIPLAYER_TOOLS
|
||||
DrawInstallMultiplayerToolsTip();
|
||||
#endif
|
||||
|
||||
{
|
||||
var iterator = serializedObject.GetIterator();
|
||||
|
||||
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
|
||||
{
|
||||
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
|
||||
{
|
||||
EditorGUILayout.PropertyField(iterator, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty);
|
||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(m_PlayerPrefabProperty);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
m_NetworkPrefabsList.DoLayoutList();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ProtocolVersionProperty);
|
||||
|
||||
EditorGUILayout.PropertyField(m_NetworkTransportProperty);
|
||||
|
||||
if (m_NetworkTransportProperty.objectReferenceValue == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("You have no transport selected. A transport is required for netcode to work. Which one do you want?", MessageType.Warning);
|
||||
|
||||
int selection = EditorGUILayout.Popup(0, m_TransportNames);
|
||||
|
||||
if (selection > 0)
|
||||
{
|
||||
ReloadTransports();
|
||||
|
||||
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]);
|
||||
|
||||
if (transportComponent == null)
|
||||
{
|
||||
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
|
||||
}
|
||||
|
||||
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
|
||||
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(m_TickRateProperty);
|
||||
|
||||
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty);
|
||||
|
||||
EditorGUILayout.LabelField("Connection", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ConnectionApprovalProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.ConnectionApproval))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Spawning", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty);
|
||||
|
||||
|
||||
EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.RecycleNetworkIds))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty);
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_RpcHashSizeProperty);
|
||||
|
||||
EditorGUILayout.LabelField("Scene Management", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_EnableSceneManagementProperty);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
|
||||
// Start buttons below
|
||||
{
|
||||
string buttonDisabledReasonSuffix = "";
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
buttonDisabledReasonSuffix = ". This can only be done in play mode";
|
||||
GUI.enabled = false;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix)))
|
||||
{
|
||||
m_NetworkManager.StartHost();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix)))
|
||||
{
|
||||
m_NetworkManager.StartServer();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix)))
|
||||
{
|
||||
m_NetworkManager.StartClient();
|
||||
}
|
||||
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string instanceType = string.Empty;
|
||||
|
||||
if (m_NetworkManager.IsHost)
|
||||
{
|
||||
instanceType = "Host";
|
||||
}
|
||||
else if (m_NetworkManager.IsServer)
|
||||
{
|
||||
instanceType = "Server";
|
||||
}
|
||||
else if (m_NetworkManager.IsClient)
|
||||
{
|
||||
instanceType = "Client";
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("You cannot edit the NetworkConfig when a " + instanceType + " is running.", MessageType.Info);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Stop " + instanceType, "Stops the " + instanceType + " instance.")))
|
||||
{
|
||||
m_NetworkManager.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawInstallMultiplayerToolsTip()
|
||||
{
|
||||
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
||||
const string openDocsButtonText = "Open Docs";
|
||||
const string dismissButtonText = "Dismiss";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tutorials/goldenpath_series/goldenpath_foundation_module";
|
||||
const string infoIconName = "console.infoicon";
|
||||
|
||||
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_CenteredWordWrappedLabelStyle == null)
|
||||
{
|
||||
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label);
|
||||
s_CenteredWordWrappedLabelStyle.wordWrap = true;
|
||||
s_CenteredWordWrappedLabelStyle.alignment = TextAnchor.MiddleLeft;
|
||||
}
|
||||
|
||||
if (s_HelpBoxStyle == null)
|
||||
{
|
||||
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox);
|
||||
s_HelpBoxStyle.padding = new RectOffset(10, 10, 10, 10);
|
||||
}
|
||||
|
||||
var openDocsButtonStyle = GUI.skin.button;
|
||||
var dismissButtonStyle = EditorStyles.linkLabel;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.BeginHorizontal(s_HelpBoxStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false), GUILayout.MaxWidth(800));
|
||||
{
|
||||
GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent(infoIconName)), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(true));
|
||||
GUILayout.Space(4);
|
||||
GUILayout.Label(getToolsText, s_CenteredWordWrappedLabelStyle, GUILayout.ExpandHeight(true));
|
||||
|
||||
GUILayout.Space(4);
|
||||
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(openDocsButtonText, openDocsButtonStyle, GUILayout.Width(90), GUILayout.Height(30)))
|
||||
{
|
||||
Application.OpenURL(targetUrl);
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.Space(4);
|
||||
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button(dismissButtonText, dismissButtonStyle, GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
PlayerPrefs.SetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 1);
|
||||
}
|
||||
EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/NetworkManagerEditor.cs.meta
Normal file
11
Editor/NetworkManagerEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74a8f011a324b7642b69098fe57bf635
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
104
Editor/NetworkObjectEditor.cs
Normal file
104
Editor/NetworkObjectEditor.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkObject), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkObjectEditor : UnityEditor.Editor
|
||||
{
|
||||
private bool m_Initialized;
|
||||
private NetworkObject m_NetworkObject;
|
||||
private bool m_ShowObservers;
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (m_Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_Initialized = true;
|
||||
m_NetworkObject = (NetworkObject)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (EditorApplication.isPlaying && !m_NetworkObject.IsSpawned && m_NetworkObject.NetworkManager != null && m_NetworkObject.NetworkManager.IsServer)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField(new GUIContent("Spawn", "Spawns the object across the network"));
|
||||
if (GUILayout.Toggle(false, "Spawn", EditorStyles.miniButtonLeft))
|
||||
{
|
||||
m_NetworkObject.Spawn();
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
else if (EditorApplication.isPlaying && m_NetworkObject.IsSpawned)
|
||||
{
|
||||
var guiEnabled = GUI.enabled;
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.GlobalObjectIdHash), m_NetworkObject.GlobalObjectIdHash.ToString());
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.NetworkObjectId), m_NetworkObject.NetworkObjectId.ToString());
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.OwnerClientId), m_NetworkObject.OwnerClientId.ToString());
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.IsSpawned), m_NetworkObject.IsSpawned);
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.IsLocalPlayer), m_NetworkObject.IsLocalPlayer);
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.IsOwner), m_NetworkObject.IsOwner);
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.IsOwnedByServer), m_NetworkObject.IsOwnedByServer);
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.IsPlayerObject), m_NetworkObject.IsPlayerObject);
|
||||
if (m_NetworkObject.IsSceneObject.HasValue)
|
||||
{
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.IsSceneObject), m_NetworkObject.IsSceneObject.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.IsSceneObject), "null");
|
||||
}
|
||||
EditorGUILayout.Toggle(nameof(NetworkObject.DestroyWithScene), m_NetworkObject.DestroyWithScene);
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.NetworkManager), m_NetworkObject.NetworkManager == null ? "null" : m_NetworkObject.NetworkManager.gameObject.name);
|
||||
GUI.enabled = guiEnabled;
|
||||
|
||||
if (m_NetworkObject.NetworkManager != null && m_NetworkObject.NetworkManager.IsServer)
|
||||
{
|
||||
m_ShowObservers = EditorGUILayout.Foldout(m_ShowObservers, "Observers");
|
||||
|
||||
if (m_ShowObservers)
|
||||
{
|
||||
HashSet<ulong>.Enumerator observerClientIds = m_NetworkObject.GetObservers();
|
||||
|
||||
EditorGUI.indentLevel += 1;
|
||||
|
||||
while (observerClientIds.MoveNext())
|
||||
{
|
||||
if (m_NetworkObject.NetworkManager.ConnectedClients[observerClientIds.Current].PlayerObject != null)
|
||||
{
|
||||
EditorGUILayout.ObjectField($"ClientId: {observerClientIds.Current}", m_NetworkObject.NetworkManager.ConnectedClients[observerClientIds.Current].PlayerObject, typeof(GameObject), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.TextField($"ClientId: {observerClientIds.Current}", EditorStyles.label);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
var guiEnabled = GUI.enabled;
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.GlobalObjectIdHash), m_NetworkObject.GlobalObjectIdHash.ToString());
|
||||
EditorGUILayout.TextField(nameof(NetworkObject.NetworkManager), m_NetworkObject.NetworkManager == null ? "null" : m_NetworkObject.NetworkManager.gameObject.name);
|
||||
GUI.enabled = guiEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/NetworkObjectEditor.cs.meta
Normal file
11
Editor/NetworkObjectEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36e4b519d287d0f4e8bfb7d088a9275f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
132
Editor/NetworkTransformEditor.cs
Normal file
132
Editor/NetworkTransformEditor.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Unity.Netcode.Components;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkTransform))]
|
||||
public class NetworkTransformEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty m_SyncPositionXProperty;
|
||||
private SerializedProperty m_SyncPositionYProperty;
|
||||
private SerializedProperty m_SyncPositionZProperty;
|
||||
private SerializedProperty m_SyncRotationXProperty;
|
||||
private SerializedProperty m_SyncRotationYProperty;
|
||||
private SerializedProperty m_SyncRotationZProperty;
|
||||
private SerializedProperty m_SyncScaleXProperty;
|
||||
private SerializedProperty m_SyncScaleYProperty;
|
||||
private SerializedProperty m_SyncScaleZProperty;
|
||||
private SerializedProperty m_PositionThresholdProperty;
|
||||
private SerializedProperty m_RotAngleThresholdProperty;
|
||||
private SerializedProperty m_ScaleThresholdProperty;
|
||||
private SerializedProperty m_InLocalSpaceProperty;
|
||||
private SerializedProperty m_InterpolateProperty;
|
||||
|
||||
private static int s_ToggleOffset = 45;
|
||||
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5;
|
||||
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
|
||||
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation");
|
||||
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
|
||||
m_SyncPositionYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionY));
|
||||
m_SyncPositionZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionZ));
|
||||
m_SyncRotationXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncRotAngleX));
|
||||
m_SyncRotationYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncRotAngleY));
|
||||
m_SyncRotationZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncRotAngleZ));
|
||||
m_SyncScaleXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncScaleX));
|
||||
m_SyncScaleYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncScaleY));
|
||||
m_SyncScaleZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncScaleZ));
|
||||
m_PositionThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionThreshold));
|
||||
m_RotAngleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotAngleThreshold));
|
||||
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold));
|
||||
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace));
|
||||
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel);
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, s_MaxRowWidth, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorStyles.numberField);
|
||||
var ctid = GUIUtility.GetControlID(7231, FocusType.Keyboard, rect);
|
||||
|
||||
rect = EditorGUI.PrefixLabel(rect, ctid, s_PositionLabel);
|
||||
rect.width = s_ToggleOffset;
|
||||
|
||||
m_SyncPositionXProperty.boolValue = EditorGUI.ToggleLeft(rect, "X", m_SyncPositionXProperty.boolValue);
|
||||
rect.x += s_ToggleOffset;
|
||||
m_SyncPositionYProperty.boolValue = EditorGUI.ToggleLeft(rect, "Y", m_SyncPositionYProperty.boolValue);
|
||||
rect.x += s_ToggleOffset;
|
||||
m_SyncPositionZProperty.boolValue = EditorGUI.ToggleLeft(rect, "Z", m_SyncPositionZProperty.boolValue);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, s_MaxRowWidth, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorStyles.numberField);
|
||||
var ctid = GUIUtility.GetControlID(7231, FocusType.Keyboard, rect);
|
||||
|
||||
rect = EditorGUI.PrefixLabel(rect, ctid, s_RotationLabel);
|
||||
rect.width = s_ToggleOffset;
|
||||
|
||||
m_SyncRotationXProperty.boolValue = EditorGUI.ToggleLeft(rect, "X", m_SyncRotationXProperty.boolValue);
|
||||
rect.x += s_ToggleOffset;
|
||||
m_SyncRotationYProperty.boolValue = EditorGUI.ToggleLeft(rect, "Y", m_SyncRotationYProperty.boolValue);
|
||||
rect.x += s_ToggleOffset;
|
||||
m_SyncRotationZProperty.boolValue = EditorGUI.ToggleLeft(rect, "Z", m_SyncRotationZProperty.boolValue);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, s_MaxRowWidth, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorStyles.numberField);
|
||||
var ctid = GUIUtility.GetControlID(7231, FocusType.Keyboard, rect);
|
||||
|
||||
rect = EditorGUI.PrefixLabel(rect, ctid, s_ScaleLabel);
|
||||
rect.width = s_ToggleOffset;
|
||||
|
||||
m_SyncScaleXProperty.boolValue = EditorGUI.ToggleLeft(rect, "X", m_SyncScaleXProperty.boolValue);
|
||||
rect.x += s_ToggleOffset;
|
||||
m_SyncScaleYProperty.boolValue = EditorGUI.ToggleLeft(rect, "Y", m_SyncScaleYProperty.boolValue);
|
||||
rect.x += s_ToggleOffset;
|
||||
m_SyncScaleZProperty.boolValue = EditorGUI.ToggleLeft(rect, "Z", m_SyncScaleZProperty.boolValue);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Thresholds", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_PositionThresholdProperty);
|
||||
EditorGUILayout.PropertyField(m_RotAngleThresholdProperty);
|
||||
EditorGUILayout.PropertyField(m_ScaleThresholdProperty);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
||||
EditorGUILayout.PropertyField(m_InterpolateProperty);
|
||||
|
||||
// if rigidbody is present but network rigidbody is not present
|
||||
var go = ((NetworkTransform)target).gameObject;
|
||||
if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" +
|
||||
"Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning);
|
||||
}
|
||||
|
||||
if (go.TryGetComponent<Rigidbody2D>(out _) && go.TryGetComponent<NetworkRigidbody2D>(out _) == false)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" +
|
||||
"Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/NetworkTransformEditor.cs.meta
Normal file
11
Editor/NetworkTransformEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17891488cb32d4243b0710884463d70f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
Editor/com.unity.netcode.editor.asmdef
Normal file
25
Editor/com.unity.netcode.editor.asmdef
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Unity.Netcode.Editor",
|
||||
"rootNamespace": "Unity.Netcode.Editor",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Netcode.Components"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
"expression": "",
|
||||
"define": "MULTIPLAYER_TOOLS"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Editor/com.unity.netcode.editor.asmdef.meta
Normal file
7
Editor/com.unity.netcode.editor.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4f5bf029cebb64f983b7bdc29f62a1
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Unity Technologies
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
7
LICENSE.md.meta
Normal file
7
LICENSE.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a850895ba88aa114f804fefa31faf4d2
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
||||
[](https://docs-multiplayer.unity3d.com/) [](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs.unity3d.com/Packages/com.unity.transport@1.0/manual/index.html).
|
||||
|
||||
### Getting Started
|
||||
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
|
||||
|
||||
### Community and Feedback
|
||||
For general questions, networking advice or discussions about Netcode for GameObjects, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/).
|
||||
7
README.md.meta
Normal file
7
README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a8193086d1cd7d4dadc6a324430350b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime.meta
Normal file
8
Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c83905999e4e71246b5a3ebf2565faf7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
12
Runtime/AssemblyInfo.cs
Normal file
12
Runtime/AssemblyInfo.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
#endif
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
|
||||
11
Runtime/AssemblyInfo.cs.meta
Normal file
11
Runtime/AssemblyInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9fd74adf4a0f6e479be3978543dc6a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Collections.meta
Normal file
8
Runtime/Collections.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2ef964afcae91248b2298b479ed1b53
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
77
Runtime/Collections/FixedQueue.cs
Normal file
77
Runtime/Collections/FixedQueue.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue with a fixed size
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queue</typeparam>
|
||||
public sealed class FixedQueue<T>
|
||||
{
|
||||
private readonly T[] m_Queue;
|
||||
private int m_QueueCount = 0;
|
||||
private int m_QueueStart;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of enqueued objects
|
||||
/// </summary>
|
||||
public int Count => m_QueueCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FixedQueue with a given size
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The size of the queue</param>
|
||||
public FixedQueue(int maxSize)
|
||||
{
|
||||
m_Queue = new T[maxSize];
|
||||
m_QueueStart = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an object
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
public bool Enqueue(T t)
|
||||
{
|
||||
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
|
||||
if (++m_QueueCount > m_Queue.Length)
|
||||
{
|
||||
--m_QueueCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (--m_QueueCount == -1)
|
||||
{
|
||||
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
|
||||
}
|
||||
|
||||
T res = m_Queue[m_QueueStart];
|
||||
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
}
|
||||
}
|
||||
11
Runtime/Collections/FixedQueue.cs.meta
Normal file
11
Runtime/Collections/FixedQueue.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8514b4eca0c7044d9b92faf9407ec93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Configuration.meta
Normal file
8
Runtime/Configuration.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3cc7700dfd03ee4397858710461d179
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Runtime/Configuration/HashSize.cs
Normal file
20
Runtime/Configuration/HashSize.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the length of a var int encoded hash
|
||||
/// Note that the HashSize does not say anything about the actual final output due to the var int encoding
|
||||
/// It just says how many bytes the maximum will be
|
||||
/// </summary>
|
||||
public enum HashSize : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Four byte hash
|
||||
/// </summary>
|
||||
VarIntFourBytes,
|
||||
|
||||
/// <summary>
|
||||
/// Eight byte hash
|
||||
/// </summary>
|
||||
VarIntEightBytes
|
||||
}
|
||||
}
|
||||
11
Runtime/Configuration/HashSize.cs.meta
Normal file
11
Runtime/Configuration/HashSize.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ae94548754e0a0409da85c0e3235bb4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
269
Runtime/Configuration/NetworkConfig.cs
Normal file
269
Runtime/Configuration/NetworkConfig.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration object used to start server, client and hosts
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol version. Different versions doesn't talk to each other.
|
||||
/// </summary>
|
||||
[Tooltip("Use this to make two builds incompatible with each other")]
|
||||
public ushort ProtocolVersion = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The transport hosts the sever uses
|
||||
/// </summary>
|
||||
[Tooltip("The NetworkTransport to use")]
|
||||
public NetworkTransport NetworkTransport = null;
|
||||
|
||||
/// <summary>
|
||||
/// The default player prefab
|
||||
/// </summary>
|
||||
[Tooltip("When set, NetworkManager will automatically create and spawn the assigned player prefab. This can be overridden by adding it to the NetworkPrefabs list and selecting override.")]
|
||||
public GameObject PlayerPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// A list of prefabs that can be dynamically spawned.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Tooltip("The prefabs that can be spawned across the network")]
|
||||
internal List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override.
|
||||
/// Generated at runtime and OnValidate
|
||||
/// </summary>
|
||||
internal Dictionary<uint, NetworkPrefab> NetworkPrefabOverrideLinks = new Dictionary<uint, NetworkPrefab>();
|
||||
|
||||
internal Dictionary<uint, uint> OverrideToNetworkPrefab = new Dictionary<uint, uint>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The tickrate of network ticks. This value controls how often netcode runs user code and sends out data.
|
||||
/// </summary>
|
||||
[Tooltip("The tickrate. This value controls how often netcode runs user code and sends out data. The value is in 'ticks per seconds' which means a value of 50 will result in 50 ticks being executed per second or a fixed delta time of 0.02.")]
|
||||
public uint TickRate = 30;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait for handshake to complete before timing out a client
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
|
||||
public int ClientConnectionBufferTimeout = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to use connection approval
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not to force clients to be approved before they connect")]
|
||||
public bool ConnectionApproval = false;
|
||||
|
||||
/// <summary>
|
||||
/// The data to send during connection which can be used to decide on if a client should get accepted
|
||||
/// </summary>
|
||||
[Tooltip("The connection data sent along with connection requests")]
|
||||
public byte[] ConnectionData = new byte[0];
|
||||
|
||||
/// <summary>
|
||||
/// If your logic uses the NetworkTime, this should probably be turned off. If however it's needed to maximize accuracy, this is recommended to be turned on
|
||||
/// </summary>
|
||||
[Tooltip("Enable this to re-sync the NetworkTime after the initial sync")]
|
||||
public bool EnableTimeResync = false;
|
||||
|
||||
/// <summary>
|
||||
/// If time re-sync is turned on, this specifies the interval between syncs in seconds.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds between re-syncs of NetworkTime, if enabled")]
|
||||
public int TimeResyncInterval = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to ensure that NetworkVariables can be read even if a client accidentally writes where its not allowed to. This costs some CPU and bandwidth.
|
||||
/// </summary>
|
||||
[Tooltip("Ensures that NetworkVariables can be read even if a client accidental writes where its not allowed to. This will cost some CPU time and bandwidth")]
|
||||
public bool EnsureNetworkVariableLengthSafety = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enables scene management. This will allow network scene switches and automatic scene difference corrections upon connect.
|
||||
/// SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.
|
||||
/// </summary>
|
||||
[Tooltip("Enables scene management. This will allow network scene switches and automatic scene difference corrections upon connect.\n" +
|
||||
"SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.")]
|
||||
public bool EnableSceneManagement = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the netcode should check for differences in the prefabs at connection.
|
||||
/// If you dynamically add prefabs at runtime, turn this OFF
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not the netcode should check for differences in the prefab lists at connection")]
|
||||
public bool ForceSamePrefabs = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, NetworkIds will be reused after the NetworkIdRecycleDelay.
|
||||
/// </summary>
|
||||
[Tooltip("If true, NetworkIds will be reused after the NetworkIdRecycleDelay")]
|
||||
public bool RecycleNetworkIds = true;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds a NetworkId has to be unused in order for it to be reused.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds a NetworkId has to unused in order for it to be reused")]
|
||||
public float NetworkIdRecycleDelay = 120f;
|
||||
|
||||
/// <summary>
|
||||
/// Decides how many bytes to use for Rpc messaging. Leave this to 2 bytes unless you are facing hash collisions
|
||||
/// </summary>
|
||||
[Tooltip("The maximum amount of bytes to use for RPC messages.")]
|
||||
public HashSize RpcHashSize = HashSize.VarIntFourBytes;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait for all clients to load or unload a requested scene
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for all clients to load or unload a requested scene (only when EnableSceneManagement is enabled)")]
|
||||
public int LoadSceneTimeOut = 120;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
|
||||
public float MessageBufferTimeout = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable network logs.
|
||||
/// </summary>
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
|
||||
/// </summary>
|
||||
public bool UseSnapshotDelta { get; } = false;
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
|
||||
/// </summary>
|
||||
public bool UseSnapshotSpawn { get; } = false;
|
||||
/// <summary>
|
||||
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
|
||||
/// </summary>
|
||||
public int SnapshotMaxSpawnUsage { get; } = 1200;
|
||||
|
||||
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
|
||||
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>
|
||||
/// <returns></returns>
|
||||
public string ToBase64()
|
||||
{
|
||||
NetworkConfig config = this;
|
||||
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteValueSafe(config.ProtocolVersion);
|
||||
writer.WriteValueSafe(config.TickRate);
|
||||
writer.WriteValueSafe(config.ClientConnectionBufferTimeout);
|
||||
writer.WriteValueSafe(config.ConnectionApproval);
|
||||
writer.WriteValueSafe(config.LoadSceneTimeOut);
|
||||
writer.WriteValueSafe(config.EnableTimeResync);
|
||||
writer.WriteValueSafe(config.EnsureNetworkVariableLengthSafety);
|
||||
writer.WriteValueSafe(config.RpcHashSize);
|
||||
writer.WriteValueSafe(ForceSamePrefabs);
|
||||
writer.WriteValueSafe(EnableSceneManagement);
|
||||
writer.WriteValueSafe(RecycleNetworkIds);
|
||||
writer.WriteValueSafe(NetworkIdRecycleDelay);
|
||||
writer.WriteValueSafe(EnableNetworkLogs);
|
||||
|
||||
// Allocates
|
||||
return Convert.ToBase64String(writer.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the NetworkConfig data with that from a base64 encoded version
|
||||
/// </summary>
|
||||
/// <param name="base64">The base64 encoded version</param>
|
||||
public void FromBase64(string base64)
|
||||
{
|
||||
NetworkConfig config = this;
|
||||
byte[] binary = Convert.FromBase64String(base64);
|
||||
using var reader = new FastBufferReader(binary, Allocator.Temp);
|
||||
using (reader)
|
||||
{
|
||||
reader.ReadValueSafe(out config.ProtocolVersion);
|
||||
reader.ReadValueSafe(out config.TickRate);
|
||||
reader.ReadValueSafe(out config.ClientConnectionBufferTimeout);
|
||||
reader.ReadValueSafe(out config.ConnectionApproval);
|
||||
reader.ReadValueSafe(out config.LoadSceneTimeOut);
|
||||
reader.ReadValueSafe(out config.EnableTimeResync);
|
||||
reader.ReadValueSafe(out config.EnsureNetworkVariableLengthSafety);
|
||||
reader.ReadValueSafe(out config.RpcHashSize);
|
||||
reader.ReadValueSafe(out config.ForceSamePrefabs);
|
||||
reader.ReadValueSafe(out config.EnableSceneManagement);
|
||||
reader.ReadValueSafe(out config.RecycleNetworkIds);
|
||||
reader.ReadValueSafe(out config.NetworkIdRecycleDelay);
|
||||
reader.ReadValueSafe(out config.EnableNetworkLogs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ulong? m_ConfigHash = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a SHA256 hash of parts of the NetworkConfig instance
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <returns></returns>
|
||||
public ulong GetConfig(bool cache = true)
|
||||
{
|
||||
if (m_ConfigHash != null && cache)
|
||||
{
|
||||
return m_ConfigHash.Value;
|
||||
}
|
||||
|
||||
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
using (writer)
|
||||
{
|
||||
writer.WriteValueSafe(ProtocolVersion);
|
||||
writer.WriteValueSafe(NetworkConstants.PROTOCOL_VERSION);
|
||||
|
||||
if (ForceSamePrefabs)
|
||||
{
|
||||
var sortedDictionary = NetworkPrefabOverrideLinks.OrderBy(x => x.Key);
|
||||
foreach (var sortedEntry in sortedDictionary)
|
||||
|
||||
{
|
||||
writer.WriteValueSafe(sortedEntry.Key);
|
||||
}
|
||||
}
|
||||
writer.WriteValueSafe(ConnectionApproval);
|
||||
writer.WriteValueSafe(ForceSamePrefabs);
|
||||
writer.WriteValueSafe(EnableSceneManagement);
|
||||
writer.WriteValueSafe(EnsureNetworkVariableLengthSafety);
|
||||
writer.WriteValueSafe(RpcHashSize);
|
||||
|
||||
if (cache)
|
||||
{
|
||||
m_ConfigHash = XXHash.Hash64(writer.ToArray());
|
||||
return m_ConfigHash.Value;
|
||||
}
|
||||
|
||||
return XXHash.Hash64(writer.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a SHA256 hash with the current NetworkConfig instances hash
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public bool CompareConfig(ulong hash)
|
||||
{
|
||||
return hash == GetConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Runtime/Configuration/NetworkConfig.cs.meta
Normal file
11
Runtime/Configuration/NetworkConfig.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b056e7faa4d1cb4aac7bc304c765c3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
Runtime/Configuration/NetworkConstants.cs
Normal file
10
Runtime/Configuration/NetworkConstants.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class containing network constants
|
||||
/// </summary>
|
||||
internal static class NetworkConstants
|
||||
{
|
||||
internal const string PROTOCOL_VERSION = "14.0.0";
|
||||
}
|
||||
}
|
||||
11
Runtime/Configuration/NetworkConstants.cs.meta
Normal file
11
Runtime/Configuration/NetworkConstants.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dae82900f88f6bb4a90c29d431f2b45a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Runtime/Configuration/NetworkPrefab.cs
Normal file
45
Runtime/Configuration/NetworkPrefab.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal enum NetworkPrefabOverride
|
||||
{
|
||||
None,
|
||||
Prefab,
|
||||
Hash
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class that represents a NetworkPrefab
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class NetworkPrefab
|
||||
{
|
||||
/// <summary>
|
||||
/// The override setttings for this NetworkPrefab
|
||||
/// </summary>
|
||||
public NetworkPrefabOverride Override;
|
||||
|
||||
/// <summary>
|
||||
/// Asset reference of the network prefab
|
||||
/// </summary>
|
||||
public GameObject Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// Used when prefab is selected for the source prefab to override value (i.e. direct reference, the prefab is within the same project)
|
||||
/// We keep a separate value as the user might want to have something different than the default Prefab for the SourcePrefabToOverride
|
||||
/// </summary>
|
||||
public GameObject SourcePrefabToOverride;
|
||||
|
||||
/// <summary>
|
||||
/// Used when hash is selected for the source prefab to override value (i.e. a direct reference is not possible such as in a multi-project pattern)
|
||||
/// </summary>
|
||||
public uint SourceHashToOverride;
|
||||
|
||||
/// <summary>
|
||||
/// The prefab to replace (override) the source prefab with
|
||||
/// </summary>
|
||||
public GameObject OverridingTargetPrefab;
|
||||
}
|
||||
}
|
||||
11
Runtime/Configuration/NetworkPrefab.cs.meta
Normal file
11
Runtime/Configuration/NetworkPrefab.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10e0511afda4e7743b2cd7c9cf95e0ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Connection.meta
Normal file
8
Runtime/Connection.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7155f1167eb96846aab2132b37c2815
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
Runtime/Connection/NetworkClient.cs
Normal file
25
Runtime/Connection/NetworkClient.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A NetworkClient
|
||||
/// </summary>
|
||||
public class NetworkClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The ClientId of the NetworkClient
|
||||
/// </summary>
|
||||
public ulong ClientId;
|
||||
|
||||
/// <summary>
|
||||
/// The PlayerObject of the Client
|
||||
/// </summary>
|
||||
public NetworkObject PlayerObject;
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkObject's owned by this Client
|
||||
/// </summary>
|
||||
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
|
||||
}
|
||||
}
|
||||
11
Runtime/Connection/NetworkClient.cs.meta
Normal file
11
Runtime/Connection/NetworkClient.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23ca4d14911834b41a761c62fb23773e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Runtime/Connection/PendingClient.cs
Normal file
33
Runtime/Connection/PendingClient.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A class representing a client that is currently in the process of connecting
|
||||
/// </summary>
|
||||
public class PendingClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The ClientId of the client
|
||||
/// </summary>
|
||||
public ulong ClientId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The state of the connection process for the client
|
||||
/// </summary>
|
||||
public State ConnectionState { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The states of a connection
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
/// <summary>
|
||||
/// Waiting for client to send it's initial connection request
|
||||
/// </summary>
|
||||
PendingConnection,
|
||||
/// <summary>
|
||||
/// Waiting for client connection request to be approved
|
||||
/// </summary>
|
||||
PendingApproval
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Connection/PendingClient.cs.meta
Normal file
11
Runtime/Connection/PendingClient.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ffbc5b303de84a4196e24f503752218
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Core.meta
Normal file
8
Runtime/Core.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb92f4010a5924b408aca753b55bd5ae
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
388
Runtime/Core/IndexAllocator.cs
Normal file
388
Runtime/Core/IndexAllocator.cs
Normal file
@@ -0,0 +1,388 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct IndexAllocatorEntry
|
||||
{
|
||||
internal int Pos; // Position where the memory of this slot is
|
||||
internal int Length; // Length of the memory allocated to this slot
|
||||
internal int Next; // Next and Prev define the order of the slots in the buffer
|
||||
internal int Prev;
|
||||
internal bool Free; // Whether this is a free slot
|
||||
}
|
||||
|
||||
internal class IndexAllocator
|
||||
{
|
||||
private const int k_NotSet = -1;
|
||||
private readonly int m_MaxSlot; // Maximum number of sections (free or not) in the buffer
|
||||
private readonly int m_BufferSize; // Size of the buffer we allocated into
|
||||
private int m_LastSlot = 0; // Last allocated slot
|
||||
private IndexAllocatorEntry[] m_Slots; // Array of slots
|
||||
private int[] m_IndexToSlot; // Mapping from the client's index to the slot index
|
||||
|
||||
internal IndexAllocator(int bufferSize, int maxSlot)
|
||||
{
|
||||
m_MaxSlot = maxSlot;
|
||||
m_BufferSize = bufferSize;
|
||||
m_Slots = new IndexAllocatorEntry[m_MaxSlot];
|
||||
m_IndexToSlot = new int[m_MaxSlot];
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset this IndexAllocator to an empty one, with the same sized buffer and slots
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
// todo: could be made faster, for example by having a last index
|
||||
// and not needing valid stuff past it
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
m_Slots[i].Free = true;
|
||||
m_Slots[i].Next = i + 1;
|
||||
m_Slots[i].Prev = i - 1;
|
||||
m_Slots[i].Pos = m_BufferSize;
|
||||
m_Slots[i].Length = 0;
|
||||
|
||||
m_IndexToSlot[i] = k_NotSet;
|
||||
}
|
||||
|
||||
m_Slots[0].Pos = 0;
|
||||
m_Slots[0].Length = m_BufferSize;
|
||||
m_Slots[0].Prev = k_NotSet;
|
||||
m_Slots[m_MaxSlot - 1].Next = k_NotSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of memory used
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the amount of memory used, starting at 0, ending after the last used slot
|
||||
/// </returns>
|
||||
internal int Range
|
||||
{
|
||||
get
|
||||
{
|
||||
// when the whole buffer is free, m_LastSlot points to an empty slot
|
||||
if (m_Slots[m_LastSlot].Free)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// otherwise return the end of the last slot used
|
||||
return m_Slots[m_LastSlot].Pos + m_Slots[m_LastSlot].Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a slot with "size" position, for index "index"
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Used in Deallocate to identify which slot</param>
|
||||
/// <param name="size">The size required. </param>
|
||||
/// <param name="pos">Returns the position to use in the buffer </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't enough memory available or no slots are large enough
|
||||
/// </returns>
|
||||
internal bool Allocate(int index, int size, out int pos)
|
||||
{
|
||||
pos = 0;
|
||||
// size must be positive, index must be within range
|
||||
if (size < 0 || index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// refuse allocation if the index is already in use
|
||||
if (m_IndexToSlot[index] != k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: this is the slowest part
|
||||
// improvement 1: list of free blocks (minor)
|
||||
// improvement 2: heap of free blocks
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
if (m_Slots[i].Free && m_Slots[i].Length >= size)
|
||||
{
|
||||
m_IndexToSlot[index] = i;
|
||||
|
||||
int leftOver = m_Slots[i].Length - size;
|
||||
int next = m_Slots[i].Next;
|
||||
if (m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[next].Pos -= leftOver;
|
||||
m_Slots[next].Length += leftOver;
|
||||
}
|
||||
else
|
||||
{
|
||||
int add = MoveSlotAfter(i);
|
||||
|
||||
m_Slots[add].Pos = m_Slots[i].Pos + size;
|
||||
m_Slots[add].Length = m_Slots[i].Length - size;
|
||||
}
|
||||
|
||||
m_Slots[i].Free = false;
|
||||
m_Slots[i].Length = size;
|
||||
|
||||
pos = m_Slots[i].Pos;
|
||||
|
||||
// if we allocate past the current range, we are the last slot
|
||||
if (m_Slots[i].Pos + m_Slots[i].Length > Range)
|
||||
{
|
||||
m_LastSlot = i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deallocate a slot
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Same index used in Allocate </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't an allocated slot at this index
|
||||
/// </returns>
|
||||
internal bool Deallocate(int index)
|
||||
{
|
||||
// size must be positive, index must be within range
|
||||
if (index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int slot = m_IndexToSlot[index];
|
||||
|
||||
if (slot == k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[slot].Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Slots[slot].Free = true;
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
// if previous slot was free, merge and grow
|
||||
if (prev != k_NotSet && m_Slots[prev].Free)
|
||||
{
|
||||
m_Slots[prev].Length += m_Slots[slot].Length;
|
||||
m_Slots[slot].Length = 0;
|
||||
|
||||
// if the slot we're merging was the last one, the last one is now the one we merged with
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = prev;
|
||||
}
|
||||
|
||||
// todo: verify what this does on full or nearly full cases
|
||||
MoveSlotToEnd(slot);
|
||||
slot = prev;
|
||||
}
|
||||
|
||||
next = m_Slots[slot].Next;
|
||||
|
||||
// merge with next slot if it is free
|
||||
if (next != k_NotSet && m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[slot].Length += m_Slots[next].Length;
|
||||
m_Slots[next].Length = 0;
|
||||
MoveSlotToEnd(next);
|
||||
}
|
||||
|
||||
// if we just deallocate the last one, we need to move last back
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = m_Slots[m_LastSlot].Prev;
|
||||
// if there's nothing allocated anymore, use 0
|
||||
if (m_LastSlot == k_NotSet)
|
||||
{
|
||||
m_LastSlot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// mark the index as available
|
||||
m_IndexToSlot[index] = k_NotSet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Take a slot at the end and link it to go just after "slot".
|
||||
// Used when allocating part of a slot and we need an entry for the rest
|
||||
// Returns the slot that was picked
|
||||
private int MoveSlotAfter(int slot)
|
||||
{
|
||||
int ret = m_Slots[m_MaxSlot - 1].Prev;
|
||||
int p0 = m_Slots[ret].Prev;
|
||||
|
||||
m_Slots[p0].Next = m_MaxSlot - 1;
|
||||
m_Slots[m_MaxSlot - 1].Prev = p0;
|
||||
|
||||
int p1 = m_Slots[slot].Next;
|
||||
m_Slots[slot].Next = ret;
|
||||
m_Slots[p1].Prev = ret;
|
||||
|
||||
m_Slots[ret].Prev = slot;
|
||||
m_Slots[ret].Next = p1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Move the slot "slot" to the end of the list.
|
||||
// Used when merging two slots, that gives us an extra entry at the end
|
||||
private void MoveSlotToEnd(int slot)
|
||||
{
|
||||
// if we're already there
|
||||
if (m_Slots[slot].Next == k_NotSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
m_Slots[prev].Next = next;
|
||||
if (next != k_NotSet)
|
||||
{
|
||||
m_Slots[next].Prev = prev;
|
||||
}
|
||||
|
||||
int p0 = m_Slots[m_MaxSlot - 1].Prev;
|
||||
|
||||
m_Slots[p0].Next = slot;
|
||||
m_Slots[slot].Next = m_MaxSlot - 1;
|
||||
|
||||
m_Slots[m_MaxSlot - 1].Prev = slot;
|
||||
m_Slots[slot].Prev = p0;
|
||||
|
||||
m_Slots[slot].Pos = m_BufferSize;
|
||||
}
|
||||
|
||||
// runs a bunch of consistency check on the Allocator
|
||||
internal bool Verify()
|
||||
{
|
||||
int pos = k_NotSet;
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
int endPos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int prev = pos;
|
||||
if (pos != k_NotSet)
|
||||
{
|
||||
pos = m_Slots[pos].Next;
|
||||
if (pos == k_NotSet)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Prev != prev)
|
||||
{
|
||||
// the previous is not correct
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Length < 0)
|
||||
{
|
||||
// Length should be positive
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev != k_NotSet && m_Slots[prev].Free && m_Slots[pos].Free && m_Slots[pos].Length > 0)
|
||||
{
|
||||
// should not have two consecutive free slots
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Pos != total)
|
||||
{
|
||||
// slots should all line up nicely
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Slots[pos].Free)
|
||||
{
|
||||
endPos = m_Slots[pos].Pos + m_Slots[pos].Length;
|
||||
}
|
||||
|
||||
total += m_Slots[pos].Length;
|
||||
count++;
|
||||
|
||||
} while (pos != k_NotSet);
|
||||
|
||||
if (count != m_MaxSlot)
|
||||
{
|
||||
// some slots were lost
|
||||
return false;
|
||||
}
|
||||
|
||||
if (total != m_BufferSize)
|
||||
{
|
||||
// total buffer should be accounted for
|
||||
return false;
|
||||
}
|
||||
|
||||
if (endPos != Range)
|
||||
{
|
||||
// end position should match reported end position
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Debug display the allocator structure
|
||||
internal void DebugDisplay()
|
||||
{
|
||||
string logMessage = "IndexAllocator structure\n";
|
||||
|
||||
bool[] seen = new bool[m_MaxSlot];
|
||||
|
||||
int pos = 0;
|
||||
int count = 0;
|
||||
bool prevEmpty = false;
|
||||
do
|
||||
{
|
||||
seen[pos] = true;
|
||||
count++;
|
||||
if (m_Slots[pos].Length == 0 && prevEmpty)
|
||||
{
|
||||
// don't display repetitive empty slots
|
||||
}
|
||||
else
|
||||
{
|
||||
logMessage += string.Format("{0}:{1}, {2} ({3}) \n", m_Slots[pos].Pos, m_Slots[pos].Length,
|
||||
m_Slots[pos].Free ? "Free" : "Used", pos);
|
||||
if (m_Slots[pos].Length == 0)
|
||||
{
|
||||
prevEmpty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
pos = m_Slots[pos].Next;
|
||||
} while (pos != k_NotSet && !seen[pos]);
|
||||
|
||||
logMessage += string.Format("{0} Total entries\n", count);
|
||||
|
||||
Debug.Log(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/IndexAllocator.cs.meta
Normal file
11
Runtime/Core/IndexAllocator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9e1475e8c8e4a6d935fe2409e3bd26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
676
Runtime/Core/NetworkBehaviour.cs
Normal file
676
Runtime/Core/NetworkBehaviour.cs
Normal file
@@ -0,0 +1,676 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class to override to write network code. Inherits MonoBehaviour
|
||||
/// </summary>
|
||||
public abstract class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal enum __RpcExecStage
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Client = 2
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
||||
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
[NonSerialized]
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
|
||||
#pragma warning restore 414 // restore assigned but its value is never used
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerRpcParams rpcParams, RpcDelivery delivery)
|
||||
#pragma warning restore 414 // restore assigned but its value is never used
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
|
||||
switch (delivery)
|
||||
{
|
||||
case RpcDelivery.Reliable:
|
||||
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
break;
|
||||
case RpcDelivery.Unreliable:
|
||||
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
|
||||
{
|
||||
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
||||
}
|
||||
networkDelivery = NetworkDelivery.Unreliable;
|
||||
break;
|
||||
}
|
||||
|
||||
var message = new RpcMessage
|
||||
{
|
||||
Header = new RpcMessage.HeaderData
|
||||
{
|
||||
Type = RpcMessage.RpcType.Server,
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourId = NetworkBehaviourId,
|
||||
NetworkMethodId = rpcMethodId
|
||||
},
|
||||
RpcData = writer
|
||||
};
|
||||
|
||||
var rpcMessageSize = 0;
|
||||
|
||||
// If we are a server/host then we just no op and send to ourself
|
||||
if (IsHost || IsServer)
|
||||
{
|
||||
using var tempBuffer = new FastBufferReader(writer, Allocator.Temp);
|
||||
var context = new NetworkContext
|
||||
{
|
||||
SenderId = NetworkManager.ServerClientId,
|
||||
Timestamp = Time.realtimeSinceStartup,
|
||||
SystemOwner = NetworkManager,
|
||||
// header information isn't valid since it's not a real message.
|
||||
// Passing false to canDefer prevents it being accessed.
|
||||
Header = new MessageHeader()
|
||||
};
|
||||
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
|
||||
rpcMessageSize = tempBuffer.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
rpcMessageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ServerClientId);
|
||||
}
|
||||
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
NetworkManager.ServerClientId,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
rpcMessageSize);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId, ClientRpcParams rpcParams, RpcDelivery delivery)
|
||||
#pragma warning disable 414 // disable assigned but its value is never used
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
{
|
||||
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
|
||||
switch (delivery)
|
||||
{
|
||||
case RpcDelivery.Reliable:
|
||||
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
break;
|
||||
case RpcDelivery.Unreliable:
|
||||
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
|
||||
{
|
||||
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
||||
}
|
||||
networkDelivery = NetworkDelivery.Unreliable;
|
||||
break;
|
||||
}
|
||||
|
||||
var message = new RpcMessage
|
||||
{
|
||||
Header = new RpcMessage.HeaderData
|
||||
{
|
||||
Type = RpcMessage.RpcType.Client,
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourId = NetworkBehaviourId,
|
||||
NetworkMethodId = rpcMethodId
|
||||
},
|
||||
RpcData = writer
|
||||
};
|
||||
int messageSize;
|
||||
|
||||
// We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC
|
||||
// to ourself. Sadly we have to figure that out from the list of clientIds :(
|
||||
bool shouldSendToHost = false;
|
||||
|
||||
if (rpcParams.Send.TargetClientIds != null)
|
||||
{
|
||||
foreach (var clientId in rpcParams.Send.TargetClientIds)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
messageSize = NetworkManager.SendMessage(message, networkDelivery, in rpcParams.Send.TargetClientIds);
|
||||
}
|
||||
else if (rpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
{
|
||||
foreach (var clientId in rpcParams.Send.TargetClientIdsNativeArray)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
messageSize = NetworkManager.SendMessage(message, networkDelivery, rpcParams.Send.TargetClientIdsNativeArray.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldSendToHost = IsHost;
|
||||
messageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ConnectedClientsIds);
|
||||
}
|
||||
|
||||
// If we are a server/host then we just no op and send to ourself
|
||||
if (shouldSendToHost)
|
||||
{
|
||||
using var tempBuffer = new FastBufferReader(writer, Allocator.Temp);
|
||||
var context = new NetworkContext
|
||||
{
|
||||
SenderId = NetworkManager.ServerClientId,
|
||||
Timestamp = Time.realtimeSinceStartup,
|
||||
SystemOwner = NetworkManager,
|
||||
// header information isn't valid since it's not a real message.
|
||||
// Passing false to canDefer prevents it being accessed.
|
||||
Header = new MessageHeader()
|
||||
};
|
||||
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
|
||||
messageSize = tempBuffer.Length;
|
||||
}
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
||||
{
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackRpcSent(
|
||||
client.Key,
|
||||
NetworkObject,
|
||||
rpcMethodName,
|
||||
__getTypeName(),
|
||||
messageSize);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
||||
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkObject.IsOwner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected bool IsServer => IsRunning && NetworkManager.IsServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient => IsRunning && NetworkManager.IsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost => IsRunning && NetworkManager.IsHost;
|
||||
|
||||
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
|
||||
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
|
||||
/// </summary>
|
||||
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false;
|
||||
|
||||
internal bool IsBehaviourEditable()
|
||||
{
|
||||
// Only server can MODIFY. So allow modification if network is either not running or we are server
|
||||
return !m_NetworkObject ||
|
||||
(m_NetworkObject.NetworkManager == null ||
|
||||
!m_NetworkObject.NetworkManager.IsListening ||
|
||||
m_NetworkObject.NetworkManager.IsServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
||||
/// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
|
||||
/// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
|
||||
/// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
|
||||
/// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get
|
||||
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
|
||||
/// how NetworkObject works but it was close to the release and too risky to change
|
||||
///
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
||||
}
|
||||
|
||||
return m_NetworkObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner.
|
||||
/// </summary>
|
||||
public bool HasNetworkObject => NetworkObject != null;
|
||||
|
||||
private NetworkObject m_NetworkObject = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
||||
/// </summary>
|
||||
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
|
||||
/// <summary>
|
||||
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
|
||||
/// </summary>
|
||||
internal ushort NetworkBehaviourIdCache = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject
|
||||
/// </summary>
|
||||
/// <param name="behaviourId">The behaviourId to return</param>
|
||||
/// <returns>Returns NetworkBehaviour with given behaviourId</returns>
|
||||
protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
|
||||
{
|
||||
return NetworkObject.GetNetworkBehaviourAtOrderIndex(behaviourId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClientId that owns the NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
|
||||
/// </summary>
|
||||
public virtual void OnNetworkSpawn() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients.
|
||||
/// </summary>
|
||||
public virtual void OnNetworkDespawn() { }
|
||||
|
||||
internal void InternalOnNetworkSpawn()
|
||||
{
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkDespawn()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the local client gains ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnGainedOwnership() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when we loose ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnLostOwnership() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
|
||||
/// </summary>
|
||||
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
|
||||
|
||||
private bool m_VarInit = false;
|
||||
|
||||
private readonly List<HashSet<int>> m_DeliveryMappedNetworkVariableIndices = new List<HashSet<int>>();
|
||||
private readonly List<NetworkDelivery> m_DeliveryTypesForNetworkVariableGroups = new List<NetworkDelivery>();
|
||||
internal readonly List<NetworkVariableBase> NetworkVariableFields = new List<NetworkVariableBase>();
|
||||
|
||||
private static Dictionary<Type, FieldInfo[]> s_FieldTypes = new Dictionary<Type, FieldInfo[]>();
|
||||
|
||||
private static FieldInfo[] GetFieldInfoForType(Type type)
|
||||
{
|
||||
if (!s_FieldTypes.ContainsKey(type))
|
||||
{
|
||||
s_FieldTypes.Add(type, GetFieldInfoForTypeRecursive(type));
|
||||
}
|
||||
|
||||
return s_FieldTypes[type];
|
||||
}
|
||||
|
||||
private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List<FieldInfo> list = null)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<FieldInfo>();
|
||||
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
}
|
||||
|
||||
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
||||
{
|
||||
return GetFieldInfoForTypeRecursive(type.BaseType, list);
|
||||
}
|
||||
|
||||
return list.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
|
||||
}
|
||||
|
||||
internal void InitializeVariables()
|
||||
{
|
||||
if (m_VarInit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_VarInit = true;
|
||||
|
||||
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
||||
|
||||
for (int i = 0; i < sortedFields.Length; i++)
|
||||
{
|
||||
Type fieldType = sortedFields[i].FieldType;
|
||||
|
||||
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
||||
{
|
||||
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = (NetworkVariableBase)Activator.CreateInstance(fieldType, true);
|
||||
sortedFields[i].SetValue(this, instance);
|
||||
}
|
||||
|
||||
instance.Initialize(this);
|
||||
|
||||
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
|
||||
var sanitizedVariableName = sortedFields[i].Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty);
|
||||
instanceNameProperty?.SetValue(instance, sanitizedVariableName);
|
||||
|
||||
NetworkVariableFields.Add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create index map for delivery types
|
||||
var firstLevelIndex = new Dictionary<NetworkDelivery, int>();
|
||||
int secondLevelCounter = 0;
|
||||
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
var networkDelivery = NetworkVariableBase.Delivery;
|
||||
if (!firstLevelIndex.ContainsKey(networkDelivery))
|
||||
{
|
||||
firstLevelIndex.Add(networkDelivery, secondLevelCounter);
|
||||
m_DeliveryTypesForNetworkVariableGroups.Add(networkDelivery);
|
||||
secondLevelCounter++;
|
||||
}
|
||||
|
||||
if (firstLevelIndex[networkDelivery] >= m_DeliveryMappedNetworkVariableIndices.Count)
|
||||
{
|
||||
m_DeliveryMappedNetworkVariableIndices.Add(new HashSet<int>());
|
||||
}
|
||||
|
||||
m_DeliveryMappedNetworkVariableIndices[firstLevelIndex[networkDelivery]].Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void PreNetworkVariableWrite()
|
||||
{
|
||||
// reset our "which variables got written" data
|
||||
NetworkVariableIndexesToReset.Clear();
|
||||
NetworkVariableIndexesToResetSet.Clear();
|
||||
}
|
||||
|
||||
internal void PostNetworkVariableWrite()
|
||||
{
|
||||
// mark any variables we wrote as no longer dirty
|
||||
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong clientId)
|
||||
{
|
||||
if (!m_VarInit)
|
||||
{
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
NetworkVariableUpdate(clientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
||||
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||
|
||||
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)
|
||||
{
|
||||
if (!CouldHaveDirtyNetworkVariables())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
{
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
{
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer))
|
||||
{
|
||||
shouldSend = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
ClientId = clientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
|
||||
using (tmpWriter)
|
||||
{
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CouldHaveDirtyNetworkVariables()
|
||||
{
|
||||
// TODO: There should be a better way by reading one dirty variable vs. 'n'
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (NetworkVariableFields[i].IsDirty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void MarkVariablesDirty()
|
||||
{
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
NetworkVariableFields[j].SetDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId);
|
||||
|
||||
if (canClientRead)
|
||||
{
|
||||
var writePos = writer.Position;
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
var startPos = writer.Position;
|
||||
NetworkVariableFields[j].WriteField(writer);
|
||||
var size = writer.Position - startPos;
|
||||
writer.Seek(writePos);
|
||||
writer.WriteValueSafe((ushort)size);
|
||||
writer.Seek(startPos + size);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
reader.ReadValueSafe(out ushort varSize);
|
||||
if (varSize == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var readStartPos = reader.Position;
|
||||
NetworkVariableFields[j].ReadField(reader);
|
||||
|
||||
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (reader.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes.");
|
||||
}
|
||||
|
||||
reader.Seek(readStartPos + varSize);
|
||||
}
|
||||
else if (reader.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes.");
|
||||
}
|
||||
|
||||
reader.Seek(readStartPos + varSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local instance of a object with a given NetworkId
|
||||
/// </summary>
|
||||
/// <param name="networkId"></param>
|
||||
/// <returns></returns>
|
||||
protected NetworkObject GetNetworkObject(ulong networkId)
|
||||
{
|
||||
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
|
||||
}
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
// 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
|
||||
// registered here and therefore won't be cleaned up.
|
||||
//
|
||||
// we should study to understand the initialization patterns
|
||||
if (!m_VarInit)
|
||||
{
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[i].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/NetworkBehaviour.cs.meta
Normal file
11
Runtime/Core/NetworkBehaviour.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8ea6ec00590bd44a983c228bcaee727
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Runtime/Core/NetworkBehaviourUpdater.cs
Normal file
84
Runtime/Core/NetworkBehaviourUpdater.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public class NetworkBehaviourUpdater
|
||||
{
|
||||
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
|
||||
#endif
|
||||
|
||||
internal void NetworkBehaviourUpdate(NetworkManager networkManager)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
m_NetworkBehaviourUpdate.Begin();
|
||||
#endif
|
||||
try
|
||||
{
|
||||
if (networkManager.IsServer)
|
||||
{
|
||||
m_Touched.Clear();
|
||||
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
var client = networkManager.ConnectedClientsList[i];
|
||||
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList;
|
||||
m_Touched.UnionWith(spawnedObjs);
|
||||
foreach (var sobj in spawnedObjs)
|
||||
{
|
||||
if (sobj.IsNetworkVisibleTo(client.ClientId))
|
||||
{
|
||||
// Sync just the variables for just the objects this client sees
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.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)
|
||||
{
|
||||
if (sobj.IsOwner)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
m_NetworkBehaviourUpdate.End();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
3
Runtime/Core/NetworkBehaviourUpdater.cs.meta
Normal file
3
Runtime/Core/NetworkBehaviourUpdater.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d084c01093b446878bcb76e5d7f3221e
|
||||
timeCreated: 1622225163
|
||||
1618
Runtime/Core/NetworkManager.cs
Normal file
1618
Runtime/Core/NetworkManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Core/NetworkManager.cs.meta
Normal file
11
Runtime/Core/NetworkManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 593a2fe42fa9d37498c96f9a383b6521
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1155
Runtime/Core/NetworkObject.cs
Normal file
1155
Runtime/Core/NetworkObject.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Core/NetworkObject.cs.meta
Normal file
11
Runtime/Core/NetworkObject.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5a57f767e5e46a458fc5d3c628d0cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
417
Runtime/Core/NetworkUpdateLoop.cs
Normal file
417
Runtime/Core/NetworkUpdateLoop.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.LowLevel;
|
||||
using UnityEngine.PlayerLoop;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the required interface of a network update system being executed by the network update loop.
|
||||
/// </summary>
|
||||
public interface INetworkUpdateSystem
|
||||
{
|
||||
void NetworkUpdate(NetworkUpdateStage updateStage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines network update stages being executed by the network update loop.
|
||||
/// </summary>
|
||||
public enum NetworkUpdateStage : byte
|
||||
{
|
||||
Unset = 0, // Default
|
||||
Initialization = 1,
|
||||
EarlyUpdate = 2,
|
||||
FixedUpdate = 3,
|
||||
PreUpdate = 4,
|
||||
Update = 5,
|
||||
PreLateUpdate = 6,
|
||||
PostLateUpdate = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the network update loop injected into low-level player loop in Unity.
|
||||
/// </summary>
|
||||
public static class NetworkUpdateLoop
|
||||
{
|
||||
private static Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>> s_UpdateSystem_Sets;
|
||||
private static Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]> s_UpdateSystem_Arrays;
|
||||
private const int k_UpdateSystem_InitialArrayCapacity = 1024;
|
||||
|
||||
static NetworkUpdateLoop()
|
||||
{
|
||||
s_UpdateSystem_Sets = new Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>>();
|
||||
s_UpdateSystem_Arrays = new Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]>();
|
||||
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
s_UpdateSystem_Sets.Add(updateStage, new HashSet<INetworkUpdateSystem>());
|
||||
s_UpdateSystem_Arrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystem_InitialArrayCapacity]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in all network update stages.
|
||||
/// </summary>
|
||||
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
RegisterNetworkUpdate(updateSystem, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in a specific network update stage.
|
||||
/// </summary>
|
||||
public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
if (!sysSet.Contains(updateSystem))
|
||||
{
|
||||
sysSet.Add(updateSystem);
|
||||
|
||||
int setLen = sysSet.Count;
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
|
||||
if (setLen > arrLen)
|
||||
{
|
||||
// double capacity
|
||||
sysArr = s_UpdateSystem_Arrays[updateStage] = new INetworkUpdateSystem[arrLen *= 2];
|
||||
}
|
||||
|
||||
sysSet.CopyTo(sysArr);
|
||||
|
||||
if (setLen < arrLen)
|
||||
{
|
||||
// null terminator
|
||||
sysArr[setLen] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from all network update stages.
|
||||
/// </summary>
|
||||
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
UnregisterNetworkUpdate(updateSystem, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from a specific network update stage.
|
||||
/// </summary>
|
||||
public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
if (sysSet.Contains(updateSystem))
|
||||
{
|
||||
sysSet.Remove(updateSystem);
|
||||
|
||||
int setLen = sysSet.Count;
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
|
||||
sysSet.CopyTo(sysArr);
|
||||
|
||||
if (setLen < arrLen)
|
||||
{
|
||||
// null terminator
|
||||
sysArr[setLen] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current network update stage being executed.
|
||||
/// </summary>
|
||||
public static NetworkUpdateStage UpdateStage;
|
||||
|
||||
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
|
||||
{
|
||||
UpdateStage = updateStage;
|
||||
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
for (int curIdx = 0; curIdx < arrLen; curIdx++)
|
||||
{
|
||||
var curSys = sysArr[curIdx];
|
||||
if (curSys == null)
|
||||
{
|
||||
// null terminator
|
||||
break;
|
||||
}
|
||||
|
||||
curSys.NetworkUpdate(updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkInitialization
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkInitialization),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Initialization)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkEarlyUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkEarlyUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.EarlyUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkFixedUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkFixedUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.FixedUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPreUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPreUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Update)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPreLateUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPreLateUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreLateUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPostLateUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPostLateUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostLateUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void Initialize()
|
||||
{
|
||||
UnregisterLoopSystems();
|
||||
RegisterLoopSystems();
|
||||
}
|
||||
|
||||
private enum LoopSystemPosition
|
||||
{
|
||||
After,
|
||||
Before
|
||||
}
|
||||
|
||||
private static bool TryAddLoopSystem(ref PlayerLoopSystem parentLoopSystem, PlayerLoopSystem childLoopSystem, Type anchorSystemType, LoopSystemPosition loopSystemPosition)
|
||||
{
|
||||
int systemPosition = -1;
|
||||
if (anchorSystemType != null)
|
||||
{
|
||||
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
|
||||
{
|
||||
var subsystem = parentLoopSystem.subSystemList[i];
|
||||
if (subsystem.type == anchorSystemType)
|
||||
{
|
||||
systemPosition = loopSystemPosition == LoopSystemPosition.After ? i + 1 : i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemPosition = loopSystemPosition == LoopSystemPosition.After ? parentLoopSystem.subSystemList.Length : 0;
|
||||
}
|
||||
|
||||
if (systemPosition == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length + 1];
|
||||
|
||||
// begin = systemsBefore + systemsAfter
|
||||
// + systemsBefore
|
||||
if (systemPosition > 0)
|
||||
{
|
||||
Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
|
||||
}
|
||||
// + childSystem
|
||||
newSubsystemList[systemPosition] = childLoopSystem;
|
||||
// + systemsAfter
|
||||
if (systemPosition < parentLoopSystem.subSystemList.Length)
|
||||
{
|
||||
Array.Copy(parentLoopSystem.subSystemList, systemPosition, newSubsystemList, systemPosition + 1, parentLoopSystem.subSystemList.Length - systemPosition);
|
||||
}
|
||||
// end = systemsBefore + childSystem + systemsAfter
|
||||
|
||||
parentLoopSystem.subSystemList = newSubsystemList;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryRemoveLoopSystem(ref PlayerLoopSystem parentLoopSystem, Type childSystemType)
|
||||
{
|
||||
int systemPosition = -1;
|
||||
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
|
||||
{
|
||||
var subsystem = parentLoopSystem.subSystemList[i];
|
||||
if (subsystem.type == childSystemType)
|
||||
{
|
||||
systemPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (systemPosition == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length - 1];
|
||||
|
||||
// begin = systemsBefore + childSystem + systemsAfter
|
||||
// + systemsBefore
|
||||
if (systemPosition > 0)
|
||||
{
|
||||
Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
|
||||
}
|
||||
// + systemsAfter
|
||||
if (systemPosition < parentLoopSystem.subSystemList.Length - 1)
|
||||
{
|
||||
Array.Copy(parentLoopSystem.subSystemList, systemPosition + 1, newSubsystemList, systemPosition, parentLoopSystem.subSystemList.Length - systemPosition - 1);
|
||||
}
|
||||
// end = systemsBefore + systemsAfter
|
||||
|
||||
parentLoopSystem.subSystemList = newSubsystemList;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void RegisterLoopSystems()
|
||||
{
|
||||
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
|
||||
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
|
||||
{
|
||||
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
|
||||
|
||||
if (currentSystem.type == typeof(Initialization))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkInitialization.CreateLoopSystem(), null, LoopSystemPosition.After);
|
||||
}
|
||||
else if (currentSystem.type == typeof(EarlyUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkEarlyUpdate.CreateLoopSystem(), typeof(EarlyUpdate.ScriptRunDelayedStartupFrame), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(FixedUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkFixedUpdate.CreateLoopSystem(), typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPreUpdate.CreateLoopSystem(), typeof(PreUpdate.PhysicsUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(Update))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkUpdate.CreateLoopSystem(), typeof(Update.ScriptRunBehaviourUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreLateUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PostLateUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPostLateUpdate.CreateLoopSystem(), typeof(PostLateUpdate.PlayerSendFrameComplete), LoopSystemPosition.After);
|
||||
}
|
||||
}
|
||||
|
||||
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
|
||||
}
|
||||
|
||||
internal static void UnregisterLoopSystems()
|
||||
{
|
||||
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
|
||||
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
|
||||
{
|
||||
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
|
||||
|
||||
if (currentSystem.type == typeof(Initialization))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkInitialization));
|
||||
}
|
||||
else if (currentSystem.type == typeof(EarlyUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkEarlyUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(FixedUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkFixedUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(Update))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreLateUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PostLateUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostLateUpdate));
|
||||
}
|
||||
}
|
||||
|
||||
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/NetworkUpdateLoop.cs.meta
Normal file
11
Runtime/Core/NetworkUpdateLoop.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cd9c24b9acfd4e82a71c795f37235c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
93
Runtime/Core/SnapshotRTT.cs
Normal file
93
Runtime/Core/SnapshotRTT.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class ConnectionRtt
|
||||
{
|
||||
private double[] m_RttSendTimes; // times at which packet were sent for RTT computations
|
||||
private int[] m_SendSequence; // tick, or other key, at which packets were sent (to allow matching)
|
||||
private double[] m_MeasuredLatencies; // measured latencies (ring buffer)
|
||||
private int m_LatenciesBegin = 0; // ring buffer begin
|
||||
private int m_LatenciesEnd = 0; // ring buffer end
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip-time data
|
||||
/// </summary>
|
||||
public struct Rtt
|
||||
{
|
||||
public double BestSec; // best RTT
|
||||
public double AverageSec; // average RTT
|
||||
public double WorstSec; // worst RTT
|
||||
public double LastSec; // latest ack'ed RTT
|
||||
public int SampleCount; // number of contributing samples
|
||||
}
|
||||
|
||||
public ConnectionRtt()
|
||||
{
|
||||
m_RttSendTimes = new double[NetworkConfig.RttWindowSize];
|
||||
m_SendSequence = new int[NetworkConfig.RttWindowSize];
|
||||
m_MeasuredLatencies = new double[NetworkConfig.RttWindowSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Round-trip-time computation for this client
|
||||
/// </summary>
|
||||
public Rtt GetRtt()
|
||||
{
|
||||
var ret = new Rtt();
|
||||
var index = m_LatenciesBegin;
|
||||
double total = 0.0;
|
||||
ret.BestSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
ret.WorstSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
|
||||
while (index != m_LatenciesEnd)
|
||||
{
|
||||
total += m_MeasuredLatencies[index];
|
||||
ret.SampleCount++;
|
||||
ret.BestSec = Math.Min(ret.BestSec, m_MeasuredLatencies[index]);
|
||||
ret.WorstSec = Math.Max(ret.WorstSec, m_MeasuredLatencies[index]);
|
||||
index = (index + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
|
||||
if (ret.SampleCount != 0)
|
||||
{
|
||||
ret.AverageSec = total / ret.SampleCount;
|
||||
// the latest RTT is one before m_LatenciesEnd
|
||||
ret.LastSec = m_MeasuredLatencies[(m_LatenciesEnd + (NetworkConfig.RttWindowSize - 1)) % NetworkConfig.RttWindowSize];
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.AverageSec = 0;
|
||||
ret.BestSec = 0;
|
||||
ret.WorstSec = 0;
|
||||
ret.SampleCount = 0;
|
||||
ret.LastSec = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal void NotifySend(int sequence, double timeSec)
|
||||
{
|
||||
m_RttSendTimes[sequence % NetworkConfig.RttWindowSize] = timeSec;
|
||||
m_SendSequence[sequence % NetworkConfig.RttWindowSize] = sequence;
|
||||
}
|
||||
|
||||
internal void NotifyAck(int sequence, double timeSec)
|
||||
{
|
||||
// if the same slot was not used by a later send
|
||||
if (m_SendSequence[sequence % NetworkConfig.RttWindowSize] == sequence)
|
||||
{
|
||||
double latency = timeSec - m_RttSendTimes[sequence % NetworkConfig.RttWindowSize];
|
||||
|
||||
m_MeasuredLatencies[m_LatenciesEnd] = latency;
|
||||
m_LatenciesEnd = (m_LatenciesEnd + 1) % NetworkConfig.RttAverageSamples;
|
||||
|
||||
if (m_LatenciesEnd == m_LatenciesBegin)
|
||||
{
|
||||
m_LatenciesBegin = (m_LatenciesBegin + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Core/SnapshotRTT.cs.meta
Normal file
11
Runtime/Core/SnapshotRTT.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1062
Runtime/Core/SnapshotSystem.cs
Normal file
1062
Runtime/Core/SnapshotSystem.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Core/SnapshotSystem.cs.meta
Normal file
11
Runtime/Core/SnapshotSystem.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c275febadb27c4d18b41218e3353b84b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Exceptions.meta
Normal file
8
Runtime/Exceptions.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2643fec0e8509fd409e5aceeba931323
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Runtime/Exceptions/InvalidParentException.cs
Normal file
14
Runtime/Exceptions/InvalidParentException.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the new parent candidate of the NetworkObject is not valid
|
||||
/// </summary>
|
||||
public class InvalidParentException : Exception
|
||||
{
|
||||
public InvalidParentException() { }
|
||||
public InvalidParentException(string message) : base(message) { }
|
||||
public InvalidParentException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
11
Runtime/Exceptions/InvalidParentException.cs.meta
Normal file
11
Runtime/Exceptions/InvalidParentException.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abe1766318991412cb1aba96c4dbcc4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Runtime/Exceptions/NetworkConfigurationException.cs
Normal file
28
Runtime/Exceptions/NetworkConfigurationException.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when a change to a configuration is wrong
|
||||
/// </summary>
|
||||
public class NetworkConfigurationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException
|
||||
/// </summary>
|
||||
public NetworkConfigurationException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NetworkConfigurationException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NetworkConfigurationException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
||||
11
Runtime/Exceptions/NetworkConfigurationException.cs.meta
Normal file
11
Runtime/Exceptions/NetworkConfigurationException.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f50fae9dd20afbd438766cc330b188a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Runtime/Exceptions/NotListeningException.cs
Normal file
28
Runtime/Exceptions/NotListeningException.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation require NetworkManager to be listening.
|
||||
/// </summary>
|
||||
public class NotListeningException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException
|
||||
/// </summary>
|
||||
public NotListeningException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NotListeningException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NotListeningException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user