Compare commits
7 Commits
1.0.0-pre.
...
1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6969670f5 | ||
|
|
e15bd056c5 | ||
|
|
18ffd5fdc8 | ||
|
|
0f7a30d285 | ||
|
|
5b1fc203ed | ||
|
|
add668dfd2 | ||
|
|
60e2dabef4 |
153
CHANGELOG.md
153
CHANGELOG.md
@@ -1,3 +1,4 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
@@ -6,14 +7,161 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||
|
||||
|
||||
## [1.0.2] - 2022-09-12
|
||||
|
||||
- Fixed issue where `NetworkTransform` was not honoring the InLocalSpace property on the authority side during OnNetworkSpawn. (#2170)
|
||||
- Fixed issue where `NetworkTransform` was not ending extrapolation for the previous state causing non-authoritative instances to become out of synch. (#2170)
|
||||
- Fixed issue where `NetworkTransform` was not continuing to interpolate for the remainder of the associated tick period. (#2170)
|
||||
- Fixed issue during `NetworkTransform.OnNetworkSpawn` for non-authoritative instances where it was initializing interpolators with the replicated network state which now only contains the transform deltas that occurred during a network tick and not the entire transform state. (#2170)
|
||||
|
||||
## [1.0.1] - 2022-08-23
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed version to 1.0.1. (#2131)
|
||||
- Updated dependency on `com.unity.transport` to 1.2.0. (#2129)
|
||||
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
|
||||
- Performance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130)
|
||||
- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110)
|
||||
- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110)
|
||||
- Fixed issue where the authoritative side was interpolating its transform. (#2110)
|
||||
- Fixed Owner-written NetworkVariable infinitely write themselves (#2109)
|
||||
- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099)
|
||||
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
|
||||
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
|
||||
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
|
||||
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
|
||||
- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084)
|
||||
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
|
||||
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
|
||||
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
|
||||
- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067)
|
||||
- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062)
|
||||
|
||||
## [1.0.0] - 2022-06-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed version to 1.0.0. (#2046)
|
||||
|
||||
## [1.0.0-pre.10] - 2022-06-21
|
||||
|
||||
### Added
|
||||
|
||||
- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
|
||||
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
|
||||
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
|
||||
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
|
||||
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now an `Action<>` taking a `ConnectionApprovalRequest` and a `ConnectionApprovalResponse` that the client code must fill (#1972) (#2002)
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
|
||||
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
|
||||
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
|
||||
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
|
||||
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
|
||||
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
|
||||
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
|
||||
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
|
||||
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
|
||||
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
|
||||
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
|
||||
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
|
||||
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
|
||||
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
|
||||
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
|
||||
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
|
||||
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
|
||||
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
|
||||
|
||||
## [1.0.0-pre.9] - 2022-05-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Hosting again after failing to host now works correctly (#1938)
|
||||
- Fixed NetworkManager to cleanup connected client lists after stopping (#1945)
|
||||
- Fixed NetworkHide followed by NetworkShow on the same frame works correctly (#1940)
|
||||
|
||||
## [1.0.0-pre.8] - 2022-04-27
|
||||
|
||||
### Changed
|
||||
|
||||
- `unmanaged` structs are no longer universally accepted as RPC parameters because some structs (i.e., structs with pointers in them, such as `NativeList<T>`) can't be supported by the default memcpy struct serializer. Structs that are intended to be serialized across the network must add `INetworkSerializeByMemcpy` to the interface list (i.e., `struct Foo : INetworkSerializeByMemcpy`). This interface is empty and just serves to mark the struct as compatible with memcpy serialization. For external structs you can't edit, you can pass them to RPCs by wrapping them in `ForceNetworkSerializeByMemcpy<T>`. (#1901)
|
||||
- Changed requirement to register in-scene placed NetworkObjects with `NetworkManager` in order to respawn them. In-scene placed NetworkObjects are now automatically tracked during runtime and no longer need to be registered as a NetworkPrefab. (#1898)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `SIPTransport` (#1870)
|
||||
- Removed `ClientNetworkTransform` from the package samples and moved to Boss Room's Utilities package which can be found [here](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs) (#1912)
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where `NetworkSceneManager` did not synchronize despawned in-scene placed NetworkObjects. (#1898)
|
||||
- Fixed `NetworkTransform` generating false positive rotation delta checks when rolling over between 0 and 360 degrees. (#1890)
|
||||
- Fixed client throwing an exception if it has messages in the outbound queue when processing the `NetworkEvent.Disconnect` event and is using UTP. (#1884)
|
||||
- Fixed issue during client synchronization if 'ValidateSceneBeforeLoading' returned false it would halt the client synchronization process resulting in a client that was approved but not synchronized or fully connected with the server. (#1883)
|
||||
- Fixed an issue where UNetTransport.StartServer would return success even if the underlying transport failed to start (#854)
|
||||
- Passing generic types to RPCs no longer causes a native crash (#1901)
|
||||
- Fixed a compile failure when compiling against com.unity.nuget.mono-cecil >= 1.11.4 (#1920)
|
||||
- Fixed an issue where calling `Shutdown` on a `NetworkManager` that was already shut down would cause an immediate shutdown the next time it was started (basically the fix makes `Shutdown` idempotent). (#1877)
|
||||
|
||||
## [1.0.0-pre.7] - 2022-04-06
|
||||
|
||||
### Added
|
||||
|
||||
- Added editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828)
|
||||
- Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823)
|
||||
- Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762)
|
||||
- `UnityTransport` settings can now be set programmatically. (#1845)
|
||||
- `FastBufferWriter` and Reader IsInitialized property. (#1859)
|
||||
- Prefabs can now be added to the network at **runtime** (i.e., from an addressable asset). If `ForceSamePrefabs` is false, this can happen after a connection has been formed. (#1882)
|
||||
- When `ForceSamePrefabs` is false, a configurable delay (default 1 second, configurable via `NetworkConfig.SpawnTimeout`) has been introduced to gracefully handle race conditions where a spawn call has been received for an object whose prefab is still being loaded. (#1882)
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed `NetcodeIntegrationTestHelpers` to use `UnityTransport` (#1870)
|
||||
- Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `SnapshotSystem` (#1852)
|
||||
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)
|
||||
- Removed `com.unity.collections` dependency from the package (#1849)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850)
|
||||
- Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847)
|
||||
- Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
|
||||
- Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838)
|
||||
- Fixed ClientRpcs would always send to all connected clients by default as opposed to only sending to the NetworkObject's Observers list by default. (#1836)
|
||||
- Fixed clarity for NetworkSceneManager client side notification when it receives a scene hash value that does not exist in its local hash table. (#1828)
|
||||
- Fixed client throws a key not found exception when it times out using UNet or UTP. (#1821)
|
||||
- Fixed network variable updates are no longer limited to 32,768 bytes when NetworkConfig.EnsureNetworkVariableLengthSafety is enabled. The limits are now determined by what the transport can send in a message. (#1811)
|
||||
- Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809)
|
||||
- Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808)
|
||||
- Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801)
|
||||
- Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780)
|
||||
- Fixed issue when spawning new player if an already existing player exists it does not remove IsPlayer from the previous player (#1779)
|
||||
- Fixed lack of notification that NetworkManager and NetworkObject cannot be added to the same GameObject with in-editor notifications (#1777)
|
||||
- Fixed parenting warning printing for false positives (#1855)
|
||||
|
||||
## [1.0.0-pre.6] - 2022-03-02
|
||||
|
||||
### Added
|
||||
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765)
|
||||
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
- Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683)
|
||||
- Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683)
|
||||
@@ -33,6 +181,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731)
|
||||
- Improved performance in NetworkAnimator (#1735)
|
||||
- Removed the "always sync" network animator (aka "autosend") parameters (#1746)
|
||||
- Fixed in-scene placed NetworkObjects not respawning after shutting down the NetworkManager and then starting it back up again (#1769)
|
||||
|
||||
## [1.0.0-pre.5] - 2022-01-26
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Solves for incoming values that are jittered
|
||||
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal abstract class BufferedLinearInterpolator<T> where T : struct
|
||||
/// <typeparam name="T">The type of interpolated value</typeparam>
|
||||
public abstract class BufferedLinearInterpolator<T> where T : struct
|
||||
{
|
||||
internal float MaxInterpolationBound = 3.0f;
|
||||
private struct BufferedItem
|
||||
{
|
||||
public T Item;
|
||||
@@ -24,6 +24,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one.
|
||||
/// </summary>
|
||||
public float MaximumInterpolationTime = 0.1f;
|
||||
|
||||
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
|
||||
|
||||
@@ -69,6 +73,9 @@ namespace Unity.Netcode
|
||||
|
||||
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Resets interpolator to initial state
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Buffer.Clear();
|
||||
@@ -76,6 +83,11 @@ namespace Unity.Netcode
|
||||
m_StartTimeConsumed = 0.0d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports current interpolation value to targetValue.
|
||||
/// </summary>
|
||||
/// <param name="targetValue">The target value to teleport instantly</param>
|
||||
/// <param name="serverTime">The current server time</param>
|
||||
public void ResetTo(T targetValue, double serverTime)
|
||||
{
|
||||
m_LifetimeConsumedCount = 1;
|
||||
@@ -89,7 +101,6 @@ namespace Unity.Netcode
|
||||
Update(0, serverTime, serverTime);
|
||||
}
|
||||
|
||||
|
||||
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
|
||||
private void TryConsumeFromBuffer(double renderTime, double serverTime)
|
||||
{
|
||||
@@ -151,6 +162,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">time since call</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
/// <returns>The newly interpolated value of type 'T'</returns>
|
||||
public T Update(float deltaTime, NetworkTime serverTime)
|
||||
{
|
||||
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
|
||||
@@ -162,6 +174,7 @@ namespace Unity.Netcode
|
||||
/// <param name="deltaTime">time since last call</param>
|
||||
/// <param name="renderTime">our current time</param>
|
||||
/// <param name="serverTime">current server time</param>
|
||||
/// <returns>The newly interpolated value of type 'T'</returns>
|
||||
public T Update(float deltaTime, double renderTime, double serverTime)
|
||||
{
|
||||
TryConsumeFromBuffer(renderTime, serverTime);
|
||||
@@ -186,26 +199,36 @@ namespace Unity.Netcode
|
||||
|
||||
if (t < 0.0f)
|
||||
{
|
||||
throw new OverflowException($"t = {t} but must be >= 0. range {range}, RenderTime {renderTime}, Start time {m_StartTimeConsumed}, end time {m_EndTimeConsumed}");
|
||||
// There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed
|
||||
// This clamps t to a minimum of 0 and fixes issues with longer frames and pauses
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}");
|
||||
}
|
||||
t = 0.0f;
|
||||
}
|
||||
|
||||
if (t > 3.0f) // max extrapolation
|
||||
if (t > MaxInterpolationBound) // max extrapolation
|
||||
{
|
||||
// TODO this causes issues with teleport, investigate
|
||||
// todo make this configurable
|
||||
t = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
|
||||
float maxInterpTime = 0.1f;
|
||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / maxInterpTime); // second interpolate to smooth out extrapolation jumps
|
||||
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps
|
||||
}
|
||||
|
||||
m_NbItemsReceivedThisFrame = 0;
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
|
||||
/// </summary>
|
||||
/// <param name="newMeasurement">The new measurement value to use</param>
|
||||
/// <param name="sentTime">The time to record for measurement</param>
|
||||
public void AddMeasurement(T newMeasurement, double sentTime)
|
||||
{
|
||||
m_NbItemsReceivedThisFrame++;
|
||||
@@ -218,11 +241,15 @@ namespace Unity.Netcode
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
ResetTo(newMeasurement, sentTime);
|
||||
// Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues
|
||||
m_Buffer.Add(m_LastBufferedItemReceived);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Part the of reason for disabling extrapolation is how we add and use measurements over time.
|
||||
// TODO: Add detailed description of this area in Jira ticket
|
||||
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
|
||||
{
|
||||
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
|
||||
@@ -230,39 +257,75 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets latest value from the interpolator. This is updated every update as time goes by.
|
||||
/// </summary>
|
||||
/// <returns>The current interpolated value of type 'T'</returns>
|
||||
public T GetInterpolatedValue()
|
||||
{
|
||||
return m_CurrentInterpValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
|
||||
/// </summary>
|
||||
/// <param name="start">The start value (min)</param>
|
||||
/// <param name="end">The end value (max)</param>
|
||||
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
|
||||
/// <returns>The interpolated value</returns>
|
||||
protected abstract T Interpolate(T start, T end, float time);
|
||||
|
||||
/// <summary>
|
||||
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
|
||||
/// </summary>
|
||||
/// <param name="start">The start value (min)</param>
|
||||
/// <param name="end">The end value (max)</param>
|
||||
/// <param name="time">The time value used to interpolate between start and end values (pos)</param>
|
||||
/// <returns>The interpolated value</returns>
|
||||
protected abstract T InterpolateUnclamped(T start, T end, float time);
|
||||
}
|
||||
|
||||
|
||||
internal class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="float"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override float InterpolateUnclamped(float start, float end, float time)
|
||||
{
|
||||
return Mathf.LerpUnclamped(start, end, time);
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override float Interpolate(float start, float end, float time)
|
||||
{
|
||||
return Mathf.Lerp(start, end, time);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is a buffered linear interpolator for a <see cref="Quaternion"/> type value
|
||||
/// </remarks>
|
||||
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
return Quaternion.SlerpUnclamped(start, end, time);
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Quaternion.Slerp(start, end, time);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
|
||||
{
|
||||
return Quaternion.SlerpUnclamped(start, end, time);
|
||||
// Disabling Extrapolation:
|
||||
// TODO: Add Jira Ticket
|
||||
return Quaternion.Slerp(start, end, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +1,102 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
|
||||
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
|
||||
/// mode of the <see cref="Rigidbody"/> and disabling it on all peers but the authoritative one.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(NetworkTransform))]
|
||||
public class NetworkRigidbody : NetworkBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if we are server (true) or owner (false) authoritative
|
||||
/// </summary>
|
||||
private bool m_IsServerAuthoritative;
|
||||
|
||||
private Rigidbody m_Rigidbody;
|
||||
private NetworkTransform m_NetworkTransform;
|
||||
|
||||
private bool m_OriginalKinematic;
|
||||
private RigidbodyInterpolation m_OriginalInterpolation;
|
||||
|
||||
// Used to cache the authority state of this rigidbody during the last frame
|
||||
// Used to cache the authority state of this Rigidbody during the last frame
|
||||
private bool m_IsAuthority;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody"/> on this peer currently holds authority.
|
||||
/// </summary>
|
||||
private bool HasAuthority => m_NetworkTransform.CanCommitToTransform;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_NetworkTransform = GetComponent<NetworkTransform>();
|
||||
m_IsServerAuthoritative = m_NetworkTransform.IsServerAuthoritative();
|
||||
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
|
||||
// Set interpolation to none if NetworkTransform is handling interpolation, otherwise it sets it to the original value
|
||||
m_Rigidbody.interpolation = m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation;
|
||||
|
||||
// Turn off physics for the rigid body until spawned, otherwise
|
||||
// clients can run fixed update before the first full
|
||||
// NetworkTransform update
|
||||
m_Rigidbody.isKinematic = true;
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
/// <summary>
|
||||
/// For owner authoritative (i.e. ClientNetworkTransform)
|
||||
/// we adjust our authority when we gain ownership
|
||||
/// </summary>
|
||||
public override void OnGainedOwnership()
|
||||
{
|
||||
if (NetworkManager.IsListening)
|
||||
{
|
||||
if (HasAuthority != m_IsAuthority)
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
}
|
||||
}
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
|
||||
private void UpdateRigidbodyKinematicMode()
|
||||
/// <summary>
|
||||
/// For owner authoritative(i.e. ClientNetworkTransform)
|
||||
/// we adjust our authority when we have lost ownership
|
||||
/// </summary>
|
||||
public override void OnLostOwnership()
|
||||
{
|
||||
if (m_IsAuthority == false)
|
||||
{
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation.None;
|
||||
/// <summary>
|
||||
/// Sets the authority differently depending upon
|
||||
/// whether it is server or owner authoritative
|
||||
/// </summary>
|
||||
private void UpdateOwnershipAuthority()
|
||||
{
|
||||
if (m_IsServerAuthoritative)
|
||||
{
|
||||
m_IsAuthority = NetworkManager.IsServer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
|
||||
m_Rigidbody.isKinematic = m_OriginalKinematic;
|
||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||
m_IsAuthority = IsOwner;
|
||||
}
|
||||
|
||||
// If you have authority then you are not kinematic
|
||||
m_Rigidbody.isKinematic = !m_IsAuthority;
|
||||
|
||||
// Set interpolation of the Rigidbody based on authority
|
||||
// With authority: let local transform handle interpolation
|
||||
// Without authority: let the NetworkTransform handle interpolation
|
||||
m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : RigidbodyInterpolation.None;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
m_IsAuthority = HasAuthority;
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
UpdateRigidbodyKinematicMode();
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
UpdateRigidbodyKinematicMode();
|
||||
m_Rigidbody.interpolation = m_OriginalInterpolation;
|
||||
// Turn off physics for the rigid body until spawned, otherwise
|
||||
// non-owners can run fixed updates before the first full
|
||||
// NetworkTransform update and physics will be applied (i.e. gravity, etc)
|
||||
m_Rigidbody.isKinematic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
@@ -78,3 +79,4 @@ namespace Unity.Netcode.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS2D
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,5 +5,22 @@
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
],
|
||||
"allowUnsafeCode": true
|
||||
"allowUnsafeCode": true,
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.modules.animation",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_ANIMATION"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.modules.physics2d",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_MODULES_PHYSICS2D"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# About Netcode for GameObjects
|
||||
|
||||
Unity Netcode for GameObjects is a high-level networking library built to abstract networking. This allows developers to focus on the game rather than low level protocols and networking frameworks.
|
||||
|
||||
## Guides
|
||||
|
||||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
||||
|
||||
* [Documentation](https://docs-multiplayer.unity3d.com/docs/getting-started/about-mlapi)
|
||||
* [Installation](https://docs-multiplayer.unity3d.com/docs/migration/install)
|
||||
* [First Steps](https://docs-multiplayer.unity3d.com/docs/tutorials/helloworld/helloworldintro)
|
||||
* [API Reference](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
# Technical details
|
||||
|
||||
## Requirements
|
||||
|
||||
This version of Netcode for GameObjects is compatible with the following Unity versions and platforms:
|
||||
|
||||
* 2020.3 and later
|
||||
* Windows, Mac, Linux platforms
|
||||
|
||||
## Document revision history
|
||||
|
||||
|Date|Reason|
|
||||
|---|---|
|
||||
|March 10, 2021|Document created. Matches package version 0.1.0|
|
||||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
||||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
||||
|August 5, 2021|Update product/package name|
|
||||
|September 9,2021|Updated the links and name of the file.|
|
||||
35
Documentation~/index.md
Normal file
35
Documentation~/index.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# About Netcode for GameObjects
|
||||
|
||||
Netcode for GameObjects is a Unity package that provides networking capabilities to GameObject & MonoBehaviour workflows.
|
||||
|
||||
## Guides
|
||||
|
||||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game:
|
||||
|
||||
- [Documentation](https://docs-multiplayer.unity3d.com/netcode/current/about)
|
||||
- [Installation](https://docs-multiplayer.unity3d.com/netcode/current/migration/install)
|
||||
- [First Steps](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro)
|
||||
- [API Reference](https://docs-multiplayer.unity3d.com/netcode/current/api/introduction)
|
||||
|
||||
# Technical details
|
||||
|
||||
## Requirements
|
||||
|
||||
Netcode for GameObjects targets the following Unity versions:
|
||||
- Unity 2020.3, 2021.1, 2021.2 and 2021.3
|
||||
|
||||
On the following runtime platforms:
|
||||
- Windows, MacOS, and Linux
|
||||
- iOS and Android
|
||||
- Most closed platforms, such as consoles. Contact us for more information about specific closed platforms.
|
||||
|
||||
## Document revision history
|
||||
|
||||
|Date|Reason|
|
||||
|---|---|
|
||||
|March 10, 2021|Document created. Matches package version 0.1.0|
|
||||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.|
|
||||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0|
|
||||
|August 5, 2021|Update product/package name|
|
||||
|September 9,2021|Updated the links and name of the file.|
|
||||
|April 20, 2022|Updated links|
|
||||
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Unity.Collections;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
using UnityEngine;
|
||||
@@ -27,6 +28,8 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
|
||||
public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName;
|
||||
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
|
||||
public static readonly string INetworkSerializeByMemcpy_FullName = typeof(INetworkSerializeByMemcpy).FullName;
|
||||
public static readonly string IUTF8Bytes_FullName = typeof(IUTF8Bytes).FullName;
|
||||
public static readonly string UnityColor_FullName = typeof(Color).FullName;
|
||||
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;
|
||||
public static readonly string UnityVector2_FullName = typeof(Vector2).FullName;
|
||||
@@ -77,6 +80,35 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string FullNameWithGenericParameters(this TypeReference typeReference, GenericParameter[] contextGenericParameters, TypeReference[] contextGenericParameterTypes)
|
||||
{
|
||||
var name = typeReference.FullName;
|
||||
if (typeReference.HasGenericParameters)
|
||||
{
|
||||
name += "<";
|
||||
for (var i = 0; i < typeReference.Resolve().GenericParameters.Count; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
name += ", ";
|
||||
}
|
||||
|
||||
for (var j = 0; j < contextGenericParameters.Length; ++j)
|
||||
{
|
||||
if (typeReference.GenericParameters[i].FullName == contextGenericParameters[i].FullName)
|
||||
{
|
||||
name += contextGenericParameterTypes[i].FullName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name += ">";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
|
||||
{
|
||||
if (typeReference.IsArray)
|
||||
|
||||
@@ -18,8 +18,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
public override ILPPInterface GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
|
||||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
|
||||
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
|
||||
@@ -2,14 +2,11 @@ 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
|
||||
{
|
||||
@@ -24,6 +21,30 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
|
||||
|
||||
private TypeReference ResolveGenericType(TypeReference type, List<TypeReference> typeStack)
|
||||
{
|
||||
var genericName = type.Name;
|
||||
var lastType = (GenericInstanceType)typeStack[typeStack.Count - 1];
|
||||
var resolvedType = lastType.Resolve();
|
||||
typeStack.RemoveAt(typeStack.Count - 1);
|
||||
for (var i = 0; i < resolvedType.GenericParameters.Count; ++i)
|
||||
{
|
||||
var parameter = resolvedType.GenericParameters[i];
|
||||
if (parameter.Name == genericName)
|
||||
{
|
||||
var underlyingType = lastType.GenericArguments[i];
|
||||
if (underlyingType.Resolve() == null)
|
||||
{
|
||||
return ResolveGenericType(underlyingType, typeStack);
|
||||
}
|
||||
|
||||
return underlyingType;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly))
|
||||
@@ -31,7 +52,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
m_Diagnostics.Clear();
|
||||
|
||||
// read
|
||||
@@ -48,22 +68,26 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ImportReferences(mainModule))
|
||||
{
|
||||
var types = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
// process `INetworkMessage` types
|
||||
if (types.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var structTypes = mainModule.GetTypes()
|
||||
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName) && !t.Resolve().IsAbstract && !t.Resolve().HasGenericParameters && t.Resolve().IsValueType)
|
||||
.ToList();
|
||||
|
||||
CreateModuleInitializer(assemblyDefinition, types);
|
||||
}
|
||||
else
|
||||
foreach (var type in structTypes)
|
||||
{
|
||||
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
|
||||
// We'll avoid some confusion by ensuring users only choose one of the two
|
||||
// serialization schemes - by method OR by memcpy, not both. We'll also do a cursory
|
||||
// check that INetworkSerializeByMemcpy types are unmanaged.
|
||||
if (type.HasInterface(CodeGenHelpers.INetworkSerializeByMemcpy_FullName))
|
||||
{
|
||||
if (type.HasInterface(CodeGenHelpers.INetworkSerializable_FullName))
|
||||
{
|
||||
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types may not implement {nameof(INetworkSerializable)} - choose one or the other.");
|
||||
}
|
||||
if (!type.IsValueType)
|
||||
{
|
||||
m_Diagnostics.AddError($"{nameof(INetworkSerializeByMemcpy)} types must be unmanaged types.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -93,75 +117,5 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
|
||||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
|
||||
}
|
||||
|
||||
private MethodReference m_InitializeDelegates_MethodRef;
|
||||
|
||||
private const string k_InitializeMethodName = nameof(NetworkVariableHelper.InitializeDelegates);
|
||||
|
||||
private bool ImportReferences(ModuleDefinition moduleDefinition)
|
||||
{
|
||||
|
||||
var helperType = typeof(NetworkVariableHelper);
|
||||
foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
{
|
||||
case k_InitializeMethodName:
|
||||
m_InitializeDelegates_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
if (staticCtorMethodDef == null)
|
||||
{
|
||||
staticCtorMethodDef = new MethodDefinition(
|
||||
".cctor", // Static Constructor (constant-constructor)
|
||||
MethodAttributes.HideBySig |
|
||||
MethodAttributes.SpecialName |
|
||||
MethodAttributes.RTSpecialName |
|
||||
MethodAttributes.Static,
|
||||
typeDefinition.Module.TypeSystem.Void);
|
||||
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
typeDefinition.Methods.Add(staticCtorMethodDef);
|
||||
}
|
||||
|
||||
return staticCtorMethodDef;
|
||||
}
|
||||
|
||||
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
|
||||
// message types in the assembly with MessagingSystem.
|
||||
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
|
||||
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
|
||||
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
|
||||
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkSerializableTypes)
|
||||
{
|
||||
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 networkSerializableTypes)
|
||||
{
|
||||
var method = new GenericInstanceMethod(m_InitializeDelegates_MethodRef);
|
||||
method.GenericArguments.Add(type);
|
||||
instructions.Add(processor.Create(OpCodes.Call, method));
|
||||
}
|
||||
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,6 +509,12 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (methodDefinition.HasGenericParameters)
|
||||
{
|
||||
m_Diagnostics.AddError(methodDefinition, "RPC method must not be generic!");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (methodDefinition.ReturnType != methodDefinition.Module.TypeSystem.Void)
|
||||
{
|
||||
m_Diagnostics.AddError(methodDefinition, "RPC method must return `void`!");
|
||||
@@ -533,6 +539,10 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
rpcAttribute = customAttribute;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,11 +585,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
var checkType = paramType.Resolve();
|
||||
if (paramType.IsArray)
|
||||
{
|
||||
checkType = paramType.GetElementType().Resolve();
|
||||
checkType = ((ArrayType)paramType).ElementType.Resolve();
|
||||
}
|
||||
|
||||
if ((parameters[0].ParameterType.Resolve() == checkType ||
|
||||
(parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
|
||||
(parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
if (parameters[0].ParameterType == paramType ||
|
||||
(parameters[0].ParameterType == paramType.MakeByReferenceType() && parameters[0].IsIn))
|
||||
{
|
||||
return method;
|
||||
}
|
||||
@@ -591,10 +607,25 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
var meetsConstraints = true;
|
||||
foreach (var constraint in method.GenericParameters[0].Constraints)
|
||||
{
|
||||
#if CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES
|
||||
var resolvedConstraint = constraint.Resolve();
|
||||
var constraintTypeRef = constraint;
|
||||
#else
|
||||
var resolvedConstraint = constraint.ConstraintType.Resolve();
|
||||
var constraintTypeRef = constraint.ConstraintType;
|
||||
#endif
|
||||
|
||||
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraint.FullName)) ||
|
||||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)) ||
|
||||
var resolvedConstraintName = resolvedConstraint.FullNameWithGenericParameters(new[] { method.GenericParameters[0] }, new[] { checkType });
|
||||
if (constraintTypeRef.IsGenericInstance)
|
||||
{
|
||||
var genericConstraint = (GenericInstanceType)constraintTypeRef;
|
||||
if (genericConstraint.HasGenericArguments && genericConstraint.GenericArguments[0].Resolve() != null)
|
||||
{
|
||||
resolvedConstraintName = constraintTypeRef.FullName;
|
||||
}
|
||||
}
|
||||
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
||||
{
|
||||
meetsConstraints = false;
|
||||
@@ -605,7 +636,14 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
if (meetsConstraints)
|
||||
{
|
||||
var instanceMethod = new GenericInstanceMethod(method);
|
||||
instanceMethod.GenericArguments.Add(checkType);
|
||||
if (paramType.IsArray)
|
||||
{
|
||||
instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType);
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceMethod.GenericArguments.Add(paramType);
|
||||
}
|
||||
return instanceMethod;
|
||||
}
|
||||
}
|
||||
@@ -653,13 +691,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
// Try NetworkSerializable first because INetworkSerializable may also be valid for WriteValueSafe
|
||||
// and that would cause boxing if so.
|
||||
var typeMethod = GetFastBufferWriterWriteMethod("WriteNetworkSerializable", paramType);
|
||||
if (typeMethod == null)
|
||||
{
|
||||
typeMethod = GetFastBufferWriterWriteMethod(k_WriteValueMethodName, paramType);
|
||||
}
|
||||
var typeMethod = GetFastBufferWriterWriteMethod(k_WriteValueMethodName, paramType);
|
||||
if (typeMethod != null)
|
||||
{
|
||||
methodRef = m_MainModule.ImportReference(typeMethod);
|
||||
@@ -699,28 +731,67 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
var checkType = paramType.Resolve();
|
||||
if (paramType.IsArray)
|
||||
{
|
||||
checkType = paramType.GetElementType().Resolve();
|
||||
checkType = ((ArrayType)paramType).ElementType.Resolve();
|
||||
}
|
||||
|
||||
if (methodParam.Resolve() == checkType.Resolve() || methodParam.Resolve() == checkType.MakeByReferenceType().Resolve())
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
if (methodParam.Resolve() == paramType || methodParam.Resolve() == paramType.MakeByReferenceType().Resolve())
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
if (method.HasGenericParameters && method.GenericParameters.Count == 1)
|
||||
{
|
||||
if (method.GenericParameters[0].HasConstraints)
|
||||
{
|
||||
var meetsConstraints = true;
|
||||
foreach (var constraint in method.GenericParameters[0].Constraints)
|
||||
{
|
||||
#if CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES
|
||||
var resolvedConstraint = constraint.Resolve();
|
||||
var constraintTypeRef = constraint;
|
||||
#else
|
||||
var resolvedConstraint = constraint.ConstraintType.Resolve();
|
||||
var constraintTypeRef = constraint.ConstraintType;
|
||||
#endif
|
||||
|
||||
if ((resolvedConstraint.IsInterface && checkType.HasInterface(resolvedConstraint.FullName)) ||
|
||||
(resolvedConstraint.IsClass && checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)))
|
||||
|
||||
var resolvedConstraintName = resolvedConstraint.FullNameWithGenericParameters(new[] { method.GenericParameters[0] }, new[] { checkType });
|
||||
if (constraintTypeRef.IsGenericInstance)
|
||||
{
|
||||
var instanceMethod = new GenericInstanceMethod(method);
|
||||
instanceMethod.GenericArguments.Add(checkType);
|
||||
return instanceMethod;
|
||||
var genericConstraint = (GenericInstanceType)constraintTypeRef;
|
||||
if (genericConstraint.HasGenericArguments && genericConstraint.GenericArguments[0].Resolve() != null)
|
||||
{
|
||||
resolvedConstraintName = constraintTypeRef.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraintName)) ||
|
||||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
|
||||
{
|
||||
meetsConstraints = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (meetsConstraints)
|
||||
{
|
||||
var instanceMethod = new GenericInstanceMethod(method);
|
||||
if (paramType.IsArray)
|
||||
{
|
||||
instanceMethod.GenericArguments.Add(((ArrayType)paramType).ElementType);
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceMethod.GenericArguments.Add(paramType);
|
||||
}
|
||||
|
||||
return instanceMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -751,13 +822,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
// Try NetworkSerializable first because INetworkSerializable may also be valid for ReadValueSafe
|
||||
// and that would cause boxing if so.
|
||||
var typeMethod = GetFastBufferReaderReadMethod("ReadNetworkSerializable", paramType);
|
||||
if (typeMethod == null)
|
||||
{
|
||||
typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
|
||||
}
|
||||
var typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType);
|
||||
if (typeMethod != null)
|
||||
{
|
||||
methodRef = m_MainModule.ImportReference(typeMethod);
|
||||
@@ -1003,6 +1068,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
// bufferWriter.WriteValueSafe(isSet);
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex));
|
||||
|
||||
for (var i = 1; i < boolMethodRef.Parameters.Count; ++i)
|
||||
{
|
||||
var param = boolMethodRef.Parameters[i];
|
||||
methodDefinition.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||
int overloadParamLocalIdx = methodDefinition.Body.Variables.Count - 1;
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, overloadParamLocalIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Initobj, param.ParameterType));
|
||||
instructions.Add(processor.Create(OpCodes.Ldloc, overloadParamLocalIdx));
|
||||
}
|
||||
|
||||
instructions.Add(processor.Create(OpCodes.Call, boolMethodRef));
|
||||
|
||||
// if(isSet) {
|
||||
@@ -1055,11 +1131,38 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
instructions.Add(processor.Create(OpCodes.Ldc_I4_0));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isExtensionMethod && methodRef.Parameters.Count > 2)
|
||||
{
|
||||
for (var i = 2; i < methodRef.Parameters.Count; ++i)
|
||||
{
|
||||
var param = methodRef.Parameters[i];
|
||||
methodDefinition.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||
int overloadParamLocalIdx = methodDefinition.Body.Variables.Count - 1;
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, overloadParamLocalIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Initobj, param.ParameterType));
|
||||
instructions.Add(processor.Create(OpCodes.Ldloc, overloadParamLocalIdx));
|
||||
}
|
||||
}
|
||||
else if (!isExtensionMethod && methodRef.Parameters.Count > 1)
|
||||
{
|
||||
for (var i = 1; i < methodRef.Parameters.Count; ++i)
|
||||
{
|
||||
var param = methodRef.Parameters[i];
|
||||
methodDefinition.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||
int overloadParamLocalIdx = methodDefinition.Body.Variables.Count - 1;
|
||||
instructions.Add(processor.Create(OpCodes.Ldloca, overloadParamLocalIdx));
|
||||
instructions.Add(processor.Create(OpCodes.Initobj, param.ParameterType));
|
||||
instructions.Add(processor.Create(OpCodes.Ldloc, overloadParamLocalIdx));
|
||||
}
|
||||
}
|
||||
}
|
||||
instructions.Add(processor.Create(OpCodes.Call, methodRef));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError(methodDefinition, $"Don't know how to serialize {paramType.Name} - implement {nameof(INetworkSerializable)} or add an extension method for {nameof(FastBufferWriter)}.{k_WriteValueMethodName} to define serialization.");
|
||||
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1298,6 +1401,17 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
int isSetLocalIndex = rpcHandler.Body.Variables.Count - 1;
|
||||
processor.Emit(OpCodes.Ldarga, 1);
|
||||
processor.Emit(OpCodes.Ldloca, isSetLocalIndex);
|
||||
|
||||
for (var i = 1; i < boolMethodRef.Parameters.Count; ++i)
|
||||
{
|
||||
var param = boolMethodRef.Parameters[i];
|
||||
rpcHandler.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||
int overloadParamLocalIdx = rpcHandler.Body.Variables.Count - 1;
|
||||
processor.Emit(OpCodes.Ldloca, overloadParamLocalIdx);
|
||||
processor.Emit(OpCodes.Initobj, param.ParameterType);
|
||||
processor.Emit(OpCodes.Ldloc, overloadParamLocalIdx);
|
||||
}
|
||||
|
||||
processor.Emit(OpCodes.Call, boolMethodRef);
|
||||
|
||||
// paramType param = null;
|
||||
@@ -1331,11 +1445,38 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
{
|
||||
processor.Emit(OpCodes.Ldc_I4_0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isExtensionMethod && methodRef.Parameters.Count > 2)
|
||||
{
|
||||
for (var i = 2; i < methodRef.Parameters.Count; ++i)
|
||||
{
|
||||
var param = methodRef.Parameters[i];
|
||||
rpcHandler.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||
int overloadParamLocalIdx = rpcHandler.Body.Variables.Count - 1;
|
||||
processor.Emit(OpCodes.Ldloca, overloadParamLocalIdx);
|
||||
processor.Emit(OpCodes.Initobj, param.ParameterType);
|
||||
processor.Emit(OpCodes.Ldloc, overloadParamLocalIdx);
|
||||
}
|
||||
}
|
||||
else if (!isExtensionMethod && methodRef.Parameters.Count > 1)
|
||||
{
|
||||
for (var i = 1; i < methodRef.Parameters.Count; ++i)
|
||||
{
|
||||
var param = methodRef.Parameters[i];
|
||||
rpcHandler.Body.Variables.Add(new VariableDefinition(param.ParameterType));
|
||||
int overloadParamLocalIdx = rpcHandler.Body.Variables.Count - 1;
|
||||
processor.Emit(OpCodes.Ldloca, overloadParamLocalIdx);
|
||||
processor.Emit(OpCodes.Initobj, param.ParameterType);
|
||||
processor.Emit(OpCodes.Ldloc, overloadParamLocalIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
processor.Emit(OpCodes.Call, methodRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Diagnostics.AddError(methodDefinition, $"Don't know how to deserialize {paramType.Name} - implement {nameof(INetworkSerializable)} or add an extension method for {nameof(FastBufferReader)}.{k_ReadValueMethodName} to define serialization.");
|
||||
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} - Don't know how to serialize {paramType.Name}. RPC parameter types must either implement {nameof(INetworkSerializeByMemcpy)} or {nameof(INetworkSerializable)}. If this type is external and you are sure its memory layout makes it serializable by memcpy, you can replace {paramType} with {typeof(ForceNetworkSerializeByMemcpy<>).Name}<{paramType}>, or you can create extension methods for {nameof(FastBufferReader)}.{nameof(FastBufferReader.ReadValueSafe)}(this {nameof(FastBufferReader)}, out {paramType}) and {nameof(FastBufferWriter)}.{nameof(FastBufferWriter.WriteValueSafe)}(this {nameof(FastBufferWriter)}, in {paramType}) to define serialization for this type.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,9 +52,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
case nameof(NetworkBehaviour):
|
||||
ProcessNetworkBehaviour(typeDefinition);
|
||||
break;
|
||||
case nameof(NetworkVariableHelper):
|
||||
ProcessNetworkVariableHelper(typeDefinition);
|
||||
break;
|
||||
case nameof(__RpcParams):
|
||||
typeDefinition.IsPublic = true;
|
||||
break;
|
||||
@@ -103,17 +100,6 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkVariableHelper(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var methodDefinition in typeDefinition.Methods)
|
||||
{
|
||||
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegates))
|
||||
{
|
||||
methodDefinition.IsPublic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
|
||||
{
|
||||
foreach (var nestedType in typeDefinition.NestedTypes)
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
"name": "Unity.Netcode.Editor.CodeGen",
|
||||
"rootNamespace": "Unity.Netcode.Editor.CodeGen",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime"
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
@@ -15,5 +17,14 @@
|
||||
"Mono.Cecil.Pdb.dll",
|
||||
"Mono.Cecil.Rocks.dll"
|
||||
],
|
||||
"autoReferenced": false
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.nuget.mono-cecil",
|
||||
"expression": "(0,1.11.4)",
|
||||
"define": "CECIL_CONSTRAINTS_ARE_TYPE_REFERENCES"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Unity.Netcode.Components;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkAnimator), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkAnimatorEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var label = new GUIContent("Animator", "The Animator component to synchronize");
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Animator"), label);
|
||||
EditorGUI.EndChangeCheck();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,8 @@ namespace Unity.Netcode.Editor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!m_Initialized)
|
||||
@@ -211,5 +213,136 @@ namespace Unity.Netcode.Editor
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once when a NetworkBehaviour component is
|
||||
/// displayed in the inspector view.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
// This can be null and throw an exception when running test runner in the editor
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// When we first add a NetworkBehaviour this editor will be enabled
|
||||
// so we go ahead and check for an already existing NetworkObject here
|
||||
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
|
||||
}
|
||||
|
||||
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
|
||||
|
||||
/// <summary>
|
||||
/// Recursively finds the root parent of a <see cref="Transform"/>
|
||||
/// </summary>
|
||||
/// <param name="transform">The current <see cref="Transform"/> we are inspecting for a parent</param>
|
||||
/// <returns>the root parent for the first <see cref="Transform"/> passed into the method</returns>
|
||||
public static Transform GetRootParentTransform(Transform transform)
|
||||
{
|
||||
if (transform.parent == null || transform.parent == transform)
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
return GetRootParentTransform(transform.parent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if a GameObject has one or more NetworkBehaviours but
|
||||
/// does not already have a NetworkObject component. If not it will notify
|
||||
/// the user that NetworkBehaviours require a NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="gameObject"><see cref="GameObject"/> to start checking for a <see cref="NetworkObject"/></param>
|
||||
/// <param name="networkObjectRemoved">used internally</param>
|
||||
public static void CheckForNetworkObject(GameObject gameObject, bool networkObjectRemoved = false)
|
||||
{
|
||||
// If there are no NetworkBehaviours or no gameObject, then exit early
|
||||
if (gameObject == null || (gameObject.GetComponent<NetworkBehaviour>() == null && gameObject.GetComponentInChildren<NetworkBehaviour>() == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Now get the root parent transform to the current GameObject (or itself)
|
||||
var rootTransform = GetRootParentTransform(gameObject.transform);
|
||||
var networkManager = rootTransform.GetComponent<NetworkManager>();
|
||||
if (networkManager == null)
|
||||
{
|
||||
networkManager = rootTransform.GetComponentInChildren<NetworkManager>();
|
||||
}
|
||||
|
||||
// If there is a NetworkManager, then notify the user that a NetworkManager cannot have NetworkBehaviour components
|
||||
if (networkManager != null)
|
||||
{
|
||||
var networkBehaviours = networkManager.gameObject.GetComponents<NetworkBehaviour>();
|
||||
var networkBehavioursChildren = networkManager.gameObject.GetComponentsInChildren<NetworkBehaviour>();
|
||||
if (networkBehaviours.Length > 0 || networkBehavioursChildren.Length > 0)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("NetworkBehaviour or NetworkManager Cannot Be Added", $"{nameof(NetworkManager)}s cannot have {nameof(NetworkBehaviour)} components added to the root parent or any of its children." +
|
||||
$" Would you like to remove the NetworkManager or NetworkBehaviour?", "NetworkManager", "NetworkBehaviour"))
|
||||
{
|
||||
DestroyImmediate(networkManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var networkBehaviour in networkBehaviours)
|
||||
{
|
||||
DestroyImmediate(networkBehaviour);
|
||||
}
|
||||
|
||||
foreach (var networkBehaviour in networkBehaviours)
|
||||
{
|
||||
DestroyImmediate(networkBehaviour);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, check to see if there is any NetworkObject from the root GameObject down to all children.
|
||||
// If not, notify the user that NetworkBehaviours require that the relative GameObject has a NetworkObject component.
|
||||
var networkObject = rootTransform.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
networkObject = rootTransform.GetComponentInChildren<NetworkObject>();
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
// If we are removing a NetworkObject but there is still one or more NetworkBehaviour components
|
||||
// and the user has already turned "Auto-Add NetworkObject" on when first notified about the requirement
|
||||
// then just send a reminder to the user why the NetworkObject they just deleted seemingly "re-appeared"
|
||||
// again.
|
||||
if (networkObjectRemoved && EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists) && EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists))
|
||||
{
|
||||
Debug.LogWarning($"{gameObject.name} still has {nameof(NetworkBehaviour)}s and Auto-Add NetworkObjects is enabled. A NetworkObject is being added back to {gameObject.name}.");
|
||||
Debug.Log($"To reset Auto-Add NetworkObjects: Select the Netcode->General->Reset Auto-Add NetworkObject menu item.");
|
||||
}
|
||||
|
||||
// Notify and provide the option to add it one time, always add a NetworkObject, or do nothing and let the user manually add it
|
||||
if (EditorUtility.DisplayDialog($"{nameof(NetworkBehaviour)}s require a {nameof(NetworkObject)}",
|
||||
$"{gameObject.name} does not have a {nameof(NetworkObject)} component. Would you like to add one now?", "Yes", "No (manually add it)",
|
||||
DialogOptOutDecisionType.ForThisMachine, AutoAddNetworkObjectIfNoneExists))
|
||||
{
|
||||
gameObject.AddComponent<NetworkObject>();
|
||||
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(activeScene);
|
||||
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This allows users to reset the Auto-Add NetworkObject preference
|
||||
/// so the next time they add a NetworkBehaviour to a GameObject without
|
||||
/// a NetworkObject it will display the dialog box again and not
|
||||
/// automatically add a NetworkObject.
|
||||
/// </summary>
|
||||
[MenuItem("Netcode/General/Reset Auto-Add NetworkObject", false, 1)]
|
||||
private static void ResetMultiplayerToolsTipStatus()
|
||||
{
|
||||
if (EditorPrefs.HasKey(AutoAddNetworkObjectIfNoneExists))
|
||||
{
|
||||
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ using UnityEditorInternal;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// This <see cref="CustomEditor"/> handles the translation between the <see cref="NetworkConfig"/> and
|
||||
/// the <see cref="NetworkManager"/> properties.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkManagerEditor : UnityEditor.Editor
|
||||
@@ -135,9 +139,13 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true);
|
||||
m_NetworkPrefabsList.elementHeightCallback = index =>
|
||||
{
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
var networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
var networkOverrideInt = 0;
|
||||
if (m_NetworkPrefabsList.count > 0)
|
||||
{
|
||||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override));
|
||||
networkOverrideInt = networkOverrideProp.enumValueIndex;
|
||||
}
|
||||
|
||||
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5);
|
||||
};
|
||||
@@ -196,6 +204,7 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
@@ -359,7 +368,7 @@ namespace Unity.Netcode.Editor
|
||||
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
|
||||
const string openDocsButtonText = "Open Docs";
|
||||
const string dismissButtonText = "Dismiss";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tools/install-tools";
|
||||
const string targetUrl = "https://docs-multiplayer.unity3d.com/netcode/current/tools/install-tools";
|
||||
const string infoIconName = "console.infoicon";
|
||||
|
||||
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
@@ -25,7 +27,6 @@ namespace Unity.Netcode.Editor
|
||||
{
|
||||
Singleton = new NetworkManagerHelper();
|
||||
NetworkManager.NetworkManagerHelper = Singleton;
|
||||
|
||||
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
|
||||
|
||||
@@ -40,20 +41,106 @@ namespace Unity.Netcode.Editor
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Clear();
|
||||
ScenesInBuildActiveSceneCheck();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects if a user is trying to enter into play mode when both conditions are true:
|
||||
/// - the currently active and open scene is not added to the scenes in build list
|
||||
/// - an instance of a NetworkManager with scene management enabled can be found
|
||||
/// If both conditions are met then the user is presented with a dialog box that
|
||||
/// provides the user with the option to add the scene to the scenes in build list
|
||||
/// before entering into play mode or the user can continue under those conditions.
|
||||
///
|
||||
/// ** When scene management is enabled the user should treat all scenes that need to
|
||||
/// be synchronized using network scene management as if they were preparing for a build.
|
||||
/// Any scene that needs to be loaded at run time has to be included in the scenes in
|
||||
/// build list. **
|
||||
/// </summary>
|
||||
private static void ScenesInBuildActiveSceneCheck()
|
||||
{
|
||||
var scenesList = EditorBuildSettings.scenes.ToList();
|
||||
var activeScene = SceneManager.GetActiveScene();
|
||||
var isSceneInBuildSettings = scenesList.Where((c) => c.path == activeScene.path).Count() == 1;
|
||||
var networkManager = Object.FindObjectOfType<NetworkManager>();
|
||||
if (!isSceneInBuildSettings && networkManager != null)
|
||||
{
|
||||
if (networkManager.NetworkConfig != null && networkManager.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Add Scene to Scenes in Build", $"The current scene was not found in the scenes" +
|
||||
$" in build and a {nameof(NetworkManager)} instance was found with scene management enabled! Clients will not be able " +
|
||||
$"to synchronize to this scene unless it is added to the scenes in build list.\n\nWould you like to add it now?",
|
||||
"Yes", "No - Continue"))
|
||||
{
|
||||
scenesList.Add(new EditorBuildSettingsScene(activeScene.path, true));
|
||||
EditorBuildSettings.scenes = scenesList.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked only when the hierarchy changes
|
||||
/// </summary>
|
||||
private static void EditorApplication_hierarchyChanged()
|
||||
{
|
||||
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
|
||||
foreach (var networkManager in allNetworkManagers)
|
||||
{
|
||||
networkManager.NetworkManagerCheckForParent();
|
||||
if (!networkManager.NetworkManagerCheckForParent())
|
||||
{
|
||||
Singleton.CheckAndNotifyUserNetworkObjectRemoved(networkManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles notifying users that they cannot add a NetworkObject component
|
||||
/// to a GameObject that also has a NetworkManager component. The NetworkObject
|
||||
/// will always be removed.
|
||||
/// GameObject + NetworkObject then NetworkManager = NetworkObject removed
|
||||
/// GameObject + NetworkManager then NetworkObject = NetworkObject removed
|
||||
/// Note: Since this is always invoked after <see cref="NetworkManagerCheckForParent"/>
|
||||
/// we do not need to check for parent when searching for a NetworkObject component
|
||||
/// </summary>
|
||||
public void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false)
|
||||
{
|
||||
// Check for any NetworkObject at the same gameObject relative layer
|
||||
var networkObject = networkManager.gameObject.GetComponent<NetworkObject>();
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
// if none is found, check to see if any children have a NetworkObject
|
||||
networkObject = networkManager.gameObject.GetComponentInChildren<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!EditorApplication.isUpdating)
|
||||
{
|
||||
Object.DestroyImmediate(networkObject);
|
||||
|
||||
if (!EditorApplication.isPlaying && !editorTest)
|
||||
{
|
||||
EditorUtility.DisplayDialog($"Removing {nameof(NetworkObject)}", NetworkManagerAndNetworkObjectNotAllowedMessage(), "OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(NetworkManagerAndNetworkObjectNotAllowedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string NetworkManagerAndNetworkObjectNotAllowedMessage()
|
||||
{
|
||||
return $"A {nameof(GameObject)} cannot have both a {nameof(NetworkManager)} and {nameof(NetworkObject)} assigned to it or any children under it.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
|
||||
/// When in edit mode it provides the option to automatically fix the issue
|
||||
|
||||
@@ -4,6 +4,9 @@ using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CustomEditor"/> for <see cref="NetworkObject"/>
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkObject), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkObjectEditor : UnityEditor.Editor
|
||||
@@ -23,6 +26,7 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkObject = (NetworkObject)target;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
Initialize();
|
||||
@@ -100,5 +104,32 @@ namespace Unity.Netcode.Editor
|
||||
GUI.enabled = guiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
// Saved for use in OnDestroy
|
||||
private GameObject m_GameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once when a NetworkObject component is
|
||||
/// displayed in the inspector view.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
// We set the GameObject upon being enabled because when the
|
||||
// NetworkObject component is removed (i.e. when OnDestroy is invoked)
|
||||
// it is no longer valid/available.
|
||||
m_GameObject = (target as NetworkObject).gameObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once when a NetworkObject component is
|
||||
/// no longer displayed in the inspector view.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Since this is also invoked when a NetworkObject component is removed
|
||||
// from a GameObject, we go ahead and check for a NetworkObject when
|
||||
// this custom editor is destroyed.
|
||||
NetworkBehaviourEditor.CheckForNetworkObject(m_GameObject, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ using Unity.Netcode.Components;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
[CustomEditor(typeof(NetworkTransform))]
|
||||
/// <summary>
|
||||
/// The <see cref="CustomEditor"/> for <see cref="NetworkTransform"/>
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NetworkTransform), true)]
|
||||
public class NetworkTransformEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty m_SyncPositionXProperty;
|
||||
@@ -28,6 +31,7 @@ namespace Unity.Netcode.Editor
|
||||
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation");
|
||||
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnEnable()
|
||||
{
|
||||
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
|
||||
@@ -46,6 +50,7 @@ namespace Unity.Netcode.Editor
|
||||
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel);
|
||||
@@ -112,6 +117,7 @@ namespace Unity.Netcode.Editor
|
||||
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
||||
EditorGUILayout.PropertyField(m_InterpolateProperty);
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
// if rigidbody is present but network rigidbody is not present
|
||||
var go = ((NetworkTransform)target).gameObject;
|
||||
if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false)
|
||||
@@ -119,12 +125,15 @@ namespace Unity.Netcode.Editor
|
||||
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" +
|
||||
"Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning);
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
|
||||
#if COM_UNITY_MODULES_PHYSICS2D
|
||||
if (go.TryGetComponent<Rigidbody2D>(out _) && go.TryGetComponent<NetworkRigidbody2D>(out _) == false)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" +
|
||||
"Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning);
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS2D
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3106ae882c6ec416d855a44c97eeaeef
|
||||
guid: a325130169714440ba1b4878082e8956
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
53
Editor/PackageChecker/UTPAdapterChecker.cs
Normal file
53
Editor/PackageChecker/UTPAdapterChecker.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
#if COM_UNITY_NETCODE_ADAPTER_UTP
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
|
||||
namespace Unity.Netcode.Editor.PackageChecker
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
internal class UTPAdapterChecker
|
||||
{
|
||||
private const string k_UTPAdapterPackageName = "com.unity.netcode.adapter.utp";
|
||||
|
||||
private static ListRequest s_ListRequest = null;
|
||||
|
||||
static UTPAdapterChecker()
|
||||
{
|
||||
if (s_ListRequest == null)
|
||||
{
|
||||
s_ListRequest = Client.List();
|
||||
EditorApplication.update += EditorUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EditorUpdate()
|
||||
{
|
||||
if (!s_ListRequest.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorApplication.update -= EditorUpdate;
|
||||
|
||||
if (s_ListRequest.Status == StatusCode.Success)
|
||||
{
|
||||
if (s_ListRequest.Result.Any(p => p.name == k_UTPAdapterPackageName))
|
||||
{
|
||||
Debug.Log($"({nameof(UTPAdapterChecker)}) Found UTP Adapter package, it is no longer needed, `UnityTransport` is now directly integrated into the SDK therefore removing it from the project.");
|
||||
Client.Remove(k_UTPAdapterPackageName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = s_ListRequest.Error;
|
||||
Debug.LogError($"({nameof(UTPAdapterChecker)}) Cannot check the list of packages -> error #{error.errorCode}: {error.message}");
|
||||
}
|
||||
|
||||
s_ListRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_NETCODE_ADAPTER_UTP
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9e1475e8c8e4a6d935fe2409e3bd26
|
||||
guid: df5ed97df956b4aad91a221ba59fa304
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Unity.Netcode.Editor.PackageChecker",
|
||||
"rootNamespace": "Unity.Netcode.Editor.PackageChecker",
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.netcode.adapter.utp",
|
||||
"expression": "",
|
||||
"define": "COM_UNITY_NETCODE_ADAPTER_UTP"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78ac2a8d1365141f68da5d0a9e10dbc6
|
||||
guid: de64d7f9ca85d4bf59c8c24738bc1057
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
@@ -1,12 +1,16 @@
|
||||
# Netcode for GameObjects
|
||||
|
||||
[](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)
|
||||
[](https://docs-multiplayer.unity3d.com/netcode/current/about) [](https://docs-multiplayer.unity3d.com/netcode/current/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-multiplayer.unity3d.com/transport/1.0.0/introduction).
|
||||
Netcode for GameObjects is a Unity package that provides networking capabilities to GameObject & MonoBehaviour workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs-multiplayer.unity3d.com/transport/current/about).
|
||||
|
||||
### Getting Started
|
||||
|
||||
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
|
||||
|
||||
You can also jump right into our [Hello World](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/helloworld/helloworldintro) guide for a taste of how to use the framework for basic networked tasks.
|
||||
|
||||
### Community and Feedback
|
||||
|
||||
For general questions, networking advice or discussions about Netcode for GameObjects, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/).
|
||||
|
||||
@@ -5,10 +5,10 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
||||
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||
#endif
|
||||
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
|
||||
|
||||
|
||||
@@ -53,9 +53,21 @@ namespace Unity.Netcode
|
||||
public uint TickRate = 30;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait for handshake to complete before timing out a client
|
||||
/// The amount of seconds for the server to wait for the connection approval handshake to complete before the client is disconnected.
|
||||
///
|
||||
/// If the timeout is reached before approval is completed the client will be disconnected.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
|
||||
/// <remarks>
|
||||
/// The period begins after the <see cref="NetworkEvent.Connect"/> is received on the server.
|
||||
/// The period ends once the server finishes processing a <see cref="ConnectionRequestMessage"/> from the client.
|
||||
///
|
||||
/// This setting is independent of any Transport-level timeouts that may be in effect. It covers the time between
|
||||
/// the connection being established on the Transport layer, the client sending a
|
||||
/// <see cref="ConnectionRequestMessage"/>, and the server processing that message through <see cref="ConnectionApproval"/>.
|
||||
///
|
||||
/// This setting is server-side only.
|
||||
/// </remarks>
|
||||
[Tooltip("The amount of seconds for the server to wait for the connection approval handshake to complete before the client is disconnected")]
|
||||
public int ClientConnectionBufferTimeout = 10;
|
||||
|
||||
/// <summary>
|
||||
@@ -128,10 +140,10 @@ namespace Unity.Netcode
|
||||
public int LoadSceneTimeOut = 120;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
|
||||
/// The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
|
||||
public float MessageBufferTimeout = 20f;
|
||||
[Tooltip("The amount of time a message should be buffered if the asset or object needed to process it doesn't exist yet. If the asset is not added/object is not spawned within this time, it will be dropped")]
|
||||
public float SpawnTimeout = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable network logs.
|
||||
@@ -139,20 +151,15 @@ namespace Unity.Netcode
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
|
||||
/// The number of RTT samples that is kept as an average for calculations
|
||||
/// </summary>
|
||||
public bool UseSnapshotDelta { get; internal set; } = false;
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
|
||||
/// </summary>
|
||||
public bool UseSnapshotSpawn { get; internal set; } = false;
|
||||
/// <summary>
|
||||
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
|
||||
/// </summary>
|
||||
public int SnapshotMaxSpawnUsage { get; } = 1000;
|
||||
|
||||
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
|
||||
|
||||
/// <summary>
|
||||
/// The number of slots used for RTT calculations. This is the maximum amount of in-flight messages
|
||||
/// </summary>
|
||||
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
|
||||
|
||||
/// <summary>
|
||||
/// Returns a base64 encoded version of the configuration
|
||||
/// </summary>
|
||||
|
||||
@@ -20,6 +20,17 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// The NetworkObject's owned by this Client
|
||||
/// </summary>
|
||||
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
|
||||
public List<NetworkObject> OwnedObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PlayerObject != null && PlayerObject.NetworkManager != null && PlayerObject.NetworkManager.IsListening)
|
||||
{
|
||||
return PlayerObject.NetworkManager.SpawnManager.GetClientOwnedObjects(ClientId);
|
||||
}
|
||||
|
||||
return new List<NetworkObject>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
Runtime/Core/ComponentFactory.cs
Normal file
65
Runtime/Core/ComponentFactory.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to support testable code by allowing any supported component used by NetworkManager to be replaced
|
||||
/// with a mock component or a test version that overloads certain methods to change or record their behavior.
|
||||
/// Components currently supported by ComponentFactory:
|
||||
/// - IDeferredMessageManager
|
||||
/// </summary>
|
||||
internal static class ComponentFactory
|
||||
{
|
||||
internal delegate object CreateObjectDelegate(NetworkManager networkManager);
|
||||
|
||||
private static Dictionary<Type, CreateObjectDelegate> s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates an instance of a given interface
|
||||
/// </summary>
|
||||
/// <param name="networkManager">The network manager</param>
|
||||
/// <typeparam name="T">The interface to instantiate it with</typeparam>
|
||||
/// <returns></returns>
|
||||
public static T Create<T>(NetworkManager networkManager)
|
||||
{
|
||||
return (T)s_Delegates[typeof(T)](networkManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the default creation logic for a given interface type
|
||||
/// </summary>
|
||||
/// <param name="creator">The factory delegate to create the instance</param>
|
||||
/// <typeparam name="T">The interface type to override</typeparam>
|
||||
public static void Register<T>(CreateObjectDelegate creator)
|
||||
{
|
||||
s_Delegates[typeof(T)] = creator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts the creation logic for a given interface type to the default logic
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The interface type to revert</typeparam>
|
||||
public static void Deregister<T>()
|
||||
{
|
||||
s_Delegates.Remove(typeof(T));
|
||||
SetDefaults();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the default creation logic for all supported component types
|
||||
/// </summary>
|
||||
public static void SetDefaults()
|
||||
{
|
||||
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
|
||||
}
|
||||
|
||||
private static void SetDefault<T>(CreateObjectDelegate creator)
|
||||
{
|
||||
if (!s_Delegates.ContainsKey(typeof(T)))
|
||||
{
|
||||
s_Delegates[typeof(T)] = creator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Core/ComponentFactory.cs.meta
Normal file
3
Runtime/Core/ComponentFactory.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fda4c0eb89644fcea5416bbf98ea0ba0
|
||||
timeCreated: 1649966562
|
||||
@@ -1,388 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct IndexAllocatorEntry
|
||||
{
|
||||
internal int Pos; // Position where the memory of this slot is
|
||||
internal int Length; // Length of the memory allocated to this slot
|
||||
internal int Next; // Next and Prev define the order of the slots in the buffer
|
||||
internal int Prev;
|
||||
internal bool Free; // Whether this is a free slot
|
||||
}
|
||||
|
||||
internal class IndexAllocator
|
||||
{
|
||||
private const int k_NotSet = -1;
|
||||
private readonly int m_MaxSlot; // Maximum number of sections (free or not) in the buffer
|
||||
private readonly int m_BufferSize; // Size of the buffer we allocated into
|
||||
private int m_LastSlot = 0; // Last allocated slot
|
||||
private IndexAllocatorEntry[] m_Slots; // Array of slots
|
||||
private int[] m_IndexToSlot; // Mapping from the client's index to the slot index
|
||||
|
||||
internal IndexAllocator(int bufferSize, int maxSlot)
|
||||
{
|
||||
m_MaxSlot = maxSlot;
|
||||
m_BufferSize = bufferSize;
|
||||
m_Slots = new IndexAllocatorEntry[m_MaxSlot];
|
||||
m_IndexToSlot = new int[m_MaxSlot];
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset this IndexAllocator to an empty one, with the same sized buffer and slots
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
// todo: could be made faster, for example by having a last index
|
||||
// and not needing valid stuff past it
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
m_Slots[i].Free = true;
|
||||
m_Slots[i].Next = i + 1;
|
||||
m_Slots[i].Prev = i - 1;
|
||||
m_Slots[i].Pos = m_BufferSize;
|
||||
m_Slots[i].Length = 0;
|
||||
|
||||
m_IndexToSlot[i] = k_NotSet;
|
||||
}
|
||||
|
||||
m_Slots[0].Pos = 0;
|
||||
m_Slots[0].Length = m_BufferSize;
|
||||
m_Slots[0].Prev = k_NotSet;
|
||||
m_Slots[m_MaxSlot - 1].Next = k_NotSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of memory used
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the amount of memory used, starting at 0, ending after the last used slot
|
||||
/// </returns>
|
||||
internal int Range
|
||||
{
|
||||
get
|
||||
{
|
||||
// when the whole buffer is free, m_LastSlot points to an empty slot
|
||||
if (m_Slots[m_LastSlot].Free)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// otherwise return the end of the last slot used
|
||||
return m_Slots[m_LastSlot].Pos + m_Slots[m_LastSlot].Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a slot with "size" position, for index "index"
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Used in Deallocate to identify which slot</param>
|
||||
/// <param name="size">The size required. </param>
|
||||
/// <param name="pos">Returns the position to use in the buffer </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't enough memory available or no slots are large enough
|
||||
/// </returns>
|
||||
internal bool Allocate(int index, int size, out int pos)
|
||||
{
|
||||
pos = 0;
|
||||
// size must be positive, index must be within range
|
||||
if (size < 0 || index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// refuse allocation if the index is already in use
|
||||
if (m_IndexToSlot[index] != k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: this is the slowest part
|
||||
// improvement 1: list of free blocks (minor)
|
||||
// improvement 2: heap of free blocks
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
if (m_Slots[i].Free && m_Slots[i].Length >= size)
|
||||
{
|
||||
m_IndexToSlot[index] = i;
|
||||
|
||||
int leftOver = m_Slots[i].Length - size;
|
||||
int next = m_Slots[i].Next;
|
||||
if (m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[next].Pos -= leftOver;
|
||||
m_Slots[next].Length += leftOver;
|
||||
}
|
||||
else
|
||||
{
|
||||
int add = MoveSlotAfter(i);
|
||||
|
||||
m_Slots[add].Pos = m_Slots[i].Pos + size;
|
||||
m_Slots[add].Length = m_Slots[i].Length - size;
|
||||
}
|
||||
|
||||
m_Slots[i].Free = false;
|
||||
m_Slots[i].Length = size;
|
||||
|
||||
pos = m_Slots[i].Pos;
|
||||
|
||||
// if we allocate past the current range, we are the last slot
|
||||
if (m_Slots[i].Pos + m_Slots[i].Length > Range)
|
||||
{
|
||||
m_LastSlot = i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deallocate a slot
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Same index used in Allocate </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't an allocated slot at this index
|
||||
/// </returns>
|
||||
internal bool Deallocate(int index)
|
||||
{
|
||||
// size must be positive, index must be within range
|
||||
if (index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int slot = m_IndexToSlot[index];
|
||||
|
||||
if (slot == k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[slot].Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Slots[slot].Free = true;
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
// if previous slot was free, merge and grow
|
||||
if (prev != k_NotSet && m_Slots[prev].Free)
|
||||
{
|
||||
m_Slots[prev].Length += m_Slots[slot].Length;
|
||||
m_Slots[slot].Length = 0;
|
||||
|
||||
// if the slot we're merging was the last one, the last one is now the one we merged with
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = prev;
|
||||
}
|
||||
|
||||
// todo: verify what this does on full or nearly full cases
|
||||
MoveSlotToEnd(slot);
|
||||
slot = prev;
|
||||
}
|
||||
|
||||
next = m_Slots[slot].Next;
|
||||
|
||||
// merge with next slot if it is free
|
||||
if (next != k_NotSet && m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[slot].Length += m_Slots[next].Length;
|
||||
m_Slots[next].Length = 0;
|
||||
MoveSlotToEnd(next);
|
||||
}
|
||||
|
||||
// if we just deallocate the last one, we need to move last back
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = m_Slots[m_LastSlot].Prev;
|
||||
// if there's nothing allocated anymore, use 0
|
||||
if (m_LastSlot == k_NotSet)
|
||||
{
|
||||
m_LastSlot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// mark the index as available
|
||||
m_IndexToSlot[index] = k_NotSet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Take a slot at the end and link it to go just after "slot".
|
||||
// Used when allocating part of a slot and we need an entry for the rest
|
||||
// Returns the slot that was picked
|
||||
private int MoveSlotAfter(int slot)
|
||||
{
|
||||
int ret = m_Slots[m_MaxSlot - 1].Prev;
|
||||
int p0 = m_Slots[ret].Prev;
|
||||
|
||||
m_Slots[p0].Next = m_MaxSlot - 1;
|
||||
m_Slots[m_MaxSlot - 1].Prev = p0;
|
||||
|
||||
int p1 = m_Slots[slot].Next;
|
||||
m_Slots[slot].Next = ret;
|
||||
m_Slots[p1].Prev = ret;
|
||||
|
||||
m_Slots[ret].Prev = slot;
|
||||
m_Slots[ret].Next = p1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Move the slot "slot" to the end of the list.
|
||||
// Used when merging two slots, that gives us an extra entry at the end
|
||||
private void MoveSlotToEnd(int slot)
|
||||
{
|
||||
// if we're already there
|
||||
if (m_Slots[slot].Next == k_NotSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
m_Slots[prev].Next = next;
|
||||
if (next != k_NotSet)
|
||||
{
|
||||
m_Slots[next].Prev = prev;
|
||||
}
|
||||
|
||||
int p0 = m_Slots[m_MaxSlot - 1].Prev;
|
||||
|
||||
m_Slots[p0].Next = slot;
|
||||
m_Slots[slot].Next = m_MaxSlot - 1;
|
||||
|
||||
m_Slots[m_MaxSlot - 1].Prev = slot;
|
||||
m_Slots[slot].Prev = p0;
|
||||
|
||||
m_Slots[slot].Pos = m_BufferSize;
|
||||
}
|
||||
|
||||
// runs a bunch of consistency check on the Allocator
|
||||
internal bool Verify()
|
||||
{
|
||||
int pos = k_NotSet;
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
int endPos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int prev = pos;
|
||||
if (pos != k_NotSet)
|
||||
{
|
||||
pos = m_Slots[pos].Next;
|
||||
if (pos == k_NotSet)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Prev != prev)
|
||||
{
|
||||
// the previous is not correct
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Length < 0)
|
||||
{
|
||||
// Length should be positive
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev != k_NotSet && m_Slots[prev].Free && m_Slots[pos].Free && m_Slots[pos].Length > 0)
|
||||
{
|
||||
// should not have two consecutive free slots
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Pos != total)
|
||||
{
|
||||
// slots should all line up nicely
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Slots[pos].Free)
|
||||
{
|
||||
endPos = m_Slots[pos].Pos + m_Slots[pos].Length;
|
||||
}
|
||||
|
||||
total += m_Slots[pos].Length;
|
||||
count++;
|
||||
|
||||
} while (pos != k_NotSet);
|
||||
|
||||
if (count != m_MaxSlot)
|
||||
{
|
||||
// some slots were lost
|
||||
return false;
|
||||
}
|
||||
|
||||
if (total != m_BufferSize)
|
||||
{
|
||||
// total buffer should be accounted for
|
||||
return false;
|
||||
}
|
||||
|
||||
if (endPos != Range)
|
||||
{
|
||||
// end position should match reported end position
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Debug display the allocator structure
|
||||
internal void DebugDisplay()
|
||||
{
|
||||
string logMessage = "IndexAllocator structure\n";
|
||||
|
||||
bool[] seen = new bool[m_MaxSlot];
|
||||
|
||||
int pos = 0;
|
||||
int count = 0;
|
||||
bool prevEmpty = false;
|
||||
do
|
||||
{
|
||||
seen[pos] = true;
|
||||
count++;
|
||||
if (m_Slots[pos].Length == 0 && prevEmpty)
|
||||
{
|
||||
// don't display repetitive empty slots
|
||||
}
|
||||
else
|
||||
{
|
||||
logMessage += string.Format("{0}:{1}, {2} ({3}) \n", m_Slots[pos].Pos, m_Slots[pos].Length,
|
||||
m_Slots[pos].Free ? "Free" : "Used", pos);
|
||||
if (m_Slots[pos].Length == 0)
|
||||
{
|
||||
prevEmpty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
pos = m_Slots[pos].Next;
|
||||
} while (pos != k_NotSet && !seen[pos]);
|
||||
|
||||
logMessage += string.Format("{0} Total entries\n", count);
|
||||
|
||||
Debug.Log(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,37 +158,57 @@ namespace Unity.Netcode
|
||||
// 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 (clientRpcParams.Send.TargetClientIds != null)
|
||||
{
|
||||
foreach (var clientId in clientRpcParams.Send.TargetClientIds)
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
if (targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to make sure we are sending to only observers, if not log an error.
|
||||
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
|
||||
{
|
||||
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
|
||||
}
|
||||
}
|
||||
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
|
||||
}
|
||||
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
{
|
||||
foreach (var clientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
if (targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to make sure we are sending to only observers, if not log an error.
|
||||
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
|
||||
{
|
||||
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
|
||||
}
|
||||
}
|
||||
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldSendToHost = IsHost;
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ConnectedClientsIds);
|
||||
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
|
||||
while (observerEnumerator.MoveNext())
|
||||
{
|
||||
// Skip over the host
|
||||
if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
continue;
|
||||
}
|
||||
rpcWriteSize = NetworkManager.MessagingSystem.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are a server/host then we just no op and send to ourself
|
||||
@@ -228,6 +248,12 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId)
|
||||
{
|
||||
var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray);
|
||||
return $"Sending ClientRpc to non-observer! {containerNameHoldingId} contains clientId {targetClientId} that is not an observer!";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
||||
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
|
||||
@@ -235,42 +261,42 @@ namespace Unity.Netcode
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
||||
/// is the local player object. If no NetworkObject is assigned it will always return false.
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
||||
public bool IsLocalPlayer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkObject.IsOwner;
|
||||
public bool IsOwner { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected bool IsServer => IsRunning && NetworkManager.IsServer;
|
||||
protected bool IsServer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient => IsRunning && NetworkManager.IsClient;
|
||||
protected bool IsClient { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost => IsRunning && NetworkManager.IsHost;
|
||||
|
||||
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
|
||||
protected bool IsHost { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
||||
public bool IsOwnedByServer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
|
||||
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
|
||||
/// </summary>
|
||||
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false;
|
||||
public bool IsSpawned { get; internal set; }
|
||||
|
||||
internal bool IsBehaviourEditable()
|
||||
{
|
||||
@@ -305,7 +331,8 @@ namespace Unity.Netcode
|
||||
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
|
||||
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
|
||||
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
|
||||
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
|
||||
// We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy)
|
||||
if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -327,12 +354,12 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
||||
public ulong NetworkObjectId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
||||
/// </summary>
|
||||
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
public ushort NetworkBehaviourId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
|
||||
@@ -352,7 +379,47 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the ClientId that owns the NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
||||
public ulong OwnerClientId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates properties with network session related
|
||||
/// dependencies such as a NetworkObject's spawned
|
||||
/// state or NetworkManager's session state.
|
||||
/// </summary>
|
||||
internal void UpdateNetworkProperties()
|
||||
{
|
||||
// Set NetworkObject dependent properties
|
||||
if (NetworkObject != null)
|
||||
{
|
||||
// Set identification related properties
|
||||
NetworkObjectId = NetworkObject.NetworkObjectId;
|
||||
IsLocalPlayer = NetworkObject.IsLocalPlayer;
|
||||
|
||||
// This is "OK" because GetNetworkBehaviourOrderIndex uses the order of
|
||||
// NetworkObject.ChildNetworkBehaviours which is set once when first
|
||||
// accessed.
|
||||
NetworkBehaviourId = NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
|
||||
// Set ownership related properties
|
||||
IsOwnedByServer = NetworkObject.IsOwnedByServer;
|
||||
IsOwner = NetworkObject.IsOwner;
|
||||
OwnerClientId = NetworkObject.OwnerClientId;
|
||||
|
||||
// Set NetworkManager dependent properties
|
||||
if (NetworkManager != null)
|
||||
{
|
||||
IsHost = NetworkManager.IsListening && NetworkManager.IsHost;
|
||||
IsClient = NetworkManager.IsListening && NetworkManager.IsClient;
|
||||
IsServer = NetworkManager.IsListening && NetworkManager.IsServer;
|
||||
}
|
||||
}
|
||||
else // Shouldn't happen, but if so then set the properties to their default value;
|
||||
{
|
||||
OwnerClientId = NetworkObjectId = default;
|
||||
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = default;
|
||||
NetworkBehaviourId = default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
|
||||
@@ -366,24 +433,45 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InternalOnNetworkSpawn()
|
||||
{
|
||||
IsSpawned = true;
|
||||
InitializeVariables();
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkSpawn();
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkDespawn() { }
|
||||
internal void InternalOnNetworkDespawn()
|
||||
{
|
||||
IsSpawned = false;
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkDespawn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the local client gains ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnGainedOwnership() { }
|
||||
|
||||
internal void InternalOnGainedOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
OnGainedOwnership();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when we loose ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnLostOwnership() { }
|
||||
|
||||
internal void InternalOnLostOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
OnLostOwnership();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
|
||||
/// </summary>
|
||||
/// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param>
|
||||
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
|
||||
|
||||
private bool m_VarInit = false;
|
||||
@@ -433,12 +521,10 @@ namespace Unity.Netcode
|
||||
|
||||
m_VarInit = true;
|
||||
|
||||
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
||||
|
||||
var sortedFields = GetFieldInfoForType(GetType());
|
||||
for (int i = 0; i < sortedFields.Length; i++)
|
||||
{
|
||||
Type fieldType = sortedFields[i].FieldType;
|
||||
|
||||
var fieldType = sortedFields[i].FieldType;
|
||||
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
||||
{
|
||||
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
|
||||
@@ -497,9 +583,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
|
||||
}
|
||||
|
||||
MarkVariablesDirty(false);
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong clientId)
|
||||
internal void PreVariableUpdate()
|
||||
{
|
||||
if (!m_VarInit)
|
||||
{
|
||||
@@ -507,67 +595,62 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
NetworkVariableUpdate(clientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong targetClientId)
|
||||
{
|
||||
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
||||
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||
|
||||
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)
|
||||
private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
|
||||
{
|
||||
if (!CouldHaveDirtyNetworkVariables())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
{
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
var networkVariable = NetworkVariableFields[k];
|
||||
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
|
||||
{
|
||||
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer))
|
||||
shouldSend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
TargetClientId = targetClientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
shouldSend = true;
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
else
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
ClientId = clientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
|
||||
}
|
||||
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -587,15 +670,15 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void MarkVariablesDirty()
|
||||
internal void MarkVariablesDirty(bool dirty)
|
||||
{
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
NetworkVariableFields[j].SetDirty(true);
|
||||
NetworkVariableFields[j].SetDirty(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
@@ -604,7 +687,7 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId);
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
|
||||
|
||||
if (canClientRead)
|
||||
{
|
||||
@@ -676,8 +759,21 @@ namespace Unity.Netcode
|
||||
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to.
|
||||
/// NOTE: If you override this, you will want to always invoke this base class version of this
|
||||
/// <see cref="OnDestroy"/> method!!
|
||||
/// </summary>
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned)
|
||||
{
|
||||
// If the associated NetworkObject is still spawned then this
|
||||
// NetworkBehaviour will be removed from the NetworkObject's
|
||||
// ChildNetworkBehaviours list.
|
||||
NetworkObject.OnNetworkBehaviourDestroyed(this);
|
||||
}
|
||||
|
||||
// this seems odd to do here, but in fact especially in tests we can find ourselves
|
||||
// here without having called InitializedVariables, which causes problems if any
|
||||
// of those variables use native containers (e.g. NetworkList) as they won't be
|
||||
@@ -689,6 +785,7 @@ namespace Unity.Netcode
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[i].Dispose();
|
||||
|
||||
@@ -3,14 +3,22 @@ using Unity.Profiling;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// An helper class that helps NetworkManager update NetworkBehaviours and replicate them down to connected clients.
|
||||
/// </summary>
|
||||
public class NetworkBehaviourUpdater
|
||||
{
|
||||
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>();
|
||||
private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
|
||||
#endif
|
||||
|
||||
internal void AddForUpdate(NetworkObject networkObject)
|
||||
{
|
||||
m_DirtyNetworkObjects.Add(networkObject);
|
||||
}
|
||||
|
||||
internal void NetworkBehaviourUpdate(NetworkManager networkManager)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
@@ -20,57 +28,59 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (networkManager.IsServer)
|
||||
{
|
||||
m_Touched.Clear();
|
||||
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
|
||||
foreach (var dirtyObj in m_DirtyNetworkObjects)
|
||||
{
|
||||
var client = networkManager.ConnectedClientsList[i];
|
||||
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList;
|
||||
m_Touched.UnionWith(spawnedObjs);
|
||||
foreach (var sobj in spawnedObjs)
|
||||
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
if (sobj.IsNetworkVisibleTo(client.ClientId))
|
||||
dirtyObj.ChildNetworkBehaviours[k].PreVariableUpdate();
|
||||
}
|
||||
|
||||
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
var client = networkManager.ConnectedClientsList[i];
|
||||
if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
|
||||
{
|
||||
// Sync just the variables for just the objects this client sees
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
|
||||
dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var sobj in m_Touched)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// when client updates the server, it tells it about all its objects
|
||||
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
|
||||
foreach (var sobj in m_DirtyNetworkObjects)
|
||||
{
|
||||
if (sobj.IsOwner)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(networkManager.ServerClientId);
|
||||
sobj.ChildNetworkBehaviours[k].PreVariableUpdate();
|
||||
}
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
|
||||
}
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var dirtyobj in m_DirtyNetworkObjects)
|
||||
{
|
||||
for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
m_DirtyNetworkObjects.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -54,8 +55,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal NetworkManager NetworkManagerOwner;
|
||||
|
||||
private ulong m_NetworkObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique Id of this object that is synced across the network
|
||||
/// </summary>
|
||||
@@ -64,33 +63,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the ClientId of the owner of this NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OwnerClientIdInternal == null)
|
||||
{
|
||||
return NetworkManager != null ? NetworkManager.ServerClientId : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OwnerClientIdInternal.Value;
|
||||
}
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (NetworkManager != null && value == NetworkManager.ServerClientId)
|
||||
{
|
||||
OwnerClientIdInternal = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnerClientIdInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong? OwnerClientIdInternal = null;
|
||||
public ulong OwnerClientId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the object will always be replicated as root on clients and the parent will be ignored.
|
||||
@@ -103,7 +76,7 @@ namespace Unity.Netcode
|
||||
public bool IsPlayerObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// Gets if the object is the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId;
|
||||
|
||||
@@ -179,6 +152,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
private readonly HashSet<ulong> m_EmptyULongHashSet = new HashSet<ulong>();
|
||||
/// <summary>
|
||||
/// Returns Observers enumerator
|
||||
/// </summary>
|
||||
@@ -187,7 +161,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
return m_EmptyULongHashSet.GetEnumerator();
|
||||
}
|
||||
|
||||
return Observers.GetEnumerator();
|
||||
@@ -202,21 +176,78 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
return false;
|
||||
}
|
||||
return Observers.Contains(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the event the scene of origin gets unloaded, we keep
|
||||
/// the most important part to uniquely identify in-scene
|
||||
/// placed NetworkObjects
|
||||
/// </summary>
|
||||
internal int SceneOriginHandle = 0;
|
||||
|
||||
private Scene m_SceneOrigin;
|
||||
/// <summary>
|
||||
/// The scene where the NetworkObject was first instantiated
|
||||
/// Note: Primarily for in-scene placed NetworkObjects
|
||||
/// We need to keep track of the original scene of origin for
|
||||
/// the NetworkObject in order to be able to uniquely identify it
|
||||
/// using the scene of origin's handle.
|
||||
/// </summary>
|
||||
internal Scene SceneOrigin
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_SceneOrigin;
|
||||
}
|
||||
|
||||
return Observers.Contains(clientId);
|
||||
set
|
||||
{
|
||||
// The scene origin should only be set once.
|
||||
// Once set, it should never change.
|
||||
if (SceneOriginHandle == 0 && value.IsValid() && value.isLoaded)
|
||||
{
|
||||
m_SceneOrigin = value;
|
||||
SceneOriginHandle = value.handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to return the correct scene handle
|
||||
/// Note: Do not use this within NetworkSpawnManager.SpawnNetworkObjectLocallyCommon
|
||||
/// </summary>
|
||||
internal int GetSceneOriginHandle()
|
||||
{
|
||||
if (SceneOriginHandle == 0 && IsSpawned && IsSceneObject != false)
|
||||
{
|
||||
throw new Exception($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
|
||||
}
|
||||
return SceneOriginHandle != 0 ? SceneOriginHandle : gameObject.scene.handle;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SetCachedParent(transform.parent);
|
||||
SceneOrigin = gameObject.scene;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a previously hidden <see cref="NetworkObject"/> to a client
|
||||
/// Makes the previously hidden <see cref="NetworkObject"/> "netcode visible" to the targeted client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to show the <see cref="NetworkObject"/> to</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to start sending updates for a previously hidden <see cref="NetworkObject"/> to the targeted client.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client side.<br />
|
||||
/// In-Scene Placed: The instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkShow(ulong)"/><br />
|
||||
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public void NetworkShow(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
@@ -234,21 +265,27 @@ namespace Unity.Netcode
|
||||
throw new VisibilityChangeException("The object is already visible");
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
{
|
||||
SnapshotSpawn(clientId);
|
||||
}
|
||||
|
||||
Observers.Add(clientId);
|
||||
|
||||
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Shows a list of previously hidden <see cref="NetworkObject"/>s to a client
|
||||
/// Makes a list of previously hidden <see cref="NetworkObject"/>s "netcode visible" for the client specified.
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The <see cref="NetworkObject"/>s to show</param>
|
||||
/// <param name="clientId">The client to show the objects to</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to start sending updates for previously hidden <see cref="NetworkObject"/>s to the targeted client.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client's side.<br />
|
||||
/// In-Scene Placed: Already instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client's side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkShow(ulong)"/><br />
|
||||
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="networkObjects">The objects to become "netcode visible" to the targeted client</param>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId)
|
||||
{
|
||||
if (networkObjects == null || networkObjects.Count == 0)
|
||||
@@ -289,9 +326,19 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a object from a specific client
|
||||
/// Hides the <see cref="NetworkObject"/> from the targeted client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to hide the object for</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
|
||||
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
|
||||
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public void NetworkHide(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
@@ -314,30 +361,33 @@ namespace Unity.Netcode
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
|
||||
Observers.Remove(clientId);
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
SnapshotDespawn(clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
|
||||
}
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
DestroyGameObject = !IsSceneObject.Value
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a list of objects from a client
|
||||
/// Hides a list of <see cref="NetworkObject"/>s from the targeted client.
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The objects to hide</param>
|
||||
/// <param name="clientId">The client to hide the objects from</param>
|
||||
/// <remarks>
|
||||
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for the currently visible <see cref="NetworkObject"/>s.<br />
|
||||
/// <br />
|
||||
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
|
||||
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
|
||||
/// <br />
|
||||
/// See Also:<br />
|
||||
/// <see cref="NetworkHide(ulong)"/><br />
|
||||
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
|
||||
/// </remarks>
|
||||
/// <param name="networkObjects">The <see cref="NetworkObject"/>s that will become "netcode invisible" to the targeted client</param>
|
||||
/// <param name="clientId">The targeted client</param>
|
||||
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
|
||||
{
|
||||
if (networkObjects == null || networkObjects.Count == 0)
|
||||
@@ -345,14 +395,14 @@ namespace Unity.Netcode
|
||||
throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided");
|
||||
}
|
||||
|
||||
NetworkManager networkManager = networkObjects[0].NetworkManager;
|
||||
var networkManager = networkObjects[0].NetworkManager;
|
||||
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (clientId == networkManager.ServerClientId)
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
@@ -384,84 +434,24 @@ namespace Unity.Netcode
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned
|
||||
&& (IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
|
||||
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
|
||||
(IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
|
||||
{
|
||||
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
|
||||
}
|
||||
|
||||
if (NetworkManager != null && NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
if (NetworkManager != null && NetworkManager.SpawnManager != null &&
|
||||
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
|
||||
if (this == networkObject)
|
||||
{
|
||||
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SnapshotDespawnCommand GetDespawnCommand()
|
||||
{
|
||||
var command = new SnapshotDespawnCommand();
|
||||
command.NetworkObjectId = NetworkObjectId;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private SnapshotSpawnCommand GetSpawnCommand()
|
||||
{
|
||||
var command = new SnapshotSpawnCommand();
|
||||
command.NetworkObjectId = NetworkObjectId;
|
||||
command.OwnerClientId = OwnerClientId;
|
||||
command.IsPlayerObject = IsPlayerObject;
|
||||
command.IsSceneObject = (IsSceneObject == null) || IsSceneObject.Value;
|
||||
|
||||
ulong? parent = NetworkManager.SpawnManager.GetSpawnParentId(this);
|
||||
if (parent != null)
|
||||
{
|
||||
command.ParentNetworkId = parent.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// write own network id, when no parents. todo: optimize this.
|
||||
command.ParentNetworkId = command.NetworkObjectId;
|
||||
}
|
||||
|
||||
command.GlobalObjectIdHash = HostCheckForGlobalObjectIdHashOverride();
|
||||
// todo: check if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(clientId)) for any clientId
|
||||
command.ObjectPosition = transform.position;
|
||||
command.ObjectRotation = transform.rotation;
|
||||
command.ObjectScale = transform.localScale;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private void SnapshotSpawn()
|
||||
{
|
||||
var command = GetSpawnCommand();
|
||||
NetworkManager.SnapshotSystem.Spawn(command);
|
||||
}
|
||||
|
||||
private void SnapshotSpawn(ulong clientId)
|
||||
{
|
||||
var command = GetSpawnCommand();
|
||||
command.TargetClientIds = new List<ulong>();
|
||||
command.TargetClientIds.Add(clientId);
|
||||
NetworkManager.SnapshotSystem.Spawn(command);
|
||||
}
|
||||
|
||||
internal void SnapshotDespawn()
|
||||
{
|
||||
var command = GetDespawnCommand();
|
||||
NetworkManager.SnapshotSystem.Despawn(command);
|
||||
}
|
||||
|
||||
internal void SnapshotDespawn(ulong clientId)
|
||||
{
|
||||
var command = GetDespawnCommand();
|
||||
command.TargetClientIds = new List<ulong>();
|
||||
command.TargetClientIds.Add(clientId);
|
||||
NetworkManager.SnapshotSystem.Despawn(command);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SpawnInternal(bool destroyWithScene, ulong? ownerClientId, bool playerObject)
|
||||
private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
|
||||
{
|
||||
if (!NetworkManager.IsListening)
|
||||
{
|
||||
@@ -473,14 +463,8 @@ namespace Unity.Netcode
|
||||
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
||||
}
|
||||
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene);
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene);
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
{
|
||||
SnapshotSpawn();
|
||||
}
|
||||
|
||||
ulong ownerId = ownerClientId != null ? ownerClientId.Value : NetworkManager.ServerClientId;
|
||||
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId))
|
||||
@@ -496,7 +480,7 @@ namespace Unity.Netcode
|
||||
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
|
||||
public void Spawn(bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(destroyWithScene, null, false);
|
||||
SpawnInternal(destroyWithScene, NetworkManager.ServerClientId, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -512,8 +496,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Spawns a <see cref="NetworkObject"/> across the network and makes it the player object for the given client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId whos player object this is</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
/// <param name="clientId">The clientId who's player object this is</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
|
||||
public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(destroyWithScene, clientId, true);
|
||||
@@ -547,17 +531,36 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InvokeBehaviourOnLostOwnership()
|
||||
{
|
||||
// Server already handles this earlier, hosts should ignore, all clients should update
|
||||
if (!NetworkManager.IsServer)
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnLostOwnership();
|
||||
ChildNetworkBehaviours[i].InternalOnLostOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourOnGainedOwnership()
|
||||
{
|
||||
// Server already handles this earlier, hosts should ignore and only client owners should update
|
||||
if (!NetworkManager.IsServer && NetworkManager.LocalClientId == OwnerClientId)
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnGainedOwnership();
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnGainedOwnership();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,16 +589,34 @@ namespace Unity.Netcode
|
||||
m_LatestParent = latestParent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent of the NetworkObject transform.
|
||||
/// </summary>
|
||||
/// <param name="parent">The new parent for this NetworkObject transform will be the child of.</param>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(Transform parent, bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent of the NetworkObject transform.
|
||||
/// </summary>
|
||||
/// <param name="parent">The new parent for this NetworkObject transform will be the child of.</param>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(GameObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
return TrySetParent(parent.GetComponent<NetworkObject>(), worldPositionStays);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parent of the NetworkObject transform.
|
||||
/// </summary>
|
||||
/// <param name="parent">The new parent for this NetworkObject transform will be the child of.</param>
|
||||
/// <param name="worldPositionStays">If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before.</param>
|
||||
/// <returns>Whether or not reparenting was successful.</returns>
|
||||
public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
if (!AutoObjectParentSync)
|
||||
@@ -724,7 +745,7 @@ namespace Unity.Netcode
|
||||
// For instance, if we're spawning NetworkObject 5 and its parent is 10, what should happen if we do not have 10 yet?
|
||||
// let's say 10 is on the way to be replicated in a few frames and we could fix that parent-child relationship later.
|
||||
//
|
||||
// If you couldn't find your parent, we put you into OrphanChildren set and everytime we spawn another NetworkObject locally due to replication,
|
||||
// If you couldn't find your parent, we put you into OrphanChildren set and every time we spawn another NetworkObject locally due to replication,
|
||||
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
|
||||
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
|
||||
|
||||
@@ -756,13 +777,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
|
||||
{
|
||||
if (OrphanChildren.Add(this))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkObject)} ({name}) cannot find its parent, added to {nameof(OrphanChildren)} set");
|
||||
}
|
||||
}
|
||||
OrphanChildren.Add(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -793,19 +808,28 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InvokeBehaviourNetworkSpawn()
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
|
||||
ChildNetworkBehaviours[i].OnNetworkSpawn();
|
||||
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkDespawn()
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkDespawn();
|
||||
ChildNetworkBehaviours[i].OnNetworkDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,25 +858,46 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var behavior = ChildNetworkBehaviours[i];
|
||||
behavior.InitializeVariables();
|
||||
behavior.WriteNetworkVariableData(writer, clientId);
|
||||
behavior.WriteNetworkVariableData(writer, targetClientId);
|
||||
}
|
||||
}
|
||||
|
||||
internal void MarkVariablesDirty()
|
||||
internal void MarkVariablesDirty(bool dirty)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var behavior = ChildNetworkBehaviours[i];
|
||||
behavior.MarkVariablesDirty();
|
||||
behavior.MarkVariablesDirty(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
// NGO currently guarantees that the client will receive spawn data for all objects in one network tick.
|
||||
// Children may arrive before their parents; when they do they are stored in OrphanedChildren and then
|
||||
// resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something
|
||||
// has gone wrong if by the end of an update we still have unresolved orphans
|
||||
//
|
||||
|
||||
// if and when we have different systems for where it is expected that orphans survive across ticks,
|
||||
// then this warning will remind us that we need to revamp the system because then we can no longer simply
|
||||
// spawn the orphan without its parent (at least, not when its transform is set to local coords mode)
|
||||
// - because then you'll have children popping at the wrong location not having their parent's global position to root them
|
||||
// - and then they'll pop to the correct location after they get the parent, and that would be not good
|
||||
internal static void VerifyParentingStatus()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
if (OrphanChildren.Count > 0)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkObject)} ({OrphanChildren.Count}) children not resolved to parents by the end of frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
@@ -907,7 +952,7 @@ namespace Unity.Netcode
|
||||
|
||||
internal struct SceneObject
|
||||
{
|
||||
public struct HeaderData
|
||||
public struct HeaderData : INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
@@ -918,7 +963,6 @@ namespace Unity.Netcode
|
||||
public bool IsSceneObject;
|
||||
public bool HasTransform;
|
||||
public bool IsReparented;
|
||||
public bool HasNetworkVariables;
|
||||
}
|
||||
|
||||
public HeaderData Header;
|
||||
@@ -927,7 +971,7 @@ namespace Unity.Netcode
|
||||
public ulong ParentObjectId;
|
||||
|
||||
//If(Metadata.HasTransform)
|
||||
public struct TransformData
|
||||
public struct TransformData : INetworkSerializeByMemcpy
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
@@ -944,16 +988,17 @@ namespace Unity.Netcode
|
||||
public NetworkObject OwnerObject;
|
||||
public ulong TargetClientId;
|
||||
|
||||
public int NetworkSceneHandle;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(
|
||||
sizeof(HeaderData) +
|
||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
||||
(Header.IsReparented
|
||||
? FastBufferWriter.GetWriteSize(IsLatestParentSet) +
|
||||
(IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0)
|
||||
: 0)))
|
||||
var writeSize = sizeof(HeaderData);
|
||||
writeSize += Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||
writeSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!writer.TryBeginWrite(writeSize))
|
||||
{
|
||||
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
@@ -979,10 +1024,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (Header.HasNetworkVariables)
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
|
||||
// sizes for dynamically spawned NetworkObjects.
|
||||
if (Header.IsSceneObject)
|
||||
{
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||
}
|
||||
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
}
|
||||
|
||||
public unsafe void Deserialize(FastBufferReader reader)
|
||||
@@ -992,10 +1044,12 @@ namespace Unity.Netcode
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
reader.ReadValue(out Header);
|
||||
if (!reader.TryBeginRead(
|
||||
(Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0) +
|
||||
(Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0) +
|
||||
(Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) : 0)))
|
||||
var readSize = Header.HasParent ? FastBufferWriter.GetWriteSize(ParentObjectId) : 0;
|
||||
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize(Transform) : 0;
|
||||
readSize += Header.IsReparented ? FastBufferWriter.GetWriteSize(IsLatestParentSet) + (IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0) : 0;
|
||||
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
||||
|
||||
if (!reader.TryBeginRead(readSize))
|
||||
{
|
||||
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
|
||||
}
|
||||
@@ -1019,10 +1073,20 @@ namespace Unity.Netcode
|
||||
LatestParent = latestParent;
|
||||
}
|
||||
}
|
||||
|
||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
||||
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
|
||||
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
|
||||
// Client-side NetworkSceneManagers use this to locate their local instance of the
|
||||
// NetworkObject instance.
|
||||
if (Header.IsSceneObject)
|
||||
{
|
||||
reader.ReadValueSafe(out NetworkSceneHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal SceneObject GetMessageSceneObject(ulong targetClientId, bool includeNetworkVariableData = true)
|
||||
internal SceneObject GetMessageSceneObject(ulong targetClientId)
|
||||
{
|
||||
var obj = new SceneObject
|
||||
{
|
||||
@@ -1033,7 +1097,6 @@ namespace Unity.Netcode
|
||||
OwnerClientId = OwnerClientId,
|
||||
IsSceneObject = IsSceneObject ?? true,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
HasNetworkVariables = includeNetworkVariableData
|
||||
},
|
||||
OwnerObject = this,
|
||||
TargetClientId = targetClientId
|
||||
@@ -1089,6 +1152,7 @@ namespace Unity.Netcode
|
||||
Vector3? position = null;
|
||||
Quaternion? rotation = null;
|
||||
ulong? parentNetworkId = null;
|
||||
int? networkSceneHandle = null;
|
||||
|
||||
if (sceneObject.Header.HasTransform)
|
||||
{
|
||||
@@ -1101,10 +1165,15 @@ namespace Unity.Netcode
|
||||
parentNetworkId = sceneObject.ParentObjectId;
|
||||
}
|
||||
|
||||
if (sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
networkSceneHandle = sceneObject.NetworkSceneHandle;
|
||||
}
|
||||
|
||||
//Attempt to create a local NetworkObject
|
||||
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(
|
||||
sceneObject.Header.IsSceneObject, sceneObject.Header.Hash,
|
||||
sceneObject.Header.OwnerClientId, parentNetworkId, position, rotation, sceneObject.Header.IsReparented);
|
||||
sceneObject.Header.OwnerClientId, parentNetworkId, networkSceneHandle, position, rotation, sceneObject.Header.IsReparented);
|
||||
|
||||
networkObject?.SetNetworkParenting(sceneObject.Header.IsReparented, sceneObject.LatestParent);
|
||||
|
||||
@@ -1150,5 +1219,21 @@ namespace Unity.Netcode
|
||||
|
||||
return GlobalObjectIdHash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkBehaviour from the ChildNetworkBehaviours list when destroyed
|
||||
/// while the NetworkObject is still spawned.
|
||||
/// </summary>
|
||||
internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour)
|
||||
{
|
||||
if (networkBehaviour.IsSpawned && IsSpawned)
|
||||
{
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)");
|
||||
}
|
||||
ChildNetworkBehaviours.Remove(networkBehaviour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,55 @@ using UnityEngine.PlayerLoop;
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the required interface of a network update system being executed by the network update loop.
|
||||
/// Defines the required interface of a network update system being executed by the <see cref="NetworkUpdateLoop"/>.
|
||||
/// </summary>
|
||||
public interface INetworkUpdateSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// The update method that is being executed in the context of related <see cref="NetworkUpdateStage"/>.
|
||||
/// </summary>
|
||||
/// <param name="updateStage">The <see cref="NetworkUpdateStage"/> that is being executed.</param>
|
||||
void NetworkUpdate(NetworkUpdateStage updateStage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines network update stages being executed by the network update loop.
|
||||
/// See for more details on update stages:
|
||||
/// https://docs.unity3d.com/ScriptReference/PlayerLoop.Initialization.html
|
||||
/// </summary>
|
||||
public enum NetworkUpdateStage : byte
|
||||
{
|
||||
Unset = 0, // Default
|
||||
/// <summary>
|
||||
/// Default value
|
||||
/// </summary>
|
||||
Unset = 0,
|
||||
/// <summary>
|
||||
/// Very first initialization update
|
||||
/// </summary>
|
||||
Initialization = 1,
|
||||
/// <summary>
|
||||
/// Invoked before Fixed update
|
||||
/// </summary>
|
||||
EarlyUpdate = 2,
|
||||
/// <summary>
|
||||
/// Fixed Update (i.e. state machine, physics, animations, etc)
|
||||
/// </summary>
|
||||
FixedUpdate = 3,
|
||||
/// <summary>
|
||||
/// Updated before the Monobehaviour.Update for all components is invoked
|
||||
/// </summary>
|
||||
PreUpdate = 4,
|
||||
/// <summary>
|
||||
/// Updated when the Monobehaviour.Update for all components is invoked
|
||||
/// </summary>
|
||||
Update = 5,
|
||||
/// <summary>
|
||||
/// Updated before the Monobehaviour.LateUpdate for all components is invoked
|
||||
/// </summary>
|
||||
PreLateUpdate = 6,
|
||||
/// <summary>
|
||||
/// Updated after the Monobehaviour.LateUpdate for all components is invoked
|
||||
/// </summary>
|
||||
PostLateUpdate = 7
|
||||
}
|
||||
|
||||
@@ -53,6 +83,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in all network update stages.
|
||||
/// </summary>
|
||||
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to register for all network updates</param>
|
||||
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
@@ -64,6 +95,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in a specific network update stage.
|
||||
/// </summary>
|
||||
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to register for all network updates</param>
|
||||
/// <param name="updateStage">The <see cref="NetworkUpdateStage"/> being registered for the <see cref="INetworkUpdateSystem"/> implementation</param>
|
||||
public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
@@ -94,6 +127,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from all network update stages.
|
||||
/// </summary>
|
||||
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to deregister from all network updates</param>
|
||||
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
@@ -105,6 +139,8 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from a specific network update stage.
|
||||
/// </summary>
|
||||
/// <param name="updateSystem">The <see cref="INetworkUpdateSystem"/> implementation to deregister from all network updates</param>
|
||||
/// <param name="updateStage">The <see cref="NetworkUpdateStage"/> to be deregistered from the <see cref="INetworkUpdateSystem"/> implementation</param>
|
||||
public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class ConnectionRtt
|
||||
{
|
||||
private double[] m_RttSendTimes; // times at which packet were sent for RTT computations
|
||||
private int[] m_SendSequence; // tick, or other key, at which packets were sent (to allow matching)
|
||||
private double[] m_MeasuredLatencies; // measured latencies (ring buffer)
|
||||
private int m_LatenciesBegin = 0; // ring buffer begin
|
||||
private int m_LatenciesEnd = 0; // ring buffer end
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip-time data
|
||||
/// </summary>
|
||||
public struct Rtt
|
||||
{
|
||||
public double BestSec; // best RTT
|
||||
public double AverageSec; // average RTT
|
||||
public double WorstSec; // worst RTT
|
||||
public double LastSec; // latest ack'ed RTT
|
||||
public int SampleCount; // number of contributing samples
|
||||
}
|
||||
|
||||
public ConnectionRtt()
|
||||
{
|
||||
m_RttSendTimes = new double[NetworkConfig.RttWindowSize];
|
||||
m_SendSequence = new int[NetworkConfig.RttWindowSize];
|
||||
m_MeasuredLatencies = new double[NetworkConfig.RttWindowSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Round-trip-time computation for this client
|
||||
/// </summary>
|
||||
public Rtt GetRtt()
|
||||
{
|
||||
var ret = new Rtt();
|
||||
var index = m_LatenciesBegin;
|
||||
double total = 0.0;
|
||||
ret.BestSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
ret.WorstSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
|
||||
while (index != m_LatenciesEnd)
|
||||
{
|
||||
total += m_MeasuredLatencies[index];
|
||||
ret.SampleCount++;
|
||||
ret.BestSec = Math.Min(ret.BestSec, m_MeasuredLatencies[index]);
|
||||
ret.WorstSec = Math.Max(ret.WorstSec, m_MeasuredLatencies[index]);
|
||||
index = (index + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
|
||||
if (ret.SampleCount != 0)
|
||||
{
|
||||
ret.AverageSec = total / ret.SampleCount;
|
||||
// the latest RTT is one before m_LatenciesEnd
|
||||
ret.LastSec = m_MeasuredLatencies[(m_LatenciesEnd + (NetworkConfig.RttWindowSize - 1)) % NetworkConfig.RttWindowSize];
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.AverageSec = 0;
|
||||
ret.BestSec = 0;
|
||||
ret.WorstSec = 0;
|
||||
ret.SampleCount = 0;
|
||||
ret.LastSec = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal void NotifySend(int sequence, double timeSec)
|
||||
{
|
||||
m_RttSendTimes[sequence % NetworkConfig.RttWindowSize] = timeSec;
|
||||
m_SendSequence[sequence % NetworkConfig.RttWindowSize] = sequence;
|
||||
}
|
||||
|
||||
internal void NotifyAck(int sequence, double timeSec)
|
||||
{
|
||||
// if the same slot was not used by a later send
|
||||
if (m_SendSequence[sequence % NetworkConfig.RttWindowSize] == sequence)
|
||||
{
|
||||
double latency = timeSec - m_RttSendTimes[sequence % NetworkConfig.RttWindowSize];
|
||||
|
||||
m_MeasuredLatencies[m_LatenciesEnd] = latency;
|
||||
m_LatenciesEnd = (m_LatenciesEnd + 1) % NetworkConfig.RttAverageSamples;
|
||||
|
||||
if (m_LatenciesEnd == m_LatenciesBegin)
|
||||
{
|
||||
m_LatenciesBegin = (m_LatenciesBegin + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,18 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public class InvalidParentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor for <see cref="InvalidParentException"/>
|
||||
/// </summary>
|
||||
public InvalidParentException() { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <param name="message"></param>
|
||||
public InvalidParentException(string message) : base(message) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="innerException"></param>
|
||||
public InvalidParentException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,15 @@ namespace Unity.Netcode
|
||||
public SpawnStateException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a specified network channel is invalid
|
||||
/// </summary>
|
||||
public class InvalidChannelException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs an InvalidChannelException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">the message</param>
|
||||
public InvalidChannelException(string message) : base(message) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,24 @@ namespace Unity.Netcode
|
||||
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
|
||||
|
||||
// internal logging
|
||||
internal static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
|
||||
internal static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
|
||||
internal static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Locally logs a info log with Netcode prefixing.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Locally logs a warning log with Netcode prefixing.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Locally logs a error log with Netcode prefixing.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Logs an info log locally and on the server if possible.
|
||||
@@ -62,9 +77,9 @@ namespace Unity.Netcode
|
||||
LogType = logType,
|
||||
Message = message
|
||||
};
|
||||
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.Singleton.ServerClientId);
|
||||
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId);
|
||||
|
||||
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size);
|
||||
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Header placed at the start of each message batch
|
||||
/// </summary>
|
||||
internal struct BatchHeader
|
||||
internal struct BatchHeader : INetworkSerializeByMemcpy
|
||||
{
|
||||
/// <summary>
|
||||
/// Total number of messages in the batch.
|
||||
|
||||
@@ -193,6 +193,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Sends a named message to all clients
|
||||
/// </summary>
|
||||
/// <param name="messageName">The message name to send</param>
|
||||
/// <param name="messageStream">The message stream containing the data</param>
|
||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||
public void SendNamedMessageToAll(string messageName, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||
@@ -238,7 +239,7 @@ namespace Unity.Netcode
|
||||
/// Sends the named message
|
||||
/// </summary>
|
||||
/// <param name="messageName">The message name to send</param>
|
||||
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
|
||||
/// <param name="clientIds">The clients to send to</param>
|
||||
/// <param name="messageStream">The message stream containing the data</param>
|
||||
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
|
||||
public void SendNamedMessage(string messageName, IReadOnlyList<ulong> clientIds, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
|
||||
|
||||
149
Runtime/Messaging/DeferredMessageManager.cs
Normal file
149
Runtime/Messaging/DeferredMessageManager.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Time = UnityEngine.Time;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class DeferredMessageManager : IDeferredMessageManager
|
||||
{
|
||||
protected struct TriggerData
|
||||
{
|
||||
public FastBufferReader Reader;
|
||||
public MessageHeader Header;
|
||||
public ulong SenderId;
|
||||
public float Timestamp;
|
||||
public int SerializedHeaderSize;
|
||||
}
|
||||
protected struct TriggerInfo
|
||||
{
|
||||
public float Expiry;
|
||||
public NativeList<TriggerData> TriggerData;
|
||||
}
|
||||
|
||||
protected readonly Dictionary<IDeferredMessageManager.TriggerType, Dictionary<ulong, TriggerInfo>> m_Triggers = new Dictionary<IDeferredMessageManager.TriggerType, Dictionary<ulong, TriggerInfo>>();
|
||||
|
||||
private readonly NetworkManager m_NetworkManager;
|
||||
|
||||
internal DeferredMessageManager(NetworkManager networkManager)
|
||||
{
|
||||
m_NetworkManager = networkManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||
///
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
public virtual unsafe void DeferMessage(IDeferredMessageManager.TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!m_Triggers.TryGetValue(trigger, out var triggers))
|
||||
{
|
||||
triggers = new Dictionary<ulong, TriggerInfo>();
|
||||
m_Triggers[trigger] = triggers;
|
||||
}
|
||||
|
||||
if (!triggers.TryGetValue(key, out var triggerInfo))
|
||||
{
|
||||
triggerInfo = new TriggerInfo
|
||||
{
|
||||
Expiry = Time.realtimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
|
||||
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
||||
};
|
||||
triggers[key] = triggerInfo;
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Add(new TriggerData
|
||||
{
|
||||
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
||||
Header = context.Header,
|
||||
Timestamp = context.Timestamp,
|
||||
SenderId = context.SenderId,
|
||||
SerializedHeaderSize = context.SerializedHeaderSize
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
public virtual unsafe void CleanupStaleTriggers()
|
||||
{
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
ulong* staleKeys = stackalloc ulong[kvp.Value.Count];
|
||||
int index = 0;
|
||||
foreach (var kvp2 in kvp.Value)
|
||||
{
|
||||
if (kvp2.Value.Expiry < Time.realtimeSinceStartup)
|
||||
{
|
||||
staleKeys[index++] = kvp2.Key;
|
||||
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < index; ++i)
|
||||
{
|
||||
kvp.Value.Remove(staleKeys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void PurgeTrigger(IDeferredMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Deferred messages were received for a trigger of type {triggerType} with key {key}, but that trigger was not received within within {m_NetworkManager.NetworkConfig.SpawnTimeout} second(s).");
|
||||
}
|
||||
|
||||
foreach (var data in triggerInfo.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Dispose();
|
||||
}
|
||||
|
||||
public virtual void ProcessTriggers(IDeferredMessageManager.TriggerType trigger, ulong key)
|
||||
{
|
||||
if (m_Triggers.TryGetValue(trigger, out var triggers))
|
||||
{
|
||||
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
|
||||
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
||||
if (triggers.TryGetValue(key, out var triggerInfo))
|
||||
{
|
||||
foreach (var deferredMessage in triggerInfo.TriggerData)
|
||||
{
|
||||
// Reader will be disposed within HandleMessage
|
||||
m_NetworkManager.MessagingSystem.HandleMessage(deferredMessage.Header, deferredMessage.Reader, deferredMessage.SenderId, deferredMessage.Timestamp, deferredMessage.SerializedHeaderSize);
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Dispose();
|
||||
triggers.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
public virtual void CleanupAllTriggers()
|
||||
{
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
foreach (var kvp2 in kvp.Value)
|
||||
{
|
||||
foreach (var data in kvp2.Value.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
kvp2.Value.TriggerData.Dispose();
|
||||
}
|
||||
}
|
||||
m_Triggers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Messaging/DeferredMessageManager.cs.meta
Normal file
3
Runtime/Messaging/DeferredMessageManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac7f57f7d16a46e2aba65558e873727f
|
||||
timeCreated: 1649799187
|
||||
35
Runtime/Messaging/IDeferredMessageManager.cs
Normal file
35
Runtime/Messaging/IDeferredMessageManager.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal interface IDeferredMessageManager
|
||||
{
|
||||
internal enum TriggerType
|
||||
{
|
||||
OnSpawn,
|
||||
OnAddPrefab,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||
///
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
void DeferMessage(TriggerType trigger, ulong key, FastBufferReader reader, ref NetworkContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
void CleanupStaleTriggers();
|
||||
|
||||
void ProcessTriggers(TriggerType trigger, ulong key);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
void CleanupAllTriggers();
|
||||
}
|
||||
}
|
||||
3
Runtime/Messaging/IDeferredMessageManager.cs.meta
Normal file
3
Runtime/Messaging/IDeferredMessageManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fb73a029c314763a04ebb015a07664d
|
||||
timeCreated: 1649966331
|
||||
@@ -91,8 +91,10 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="senderId">The source clientId</param>
|
||||
/// <param name="messageType">The type of the message</param>
|
||||
/// <param name="messageContent">The FastBufferReader containing the message</param>
|
||||
/// <param name="context">The NetworkContext the message is being processed in</param>
|
||||
/// <returns></returns>
|
||||
bool OnVerifyCanReceive(ulong senderId, Type messageType);
|
||||
bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Called after a message is serialized, but before it's handled.
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// This is the header data that's serialized to the network when sending an <see cref="INetworkMessage"/>
|
||||
/// </summary>
|
||||
internal struct MessageHeader
|
||||
internal struct MessageHeader : INetworkSerializeByMemcpy
|
||||
{
|
||||
/// <summary>
|
||||
/// The byte representation of the message type. This is automatically assigned to each message
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct ChangeOwnershipMessage : INetworkMessage
|
||||
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
@@ -20,7 +20,7 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out this);
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,24 +29,33 @@ namespace Unity.Netcode
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
|
||||
if (networkObject.OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are current owner.
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
var originalOwner = networkObject.OwnerClientId;
|
||||
|
||||
networkObject.OwnerClientId = OwnerClientId;
|
||||
|
||||
// We are current owner.
|
||||
if (originalOwner == networkManager.LocalClientId)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
// We are new owner.
|
||||
if (OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are new owner.
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
|
||||
// For all other clients that are neither the former or current owner, update the behaviours' properties
|
||||
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
|
||||
{
|
||||
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties();
|
||||
}
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,16 +101,24 @@ namespace Unity.Netcode
|
||||
{
|
||||
// Note: Delegate creation allocates.
|
||||
// Note: ToArray() also allocates. :(
|
||||
networkManager.InvokeConnectionApproval(ConnectionData, senderId,
|
||||
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
|
||||
var response = new NetworkManager.ConnectionApprovalResponse();
|
||||
networkManager.ClientsToApprove[senderId] = response;
|
||||
|
||||
networkManager.ConnectionApprovalCallback(
|
||||
new NetworkManager.ConnectionApprovalRequest
|
||||
{
|
||||
var localCreatePlayerObject = createPlayerObject;
|
||||
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation);
|
||||
});
|
||||
Payload = ConnectionData,
|
||||
ClientNetworkId = senderId
|
||||
}, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.HandleApproval(senderId, networkManager.NetworkConfig.PlayerPrefab != null, null, true, null, null);
|
||||
var response = new NetworkManager.ConnectionApprovalResponse
|
||||
{
|
||||
Approved = true,
|
||||
CreatePlayerObject = networkManager.NetworkConfig.PlayerPrefab != null
|
||||
};
|
||||
networkManager.HandleConnectionApproval(senderId, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
ObjectInfo.Deserialize(reader);
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct DestroyObjectMessage : INetworkMessage
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public bool DestroyGameObject;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
@@ -16,7 +17,14 @@ namespace Unity.Netcode
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out this);
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -30,7 +38,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
|
||||
networkManager.SpawnManager.OnDespawnObject(networkObject, DestroyGameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Unity.Netcode
|
||||
public ushort NetworkBehaviourIndex;
|
||||
|
||||
public HashSet<int> DeliveryMappedNetworkVariableIndex;
|
||||
public ulong ClientId;
|
||||
public ulong TargetClientId;
|
||||
public NetworkBehaviour NetworkBehaviour;
|
||||
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
@@ -31,9 +31,9 @@ namespace Unity.Netcode
|
||||
writer.WriteValue(NetworkObjectId);
|
||||
writer.WriteValue(NetworkBehaviourIndex);
|
||||
|
||||
for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++)
|
||||
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(k))
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
|
||||
{
|
||||
// This var does not belong to the currently iterating delivery group.
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
@@ -48,15 +48,25 @@ namespace Unity.Netcode
|
||||
continue;
|
||||
}
|
||||
|
||||
// if I'm dirty AND a client, write (server always has all permissions)
|
||||
// if I'm dirty AND the server AND the client can read me, send.
|
||||
bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer);
|
||||
var startingSize = writer.Length;
|
||||
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
|
||||
var shouldWrite = networkVariable.IsDirty() &&
|
||||
networkVariable.CanClientRead(TargetClientId) &&
|
||||
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
|
||||
|
||||
// Prevent the server from writing to the client that owns a given NetworkVariable
|
||||
// Allowing the write would send an old value to the client and cause jitter
|
||||
if (networkVariable.WritePerm == NetworkVariableWritePermission.Owner &&
|
||||
networkVariable.OwnerClientId() == TargetClientId)
|
||||
{
|
||||
shouldWrite = false;
|
||||
}
|
||||
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
BytePacker.WriteValueBitPacked(writer, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -68,34 +78,34 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue);
|
||||
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter);
|
||||
var tempWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
|
||||
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
|
||||
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<ushort>() + tmpWriter.Length))
|
||||
if (!writer.TryBeginWrite(tempWriter.Length))
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
|
||||
writer.WriteValue((ushort)tmpWriter.Length);
|
||||
tmpWriter.CopyTo(writer);
|
||||
tempWriter.CopyTo(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(writer);
|
||||
networkVariable.WriteDelta(writer);
|
||||
}
|
||||
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k))
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(k);
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
|
||||
}
|
||||
|
||||
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
||||
ClientId,
|
||||
TargetClientId,
|
||||
NetworkBehaviour.NetworkObject,
|
||||
NetworkBehaviour.NetworkVariableFields[k].Name,
|
||||
networkVariable.Name,
|
||||
NetworkBehaviour.__getTypeName(),
|
||||
writer.Length);
|
||||
writer.Length - startingSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,9 +131,9 @@ namespace Unity.Netcode
|
||||
|
||||
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
|
||||
{
|
||||
NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||
|
||||
if (behaviour == null)
|
||||
if (networkBehaviour == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -132,13 +142,12 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
|
||||
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
int varSize = 0;
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
m_ReceivedNetworkVariableData.ReadValueSafe(out varSize);
|
||||
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
|
||||
|
||||
if (varSize == 0)
|
||||
{
|
||||
@@ -154,15 +163,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (networkManager.IsServer)
|
||||
var networkVariable = networkBehaviour.NetworkVariableFields[i];
|
||||
|
||||
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
|
||||
{
|
||||
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
|
||||
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||
}
|
||||
|
||||
m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize);
|
||||
@@ -176,23 +187,23 @@ namespace Unity.Netcode
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
|
||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
int readStartPos = m_ReceivedNetworkVariableData.Position;
|
||||
|
||||
behaviour.NetworkVariableFields[i].ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
||||
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
||||
|
||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
|
||||
context.SenderId,
|
||||
networkObject,
|
||||
behaviour.NetworkVariableFields[i].Name,
|
||||
behaviour.__getTypeName(),
|
||||
networkVariable.Name,
|
||||
networkBehaviour.__getTypeName(),
|
||||
context.MessageSize);
|
||||
|
||||
|
||||
@@ -202,7 +213,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
}
|
||||
|
||||
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
|
||||
@@ -211,7 +222,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogWarning($"Var delta read too little. {readStartPos + varSize - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
}
|
||||
|
||||
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
|
||||
@@ -222,7 +233,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
50
Runtime/Messaging/Messages/OrderingMessage.cs
Normal file
50
Runtime/Messaging/Messages/OrderingMessage.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides
|
||||
/// have the same message types in the same positions in
|
||||
/// - MessagingSystem.m_MessageHandlers
|
||||
/// - MessagingSystem.m_ReverseTypeMap
|
||||
/// even if one side has extra messages (compilation, version, patch, or platform differences, etc...)
|
||||
///
|
||||
/// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning
|
||||
/// of the mapping, to guarantee they can be exchanged before the two sides share their ordering
|
||||
/// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time
|
||||
/// </summary>
|
||||
internal struct OrderingMessage : INetworkMessage
|
||||
{
|
||||
public int Order;
|
||||
public uint Hash;
|
||||
|
||||
public void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
|
||||
}
|
||||
|
||||
writer.WriteValue(Order);
|
||||
writer.WriteValue(Hash);
|
||||
}
|
||||
|
||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
|
||||
{
|
||||
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
|
||||
}
|
||||
|
||||
reader.ReadValue(out Order);
|
||||
reader.ReadValue(out Hash);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
|
||||
guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -48,7 +48,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Unity.Netcode
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(metadata.NetworkObjectId, reader, ref context);
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal struct RpcMetadata
|
||||
internal struct RpcMetadata : INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ushort NetworkBehaviourId;
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct SnapshotDataMessage : INetworkMessage
|
||||
{
|
||||
public int CurrentTick;
|
||||
public ushort Sequence;
|
||||
|
||||
public ushort Range;
|
||||
|
||||
public byte[] SendMainBuffer;
|
||||
public NativeArray<byte> ReceiveMainBuffer;
|
||||
|
||||
public struct AckData
|
||||
{
|
||||
public ushort LastReceivedSequence;
|
||||
public ushort ReceivedSequenceMask;
|
||||
}
|
||||
|
||||
public AckData Ack;
|
||||
|
||||
public struct EntryData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ushort BehaviourIndex;
|
||||
public ushort VariableIndex;
|
||||
public int TickWritten;
|
||||
public ushort Position;
|
||||
public ushort Length;
|
||||
}
|
||||
|
||||
public NativeList<EntryData> Entries;
|
||||
|
||||
public struct SpawnData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public uint Hash;
|
||||
public bool IsSceneObject;
|
||||
|
||||
public bool IsPlayerObject;
|
||||
public ulong OwnerClientId;
|
||||
public ulong ParentNetworkId;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
|
||||
public int TickWritten;
|
||||
}
|
||||
|
||||
public NativeList<SpawnData> Spawns;
|
||||
|
||||
public struct DespawnData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public int TickWritten;
|
||||
}
|
||||
|
||||
public NativeList<DespawnData> Despawns;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(
|
||||
FastBufferWriter.GetWriteSize(CurrentTick) +
|
||||
FastBufferWriter.GetWriteSize(Sequence) +
|
||||
FastBufferWriter.GetWriteSize(Range) + Range +
|
||||
FastBufferWriter.GetWriteSize(Ack) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Entries.Length * sizeof(EntryData) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Spawns.Length * sizeof(SpawnData) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Despawns.Length * sizeof(DespawnData)
|
||||
))
|
||||
{
|
||||
throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}");
|
||||
}
|
||||
writer.WriteValue(CurrentTick);
|
||||
writer.WriteValue(Sequence);
|
||||
|
||||
writer.WriteValue(Range);
|
||||
writer.WriteBytes(SendMainBuffer, Range);
|
||||
writer.WriteValue(Ack);
|
||||
|
||||
writer.WriteValue((ushort)Entries.Length);
|
||||
writer.WriteBytes((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
|
||||
|
||||
writer.WriteValue((ushort)Spawns.Length);
|
||||
writer.WriteBytes((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
|
||||
|
||||
writer.WriteValue((ushort)Despawns.Length);
|
||||
writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
|
||||
}
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!reader.TryBeginRead(
|
||||
FastBufferWriter.GetWriteSize(CurrentTick) +
|
||||
FastBufferWriter.GetWriteSize(Sequence) +
|
||||
FastBufferWriter.GetWriteSize(Range)
|
||||
))
|
||||
{
|
||||
throw new OverflowException($"Not enough space to deserialize {nameof(SnapshotDataMessage)}");
|
||||
}
|
||||
reader.ReadValue(out CurrentTick);
|
||||
reader.ReadValue(out Sequence);
|
||||
|
||||
reader.ReadValue(out Range);
|
||||
ReceiveMainBuffer = new NativeArray<byte>(Range, Allocator.Temp);
|
||||
reader.ReadBytesSafe((byte*)ReceiveMainBuffer.GetUnsafePtr(), Range);
|
||||
reader.ReadValueSafe(out Ack);
|
||||
|
||||
reader.ReadValueSafe(out ushort length);
|
||||
Entries = new NativeList<EntryData>(length, Allocator.Temp) { Length = length };
|
||||
reader.ReadBytesSafe((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
|
||||
|
||||
reader.ReadValueSafe(out length);
|
||||
Spawns = new NativeList<SpawnData>(length, Allocator.Temp) { Length = length };
|
||||
reader.ReadBytesSafe((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
|
||||
|
||||
reader.ReadValueSafe(out length);
|
||||
Despawns = new NativeList<DespawnData>(length, Allocator.Temp) { Length = length };
|
||||
reader.ReadBytesSafe((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
using (ReceiveMainBuffer)
|
||||
using (Entries)
|
||||
using (Spawns)
|
||||
using (Despawns)
|
||||
{
|
||||
var systemOwner = context.SystemOwner;
|
||||
var senderId = context.SenderId;
|
||||
if (systemOwner is NetworkManager networkManager)
|
||||
{
|
||||
// todo: temporary hack around bug
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
senderId = networkManager.ServerClientId;
|
||||
}
|
||||
|
||||
var snapshotSystem = networkManager.SnapshotSystem;
|
||||
snapshotSystem.HandleSnapshot(senderId, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
|
||||
var snapshotSystem = ownerData.Item1;
|
||||
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cf75026c2ab86646aac16b39d7259ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct TimeSyncMessage : INetworkMessage
|
||||
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public int Tick;
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class HandlerNotRegisteredException : SystemException
|
||||
{
|
||||
public HandlerNotRegisteredException() { }
|
||||
public HandlerNotRegisteredException(string issue) : base(issue) { }
|
||||
}
|
||||
|
||||
internal class InvalidMessageStructureException : SystemException
|
||||
{
|
||||
@@ -44,8 +49,9 @@ namespace Unity.Netcode
|
||||
|
||||
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
|
||||
|
||||
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
|
||||
private Type[] m_ReverseTypeMap = new Type[255];
|
||||
// These array will grow as we need more message handlers. 4 is just a starting size.
|
||||
private MessageHandler[] m_MessageHandlers = new MessageHandler[4];
|
||||
private Type[] m_ReverseTypeMap = new Type[4];
|
||||
|
||||
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
|
||||
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
|
||||
@@ -59,6 +65,7 @@ namespace Unity.Netcode
|
||||
|
||||
internal Type[] MessageTypes => m_ReverseTypeMap;
|
||||
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
|
||||
|
||||
internal uint MessageHandlerCount => m_HighMessageType;
|
||||
|
||||
internal uint GetMessageType(Type t)
|
||||
@@ -67,7 +74,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
|
||||
|
||||
internal struct MessageWithHandler
|
||||
{
|
||||
@@ -75,6 +82,35 @@ namespace Unity.Netcode
|
||||
public MessageHandler Handler;
|
||||
}
|
||||
|
||||
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
|
||||
{
|
||||
var prioritizedTypes = new List<MessageWithHandler>();
|
||||
|
||||
// first pass puts the priority message in the first indices
|
||||
// Those are the messages that must be delivered in order to allow re-ordering the others later
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" ||
|
||||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
|
||||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in allowedTypes)
|
||||
{
|
||||
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
|
||||
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
|
||||
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
|
||||
{
|
||||
prioritizedTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
return prioritizedTypes;
|
||||
}
|
||||
|
||||
public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
|
||||
{
|
||||
try
|
||||
@@ -89,6 +125,7 @@ namespace Unity.Netcode
|
||||
var allowedTypes = provider.GetMessages();
|
||||
|
||||
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
|
||||
allowedTypes = PrioritizeMessageOrder(allowedTypes);
|
||||
foreach (var type in allowedTypes)
|
||||
{
|
||||
RegisterMessageType(type);
|
||||
@@ -136,8 +173,20 @@ namespace Unity.Netcode
|
||||
m_Hooks.Add(hooks);
|
||||
}
|
||||
|
||||
public void Unhook(INetworkHooks hooks)
|
||||
{
|
||||
m_Hooks.Remove(hooks);
|
||||
}
|
||||
|
||||
private void RegisterMessageType(MessageWithHandler messageWithHandler)
|
||||
{
|
||||
// if we are out of space, perform amortized linear growth
|
||||
if (m_HighMessageType == m_MessageHandlers.Length)
|
||||
{
|
||||
Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length);
|
||||
Array.Resize(ref m_ReverseTypeMap, 2 * m_ReverseTypeMap.Length);
|
||||
}
|
||||
|
||||
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
|
||||
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
|
||||
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
|
||||
@@ -208,11 +257,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanReceive(ulong clientId, Type messageType)
|
||||
private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
{
|
||||
if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType))
|
||||
if (!m_Hooks[hookIdx].OnVerifyCanReceive(clientId, messageType, messageContent, ref context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -221,6 +270,70 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
|
||||
// This allows the server to tell the client which id it is using for which message and make sure the right
|
||||
// message is used when deserializing.
|
||||
internal void ReorderMessage(int desiredOrder, uint targetHash)
|
||||
{
|
||||
if (desiredOrder < 0)
|
||||
{
|
||||
throw new ArgumentException("ReorderMessage desiredOrder must be positive");
|
||||
}
|
||||
|
||||
if (desiredOrder < m_ReverseTypeMap.Length &&
|
||||
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash)
|
||||
{
|
||||
// matching positions and hashes. All good.
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"Unexpected hash for {desiredOrder}");
|
||||
|
||||
// Since the message at `desiredOrder` is not the expected one,
|
||||
// insert an empty placeholder and move the messages down
|
||||
var typesAsList = new List<Type>(m_ReverseTypeMap);
|
||||
|
||||
typesAsList.Insert(desiredOrder, null);
|
||||
var handlersAsList = new List<MessageHandler>(m_MessageHandlers);
|
||||
handlersAsList.Insert(desiredOrder, null);
|
||||
|
||||
// we added a dummy message, bump the end up
|
||||
m_HighMessageType++;
|
||||
|
||||
// Here, we rely on the server telling us about all messages, in order.
|
||||
// So, we know the handlers before desiredOrder are correct.
|
||||
// We start at desiredOrder to not shift them when we insert.
|
||||
int position = desiredOrder;
|
||||
bool found = false;
|
||||
while (position < typesAsList.Count)
|
||||
{
|
||||
if (typesAsList[position] != null &&
|
||||
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
// Copy the handler and type to the right index
|
||||
|
||||
typesAsList[desiredOrder] = typesAsList[position];
|
||||
handlersAsList[desiredOrder] = handlersAsList[position];
|
||||
typesAsList.RemoveAt(position);
|
||||
handlersAsList.RemoveAt(position);
|
||||
|
||||
// we removed a copy after moving a message, reduce the high message index
|
||||
m_HighMessageType--;
|
||||
}
|
||||
|
||||
m_ReverseTypeMap = typesAsList.ToArray();
|
||||
m_MessageHandlers = handlersAsList.ToArray();
|
||||
}
|
||||
|
||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
|
||||
{
|
||||
if (header.MessageType >= m_HighMessageType)
|
||||
@@ -240,7 +353,7 @@ namespace Unity.Netcode
|
||||
};
|
||||
|
||||
var type = m_ReverseTypeMap[header.MessageType];
|
||||
if (!CanReceive(senderId, type))
|
||||
if (!CanReceive(senderId, type, reader, ref context))
|
||||
{
|
||||
reader.Dispose();
|
||||
return;
|
||||
@@ -254,18 +367,29 @@ namespace Unity.Netcode
|
||||
var handler = m_MessageHandlers[header.MessageType];
|
||||
using (reader)
|
||||
{
|
||||
// No user-land message handler exceptions should escape the receive loop.
|
||||
// If an exception is throw, the message is ignored.
|
||||
// Example use case: A bad message is received that can't be deserialized and throws
|
||||
// an OverflowException because it specifies a length greater than the number of bytes in it
|
||||
// for some dynamic-length value.
|
||||
try
|
||||
// This will also log an exception is if the server knows about a message type the client doesn't know
|
||||
// about. In this case the handler will be null. It is still an issue the user must deal with: If the
|
||||
// two connecting builds know about different messages, the server should not send a message to a client
|
||||
// that doesn't know about it
|
||||
if (handler == null)
|
||||
{
|
||||
handler.Invoke(reader, ref context, this);
|
||||
Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString()));
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
Debug.LogException(e);
|
||||
// No user-land message handler exceptions should escape the receive loop.
|
||||
// If an exception is throw, the message is ignored.
|
||||
// Example use case: A bad message is received that can't be deserialized and throws
|
||||
// an OverflowException because it specifies a length greater than the number of bytes in it
|
||||
// for some dynamic-length value.
|
||||
try
|
||||
{
|
||||
handler.Invoke(reader, ref context, this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||
|
||||
@@ -3,21 +3,52 @@ using Unity.Collections;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-Side RPC
|
||||
/// Place holder. <see cref="ServerRpcParams"/>
|
||||
/// Note: Clients always send to one destination when sending RPCs to the server
|
||||
/// so this structure is a place holder
|
||||
/// </summary>
|
||||
public struct ServerRpcSendParams
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The receive parameters for server-side remote procedure calls
|
||||
/// </summary>
|
||||
public struct ServerRpcReceiveParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-Side RPC
|
||||
/// The client identifier of the sender
|
||||
/// </summary>
|
||||
public ulong SenderClientId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-Side RPC
|
||||
/// Can be used with any sever-side remote procedure call
|
||||
/// Note: typically this is use primarily for the <see cref="ServerRpcReceiveParams"/>
|
||||
/// </summary>
|
||||
public struct ServerRpcParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The server RPC send parameters (currently a place holder)
|
||||
/// </summary>
|
||||
public ServerRpcSendParams Send;
|
||||
|
||||
/// <summary>
|
||||
/// The client RPC receive parameters provides you with the sender's identifier
|
||||
/// </summary>
|
||||
public ServerRpcReceiveParams Receive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client-Side RPC
|
||||
/// The send parameters, when sending client RPCs, provides you wil the ability to
|
||||
/// target specific clients as a managed or unmanaged list:
|
||||
/// <see cref="TargetClientIds"/> and <see cref="TargetClientIdsNativeArray"/>
|
||||
/// </summary>
|
||||
public struct ClientRpcSendParams
|
||||
{
|
||||
/// <summary>
|
||||
@@ -34,13 +65,32 @@ namespace Unity.Netcode
|
||||
public NativeArray<ulong>? TargetClientIdsNativeArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client-Side RPC
|
||||
/// Place holder. <see cref="ServerRpcParams"/>
|
||||
/// Note: Server will always be the sender, so this structure is a place holder
|
||||
/// </summary>
|
||||
public struct ClientRpcReceiveParams
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client-Side RPC
|
||||
/// Can be used with any client-side remote procedure call
|
||||
/// Note: Typically this is used primarily for sending to a specific list
|
||||
/// of clients as opposed to the default (all).
|
||||
/// <see cref="ClientRpcSendParams"/>
|
||||
/// </summary>
|
||||
public struct ClientRpcParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The client RPC send parameters provides you with the ability to send to a specific list of clients
|
||||
/// </summary>
|
||||
public ClientRpcSendParams Send;
|
||||
|
||||
/// <summary>
|
||||
/// The client RPC receive parameters (currently a place holder)
|
||||
/// </summary>
|
||||
public ClientRpcReceiveParams Receive;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,13 @@ namespace Unity.Netcode
|
||||
|
||||
void TrackPacketReceived(uint packetCount);
|
||||
|
||||
void TrackRttToServer(int rtt);
|
||||
void UpdateRttToServer(int rtt);
|
||||
|
||||
void UpdateNetworkObjectsCount(int count);
|
||||
|
||||
void UpdateConnectionsCount(int count);
|
||||
|
||||
void UpdatePacketLoss(float packetLoss);
|
||||
|
||||
void DispatchFrame();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Unity.Netcode
|
||||
private readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id);
|
||||
private readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id);
|
||||
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
@@ -79,6 +79,15 @@ namespace Unity.Netcode
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_NetworkObjectsGauge = new Gauge(NetworkMetricTypes.NetworkObjects.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_ConnectionsGauge = new Gauge(NetworkMetricTypes.ConnectedClients.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_PacketLossGauge = new Gauge(NetworkMetricTypes.PacketLoss.Id);
|
||||
#endif
|
||||
|
||||
private ulong m_NumberOfMetricsThisFrame;
|
||||
@@ -97,9 +106,12 @@ namespace Unity.Netcode
|
||||
.WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent)
|
||||
.WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent)
|
||||
.WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent)
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
.WithCounters(m_PacketSentCounter, m_PacketReceivedCounter)
|
||||
.WithGauges(m_RttToServerGauge)
|
||||
.WithGauges(m_NetworkObjectsGauge)
|
||||
.WithGauges(m_ConnectionsGauge)
|
||||
.WithGauges(m_PacketLossGauge)
|
||||
#endif
|
||||
.Build();
|
||||
|
||||
@@ -428,7 +440,7 @@ namespace Unity.Netcode
|
||||
|
||||
public void TrackPacketSent(uint packetCount)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
@@ -441,7 +453,7 @@ namespace Unity.Netcode
|
||||
|
||||
public void TrackPacketReceived(uint packetCount)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
@@ -452,15 +464,51 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
public void TrackRttToServer(int rtt)
|
||||
public void UpdateRttToServer(int rttMilliseconds)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var rttSeconds = rttMilliseconds * 1e-3;
|
||||
m_RttToServerGauge.Set(rttSeconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateNetworkObjectsCount(int count)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_RttToServerGauge.Set(rtt);
|
||||
m_NetworkObjectsGauge.Set(count);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateConnectionsCount(int count)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_ConnectionsGauge.Set(count);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdatePacketLoss(float packetLoss)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_PacketLossGauge.Set(packetLoss);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,19 @@ namespace Unity.Netcode
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackRttToServer(int rtt)
|
||||
public void UpdateRttToServer(int rtt)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateNetworkObjectsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateConnectionsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdatePacketLoss(float packetLoss)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Unity.Netcode
|
||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<T> m_ListAtLastReset = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
|
||||
|
||||
/// <summary>
|
||||
@@ -25,16 +26,18 @@ namespace Unity.Netcode
|
||||
public event OnListChangedDelegate OnListChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and settings
|
||||
/// Constructor method for <see cref="NetworkList"/>
|
||||
/// </summary>
|
||||
public NetworkList() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission to use for the NetworkList</param>
|
||||
/// <param name="values">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(NetworkVariableReadPermission readPerm, IEnumerable<T> values) : base(readPerm)
|
||||
/// <inheritdoc/>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="readPerm"></param>
|
||||
/// <param name="writePerm"></param>
|
||||
public NetworkList(IEnumerable<T> values = default,
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
@@ -42,24 +45,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="values">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(IEnumerable<T> values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
m_List.Add(value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ResetDirty()
|
||||
{
|
||||
base.ResetDirty();
|
||||
m_DirtyEvents.Clear();
|
||||
if (m_DirtyEvents.Length > 0)
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
m_ListAtLastReset.CopyFrom(m_List);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -69,6 +63,11 @@ namespace Unity.Netcode
|
||||
return base.IsDirty() || m_DirtyEvents.Length > 0;
|
||||
}
|
||||
|
||||
internal void MarkNetworkObjectDirty()
|
||||
{
|
||||
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteDelta(FastBufferWriter writer)
|
||||
{
|
||||
@@ -85,34 +84,35 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe((ushort)m_DirtyEvents.Length);
|
||||
for (int i = 0; i < m_DirtyEvents.Length; i++)
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Type);
|
||||
switch (m_DirtyEvents[i].Type)
|
||||
var element = m_DirtyEvents.ElementAt(i);
|
||||
writer.WriteValueSafe(element.Type);
|
||||
switch (element.Type)
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
writer.WriteValueSafe(element.Index);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref element.Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
@@ -127,10 +127,26 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
// The listAtLastReset mechanism was put in place to deal with duplicate adds
|
||||
// upon initial spawn. However, it causes issues with in-scene placed objects
|
||||
// due to difference in spawn order. In order to address this, we pick the right
|
||||
// list based on the type of object.
|
||||
bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false;
|
||||
if (isSceneObject)
|
||||
{
|
||||
NetworkVariable<T>.Write(writer, m_List[i]);
|
||||
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
|
||||
for (int i = 0; i < m_ListAtLastReset.Length; i++)
|
||||
{
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
{
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +157,7 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out ushort count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -157,7 +173,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
m_List.Add(value);
|
||||
|
||||
if (OnListChanged != null)
|
||||
@@ -178,15 +194,24 @@ namespace Unity.Netcode
|
||||
Index = m_List.Length - 1,
|
||||
Value = m_List[m_List.Length - 1]
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = value;
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
|
||||
if (index < m_List.Length)
|
||||
{
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_List.Add(value);
|
||||
}
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
@@ -206,12 +231,13 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = m_List[index]
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
int index = m_List.IndexOf(value);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -238,6 +264,7 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -265,13 +292,14 @@ namespace Unity.Netcode
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
NetworkVariableSerialization<T>.Read(reader, out T value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
@@ -300,6 +328,7 @@ namespace Unity.Netcode
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -322,6 +351,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
MarkNetworkObjectDirty();
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -408,8 +438,15 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = item;
|
||||
if (index < m_List.Length)
|
||||
{
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_List.Add(item);
|
||||
}
|
||||
|
||||
var listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
@@ -441,13 +478,15 @@ namespace Unity.Netcode
|
||||
get => m_List[index];
|
||||
set
|
||||
{
|
||||
var previousValue = m_List[index];
|
||||
m_List[index] = value;
|
||||
|
||||
var listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.Value,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
@@ -457,9 +496,13 @@ namespace Unity.Netcode
|
||||
private void HandleAddListEvent(NetworkListEvent<T> listEvent)
|
||||
{
|
||||
m_DirtyEvents.Add(listEvent);
|
||||
MarkNetworkObjectDirty();
|
||||
OnListChanged?.Invoke(listEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is actually unused left-over from a previous interface
|
||||
/// </summary>
|
||||
public int LastModifiedTick
|
||||
{
|
||||
get
|
||||
@@ -469,9 +512,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden <see cref="IDisposable"/> implementation.
|
||||
/// CAUTION: If you derive from this class and override the <see cref="Dispose"/> method,
|
||||
/// you **must** always invoke the base.Dispose() method!
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
m_List.Dispose();
|
||||
m_ListAtLastReset.Dispose();
|
||||
m_DirtyEvents.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,17 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// A variable that can be synchronized over the network.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
|
||||
{
|
||||
// Functions that know how to serialize INetworkSerializable
|
||||
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : INetworkSerializable, new()
|
||||
{
|
||||
writer.WriteNetworkSerializable(value);
|
||||
}
|
||||
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : INetworkSerializable, new()
|
||||
{
|
||||
reader.ReadNetworkSerializable(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize other types
|
||||
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value) where TForMethod : unmanaged
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
|
||||
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
|
||||
|
||||
internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
|
||||
|
||||
// These static delegates provide the right implementation for writing and reading a particular network variable
|
||||
// type.
|
||||
//
|
||||
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
|
||||
//
|
||||
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
|
||||
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
|
||||
//
|
||||
// In the future we may be able to use this to provide packing implementations for floats and integers to
|
||||
// optimize bandwidth usage.
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
internal static WriteDelegate<T> Write = WriteValue;
|
||||
internal static ReadDelegate<T> Read = ReadValue;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
/// </summary>
|
||||
@@ -70,41 +24,22 @@ namespace Unity.Netcode
|
||||
public OnValueChangedDelegate OnValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom read permission
|
||||
/// Constructor for <see cref="NetworkVariable{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
|
||||
public NetworkVariable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom read permission
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableReadPermission readPerm) : base(readPerm)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableReadPermission readPerm, T value) : base(readPerm)
|
||||
/// <param name="value">initial value set that is of type T</param>
|
||||
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> for this <see cref="NetworkVariable{T}"/></param>
|
||||
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> for this <see cref="NetworkVariable{T}"/></param>
|
||||
public NetworkVariable(T value = default,
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and the default read permission
|
||||
/// The internal value of the NetworkVariable
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(T value)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private protected T m_InternalValue;
|
||||
|
||||
@@ -116,22 +51,43 @@ namespace Unity.Netcode
|
||||
get => m_InternalValue;
|
||||
set
|
||||
{
|
||||
// this could be improved. The Networking Manager is not always initialized here
|
||||
// Good place to decouple network manager from the network variable
|
||||
|
||||
// Also, note this is not really very water-tight, if you are running as a host
|
||||
// we cannot tell if a NetworkVariable write is happening inside client-ish code
|
||||
if (m_NetworkBehaviour && (m_NetworkBehaviour.NetworkManager.IsClient && !m_NetworkBehaviour.NetworkManager.IsHost))
|
||||
// Compare bitwise
|
||||
if (ValueEquals(ref m_InternalValue, ref value))
|
||||
{
|
||||
throw new InvalidOperationException("Client can't write to NetworkVariables");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
|
||||
}
|
||||
|
||||
Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Compares two values of the same unmanaged type by underlying memory
|
||||
// Ignoring any overridden value checks
|
||||
// Size is fixed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe bool ValueEquals(ref T a, ref T b)
|
||||
{
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="Value"/>, marks the <see cref="NetworkVariable{T}"/> dirty, and invokes the <see cref="OnValueChanged"/> callback
|
||||
/// if there are subscribers to that event.
|
||||
/// </summary>
|
||||
/// <param name="value">the new value of type `T` to be set/></param>
|
||||
private protected void Set(T value)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
SetDirty(true);
|
||||
T previousValue = m_InternalValue;
|
||||
m_InternalValue = value;
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
@@ -146,7 +102,6 @@ namespace Unity.Netcode
|
||||
WriteField(writer);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads value from the reader and applies it
|
||||
/// </summary>
|
||||
@@ -154,12 +109,17 @@ namespace Unity.Netcode
|
||||
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
||||
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
||||
{
|
||||
// todo:
|
||||
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
|
||||
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
|
||||
// would be stored in different fields
|
||||
|
||||
T previousValue = m_InternalValue;
|
||||
Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
SetDirty(true);
|
||||
}
|
||||
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
@@ -168,13 +128,13 @@ namespace Unity.Netcode
|
||||
/// <inheritdoc />
|
||||
public override void ReadField(FastBufferReader reader)
|
||||
{
|
||||
Read(reader, out m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Read(reader, out m_InternalValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteField(FastBufferWriter writer)
|
||||
{
|
||||
Write(writer, m_InternalValue);
|
||||
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,49 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
|
||||
/// <summary>
|
||||
/// Maintains a link to the associated NetworkBehaviour
|
||||
/// </summary>
|
||||
private protected NetworkBehaviour m_NetworkBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the NetworkVariable
|
||||
/// </summary>
|
||||
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
|
||||
public void Initialize(NetworkBehaviour networkBehaviour)
|
||||
{
|
||||
m_NetworkBehaviour = networkBehaviour;
|
||||
}
|
||||
|
||||
protected NetworkVariableBase(NetworkVariableReadPermission readPermIn = NetworkVariableReadPermission.Everyone)
|
||||
/// <summary>
|
||||
/// The default read permissions
|
||||
/// </summary>
|
||||
public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone;
|
||||
|
||||
/// <summary>
|
||||
/// The default write permissions
|
||||
/// </summary>
|
||||
public const NetworkVariableWritePermission DefaultWritePerm = NetworkVariableWritePermission.Server;
|
||||
|
||||
/// <summary>
|
||||
/// The default constructor for <see cref="NetworkVariableBase"/> that can be used to create a
|
||||
/// custom NetworkVariable.
|
||||
/// </summary>
|
||||
/// <param name="readPerm">the <see cref="NetworkVariableReadPermission"/> access settings</param>
|
||||
/// <param name="writePerm">the <see cref="NetworkVariableWritePermission"/> access settings</param>
|
||||
protected NetworkVariableBase(
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
{
|
||||
ReadPerm = readPermIn;
|
||||
ReadPerm = readPerm;
|
||||
WritePerm = writePerm;
|
||||
}
|
||||
|
||||
private protected bool m_IsDirty;
|
||||
/// <summary>
|
||||
/// The <see cref="m_IsDirty"/> property is used to determine if the
|
||||
/// value of the `NetworkVariable` has changed.
|
||||
/// </summary>
|
||||
private bool m_IsDirty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the network variable's instance
|
||||
@@ -37,12 +67,22 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public readonly NetworkVariableReadPermission ReadPerm;
|
||||
|
||||
/// <summary>
|
||||
/// The write permission for this var
|
||||
/// </summary>
|
||||
public readonly NetworkVariableWritePermission WritePerm;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not the variable needs to be delta synced
|
||||
/// </summary>
|
||||
/// <param name="isDirty">Whether or not the var is dirty</param>
|
||||
public virtual void SetDirty(bool isDirty)
|
||||
{
|
||||
m_IsDirty = isDirty;
|
||||
if (m_IsDirty && m_NetworkBehaviour != null)
|
||||
{
|
||||
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,26 +102,46 @@ namespace Unity.Netcode
|
||||
return m_IsDirty;
|
||||
}
|
||||
|
||||
public virtual bool ShouldWrite(ulong clientId, bool isServer)
|
||||
{
|
||||
return IsDirty() && isServer && CanClientRead(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not a specific client can read to the varaible
|
||||
/// Gets if a specific client has permission to read the var or not
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the remote client</param>
|
||||
/// <returns>Whether or not the client can read to the variable</returns>
|
||||
/// <param name="clientId">The client id</param>
|
||||
/// <returns>Whether or not the client has permission to read</returns>
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (ReadPerm)
|
||||
{
|
||||
default:
|
||||
case NetworkVariableReadPermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariableReadPermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariableReadPermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if a specific client has permission to write the var or not
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client id</param>
|
||||
/// <returns>Whether or not the client has permission to write</returns>
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (WritePerm)
|
||||
{
|
||||
default:
|
||||
case NetworkVariableWritePermission.Server:
|
||||
return clientId == NetworkManager.ServerClientId;
|
||||
case NetworkVariableWritePermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the ClientId of the owning client
|
||||
/// </summary>
|
||||
internal ulong OwnerClientId()
|
||||
{
|
||||
return m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,9 +167,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="reader">The stream to read the delta from</param>
|
||||
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
||||
|
||||
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
|
||||
|
||||
/// <summary>
|
||||
/// Virtual <see cref="IDisposable"/> implementation
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public class NetworkVariableHelper
|
||||
{
|
||||
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
|
||||
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
//
|
||||
// RuntimeAccessModifiersILPP will make this `public`
|
||||
internal static void InitializeDelegates<T>() where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable;
|
||||
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e54b65208bd3bbe4eaf62ca0384ae21f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,18 +1,32 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Permission type
|
||||
/// The permission types for reading a var
|
||||
/// </summary>
|
||||
public enum NetworkVariableReadPermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Everyone
|
||||
/// Everyone can read
|
||||
/// </summary>
|
||||
Everyone,
|
||||
|
||||
/// <summary>
|
||||
/// Owner-ownly
|
||||
/// Only the owner and the server can read
|
||||
/// </summary>
|
||||
OwnerOnly,
|
||||
Owner,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The permission types for writing a var
|
||||
/// </summary>
|
||||
public enum NetworkVariableWritePermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Only the server can write
|
||||
/// </summary>
|
||||
Server,
|
||||
/// <summary>
|
||||
/// Only the owner can write
|
||||
/// </summary>
|
||||
Owner
|
||||
}
|
||||
}
|
||||
|
||||
267
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
267
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface used by NetworkVariables to serialize them
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal interface INetworkVariableSerializer<T>
|
||||
{
|
||||
// Write has to be taken by ref here because of INetworkSerializable
|
||||
// Open Instance Delegates (pointers to methods without an instance attached to them)
|
||||
// require the first parameter passed to them (the instance) to be passed by ref.
|
||||
// So foo.Bar() becomes BarDelegate(ref foo);
|
||||
// Taking T as an in parameter like we do in other places would require making a copy
|
||||
// of it to pass it as a ref parameter.
|
||||
public void Write(FastBufferWriter writer, ref T value);
|
||||
public void Read(FastBufferReader reader, out T value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic serializer for unmanaged types.
|
||||
/// This covers primitives, built-in unity types, and IForceSerializeByMemcpy
|
||||
/// Since all of those ultimately end up calling WriteUnmanagedSafe, this simplifies things
|
||||
/// by calling that directly - thus preventing us from having to have a specific T that meets
|
||||
/// the specific constraints that the various generic WriteValue calls require.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class UnmanagedTypeSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
writer.WriteUnmanagedSafe(value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
reader.ReadUnmanagedSafe(out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for FixedStrings, which does the same thing FastBufferWriter/FastBufferReader do,
|
||||
/// but is implemented to get the data it needs using open instance delegates that are passed in
|
||||
/// via reflection. This prevents needing T to meet any interface requirements (which isn't achievable
|
||||
/// without incurring GC allocs on every call to Write or Read - reflection + Open Instance Delegates
|
||||
/// circumvent that.)
|
||||
///
|
||||
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||
/// aren't known to actually contain those methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class FixedStringSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
{
|
||||
internal delegate int GetLengthDelegate(ref T value);
|
||||
internal delegate void SetLengthDelegate(ref T value, int length);
|
||||
internal unsafe delegate byte* GetUnsafePtrDelegate(ref T value);
|
||||
|
||||
internal GetLengthDelegate GetLength;
|
||||
internal SetLengthDelegate SetLength;
|
||||
internal GetUnsafePtrDelegate GetUnsafePtr;
|
||||
|
||||
public unsafe void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
int length = GetLength(ref value);
|
||||
byte* data = GetUnsafePtr(ref value);
|
||||
writer.WriteUnmanagedSafe(length);
|
||||
writer.WriteBytesSafe(data, length);
|
||||
}
|
||||
public unsafe void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
value = new T();
|
||||
reader.ReadValueSafe(out int length);
|
||||
SetLength(ref value, length);
|
||||
reader.ReadBytesSafe(GetUnsafePtr(ref value), length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer for INetworkSerializable types, which does the same thing
|
||||
/// FastBufferWriter/FastBufferReader do, but is implemented to call the NetworkSerialize() method
|
||||
/// via open instance delegates passed in via reflection. This prevents needing T to meet any interface
|
||||
/// requirements (which isn't achievable without incurring GC allocs on every call to Write or Read -
|
||||
/// reflection + Open Instance Delegates circumvent that.)
|
||||
///
|
||||
/// Tests show that calling these delegates doesn't cause any GC allocations even though they're
|
||||
/// obtained via reflection and Delegate.CreateDelegate() and called on types that, at compile time,
|
||||
/// aren't known to actually contain those methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class NetworkSerializableSerializer<T> : INetworkVariableSerializer<T> where T : unmanaged
|
||||
{
|
||||
internal delegate void WriteValueDelegate(ref T value, BufferSerializer<BufferSerializerWriter> serializer);
|
||||
internal delegate void ReadValueDelegate(ref T value, BufferSerializer<BufferSerializerReader> serializer);
|
||||
|
||||
internal WriteValueDelegate WriteValue;
|
||||
internal ReadValueDelegate ReadValue;
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||
WriteValue(ref value, bufferSerializer);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
value = new T();
|
||||
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
|
||||
ReadValue(ref value, bufferSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to register user serialization with NetworkVariables for types
|
||||
/// that are serialized via user serialization, such as with FastBufferReader and FastBufferWriter
|
||||
/// extension methods. Finding those methods isn't achievable efficiently at runtime, so this allows
|
||||
/// users to tell NetworkVariable about those extension methods (or simply pass in a lambda)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class UserNetworkVariableSerialization<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The write value delegate handler definition
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="FastBufferWriter"/> to write the value of type `T`</param>
|
||||
/// <param name="value">The value of type `T` to be written</param>
|
||||
public delegate void WriteValueDelegate(FastBufferWriter writer, in T value);
|
||||
|
||||
/// <summary>
|
||||
/// The read value delegate handler definition
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="FastBufferReader"/> to read the value of type `T`</param>
|
||||
/// <param name="value">The value of type `T` to be read</param>
|
||||
public delegate void ReadValueDelegate(FastBufferReader reader, out T value);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="WriteValueDelegate"/> delegate handler declaration
|
||||
/// </summary>
|
||||
public static WriteValueDelegate WriteValue;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ReadValueDelegate"/> delegate handler declaration
|
||||
/// </summary>
|
||||
public static ReadValueDelegate ReadValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is instantiated for types that we can't determine ahead of time are serializable - types
|
||||
/// that don't meet any of the constraints for methods that are available on FastBufferReader and
|
||||
/// FastBufferWriter. These types may or may not be serializable through extension methods. To ensure
|
||||
/// the user has time to pass in the delegates to UserNetworkVariableSerialization, the existence
|
||||
/// of user serialization isn't checked until it's used, so if no serialization is provided, this
|
||||
/// will throw an exception when an object containing the relevant NetworkVariable is spawned.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class FallbackSerializer<T> : INetworkVariableSerializer<T>
|
||||
{
|
||||
public void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||
{
|
||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||
}
|
||||
UserNetworkVariableSerialization<T>.WriteValue(writer, value);
|
||||
}
|
||||
public void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
if (UserNetworkVariableSerialization<T>.ReadValue == null || UserNetworkVariableSerialization<T>.WriteValue == null)
|
||||
{
|
||||
throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.WriteValue)} and {nameof(UserNetworkVariableSerialization<T>)}.{nameof(UserNetworkVariableSerialization<T>.ReadValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}.");
|
||||
}
|
||||
UserNetworkVariableSerialization<T>.ReadValue(reader, out value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NetworkVariableSerializationTypes
|
||||
{
|
||||
internal static readonly HashSet<Type> BaseSupportedTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(char),
|
||||
typeof(decimal),
|
||||
typeof(double),
|
||||
typeof(float),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(Vector2),
|
||||
typeof(Vector3),
|
||||
typeof(Vector2Int),
|
||||
typeof(Vector3Int),
|
||||
typeof(Vector4),
|
||||
typeof(Quaternion),
|
||||
typeof(Color),
|
||||
typeof(Color32),
|
||||
typeof(Ray),
|
||||
typeof(Ray2D)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Support methods for reading/writing NetworkVariables
|
||||
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
|
||||
/// but there's no way to achieve the same thing with a class, this sets up various read/write schemes
|
||||
/// based on which constraints are met by `T` using reflection, which is done at module load time.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type the associated NetworkVariable is templated on</typeparam>
|
||||
[Serializable]
|
||||
public static class NetworkVariableSerialization<T> where T : unmanaged
|
||||
{
|
||||
private static INetworkVariableSerializer<T> s_Serializer = GetSerializer();
|
||||
|
||||
private static INetworkVariableSerializer<T> GetSerializer()
|
||||
{
|
||||
if (NetworkVariableSerializationTypes.BaseSupportedTypes.Contains(typeof(T)))
|
||||
{
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
if (typeof(Enum).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
return new UnmanagedTypeSerializer<T>();
|
||||
}
|
||||
|
||||
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
// Obtains "Open Instance Delegates" for the type's NetworkSerialize() methods -
|
||||
// one for an instance of the generic method taking BufferSerializerWriter as T,
|
||||
// one for an instance of the generic method taking BufferSerializerReader as T
|
||||
var writeMethod = (NetworkSerializableSerializer<T>.WriteValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.WriteValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerWriter)));
|
||||
var readMethod = (NetworkSerializableSerializer<T>.ReadValueDelegate)Delegate.CreateDelegate(typeof(NetworkSerializableSerializer<T>.ReadValueDelegate), null, typeof(T).GetMethod(nameof(INetworkSerializable.NetworkSerialize)).MakeGenericMethod(typeof(BufferSerializerReader)));
|
||||
return new NetworkSerializableSerializer<T> { WriteValue = writeMethod, ReadValue = readMethod };
|
||||
}
|
||||
|
||||
if (typeof(IUTF8Bytes).IsAssignableFrom(typeof(T)) && typeof(INativeList<byte>).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
// Get "OpenInstanceDelegates" for the Length property (get and set, which are prefixed
|
||||
// with "get_" and "set_" under the hood and emitted as methods) and GetUnsafePtr()
|
||||
var getLength = (FixedStringSerializer<T>.GetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetLengthDelegate), null, typeof(T).GetMethod("get_" + nameof(INativeList<byte>.Length)));
|
||||
var setLength = (FixedStringSerializer<T>.SetLengthDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.SetLengthDelegate), null, typeof(T).GetMethod("set_" + nameof(INativeList<byte>.Length)));
|
||||
var getUnsafePtr = (FixedStringSerializer<T>.GetUnsafePtrDelegate)Delegate.CreateDelegate(typeof(FixedStringSerializer<T>.GetUnsafePtrDelegate), null, typeof(T).GetMethod(nameof(IUTF8Bytes.GetUnsafePtr)));
|
||||
return new FixedStringSerializer<T> { GetLength = getLength, SetLength = setLength, GetUnsafePtr = getUnsafePtr };
|
||||
}
|
||||
|
||||
return new FallbackSerializer<T>();
|
||||
}
|
||||
|
||||
internal static void Write(FastBufferWriter writer, ref T value)
|
||||
{
|
||||
s_Serializer.Write(writer, ref value);
|
||||
}
|
||||
|
||||
internal static void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
s_Serializer.Read(reader, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c6ef5fdf2e94ec3b4ce8086d52700b3
|
||||
timeCreated: 1650985453
|
||||
@@ -82,7 +82,7 @@ namespace Unity.Netcode
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,14 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal uint SceneEventId;
|
||||
internal Action<uint> EventAction;
|
||||
/// <summary>
|
||||
/// Used server-side for integration testing in order to
|
||||
/// invoke the SceneEventProgress once done loading
|
||||
/// </summary>
|
||||
internal Action Completed;
|
||||
internal void Invoke()
|
||||
{
|
||||
Completed?.Invoke();
|
||||
EventAction.Invoke(SceneEventId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Unity.Netcode
|
||||
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
|
||||
/// scene event status.<br/>
|
||||
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// See also: <br/>
|
||||
/// <seealso cref="SceneEventType"/>
|
||||
/// </summary>
|
||||
@@ -132,7 +133,7 @@ namespace Unity.Netcode
|
||||
private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
internal const int InvalidSceneNameOrPath = -1;
|
||||
|
||||
// Used to be able to turn re-synchronization off for future snapshot development purposes.
|
||||
// Used to be able to turn re-synchronization off
|
||||
internal static bool DisableReSynchronization;
|
||||
|
||||
/// <summary>
|
||||
@@ -166,6 +167,7 @@ namespace Unity.Netcode
|
||||
/// <item><term><see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed</term></item>
|
||||
/// <item><term><see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed</term></item>
|
||||
/// </list>
|
||||
/// <em>Note: Do not start new scene events within NetworkSceneManager scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event SceneEventDelegate OnSceneEvent;
|
||||
|
||||
@@ -239,13 +241,15 @@ namespace Unity.Netcode
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnLoadDelegateHandler OnLoad;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnUnloadDelegateHandler OnUnload;
|
||||
|
||||
@@ -254,7 +258,8 @@ namespace Unity.Netcode
|
||||
/// after a client is approved for connection in order to synchronize the client with the currently loaded
|
||||
/// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.<br/>
|
||||
/// <em>Note: The server and connected client(s) will always receive this notification.
|
||||
/// This event is generated on a per newly connected and approved client basis.</em>
|
||||
/// This event is generated on a per newly connected and approved client basis.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnSynchronizeDelegateHandler OnSynchronize;
|
||||
|
||||
@@ -263,7 +268,8 @@ namespace Unity.Netcode
|
||||
/// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains
|
||||
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
|
||||
/// finished the <see cref="SceneEventType.Load"/> event.<br/>
|
||||
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em>
|
||||
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
|
||||
|
||||
@@ -273,21 +279,24 @@ namespace Unity.Netcode
|
||||
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
|
||||
/// finished the <see cref="SceneEventType.Unload"/> event.<br/>
|
||||
/// <em>Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will
|
||||
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em>
|
||||
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
|
||||
/// <em>Note: The server receives this message from all clients (including itself).
|
||||
/// Each client receives their own notification sent to the server.</em>
|
||||
/// Each client receives their own notification sent to the server.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnLoadCompleteDelegateHandler OnLoadComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
|
||||
/// <em>Note: The server receives this message from all clients (including itself).
|
||||
/// Each client receives their own notification sent to the server.</em>
|
||||
/// Each client receives their own notification sent to the server.</em><br/>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
|
||||
|
||||
@@ -296,6 +305,7 @@ namespace Unity.Netcode
|
||||
/// <em> Note: The server receives this message from the client, but will never generate this event for itself.
|
||||
/// Each client receives their own notification sent to the server. This is useful to know that a client has
|
||||
/// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
|
||||
/// <em>*** Do not start new scene events within scene event notification callbacks.</em><br/>
|
||||
/// </summary>
|
||||
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
|
||||
|
||||
@@ -319,8 +329,12 @@ namespace Unity.Netcode
|
||||
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
||||
|
||||
/// <summary>
|
||||
/// Proof of concept: Interface version that allows for the decoupling from
|
||||
/// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage)
|
||||
/// The SceneManagerHandler implementation
|
||||
/// </summary>
|
||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||
|
||||
/// <summary>
|
||||
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
|
||||
/// </summary>
|
||||
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
||||
{
|
||||
@@ -339,10 +353,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||
/// End of Proof of Concept
|
||||
|
||||
|
||||
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
||||
|
||||
/// <summary>
|
||||
@@ -425,6 +435,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
SceneUnloadEventHandler.Shutdown();
|
||||
|
||||
foreach (var keypair in SceneEventDataStore)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
@@ -488,8 +500,18 @@ namespace Unity.Netcode
|
||||
var scenePath = SceneUtility.GetScenePathByBuildIndex(i);
|
||||
var hash = XXHash.Hash32(scenePath);
|
||||
var buildIndex = SceneUtility.GetBuildIndexByScenePath(scenePath);
|
||||
HashToBuildIndex.Add(hash, buildIndex);
|
||||
BuildIndexToHash.Add(buildIndex, hash);
|
||||
|
||||
// In the rare-case scenario where a programmatically generated build has duplicate
|
||||
// scene entries, we will log an error and skip the entry
|
||||
if (!HashToBuildIndex.ContainsKey(hash))
|
||||
{
|
||||
HashToBuildIndex.Add(hash, buildIndex);
|
||||
BuildIndexToHash.Add(buildIndex, hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"{nameof(NetworkSceneManager)} is skipping duplicate scene path entry {scenePath}. Make sure your scenes in build list does not contain duplicates!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +542,8 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table!");
|
||||
throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table! Verify that all scenes requiring" +
|
||||
$" server to client synchronization are in the scenes in build list.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,6 +644,12 @@ namespace Unity.Netcode
|
||||
return validated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for NetcodeIntegrationTest testing in order to properly
|
||||
/// assign the right loaded scene to the right client's ScenesLoaded list
|
||||
/// </summary>
|
||||
internal Func<string, Scene> OverrideGetAndAddNewlyLoadedSceneByName;
|
||||
|
||||
/// <summary>
|
||||
/// Since SceneManager.GetSceneByName only returns the first scene that matches the name
|
||||
/// we must "find" a newly added scene by looking through all loaded scenes and determining
|
||||
@@ -632,20 +661,27 @@ namespace Unity.Netcode
|
||||
/// <returns></returns>
|
||||
internal Scene GetAndAddNewlyLoadedSceneByName(string sceneName)
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
if (OverrideGetAndAddNewlyLoadedSceneByName != null)
|
||||
{
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
return OverrideGetAndAddNewlyLoadedSceneByName.Invoke(sceneName);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
var sceneLoaded = SceneManager.GetSceneAt(i);
|
||||
if (sceneLoaded.name == sceneName)
|
||||
{
|
||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||
{
|
||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||
return sceneLoaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -709,18 +745,18 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="globalObjectIdHash"></param>
|
||||
/// <returns></returns>
|
||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash)
|
||||
internal NetworkObject GetSceneRelativeInSceneNetworkObject(uint globalObjectIdHash, int? networkSceneHandle)
|
||||
{
|
||||
if (ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(SceneBeingSynchronized.handle))
|
||||
var sceneHandle = SceneBeingSynchronized.handle;
|
||||
if (networkSceneHandle.HasValue && networkSceneHandle.Value != 0)
|
||||
{
|
||||
var inScenePlacedNetworkObject = ScenePlacedObjects[globalObjectIdHash][SceneBeingSynchronized.handle];
|
||||
|
||||
// We can only have 1 duplicated globalObjectIdHash per scene instance, so remove it once it has been returned
|
||||
ScenePlacedObjects[globalObjectIdHash].Remove(SceneBeingSynchronized.handle);
|
||||
|
||||
return inScenePlacedNetworkObject;
|
||||
sceneHandle = ServerSceneHandleToClientSceneHandle[networkSceneHandle.Value];
|
||||
}
|
||||
if (ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
|
||||
{
|
||||
return ScenePlacedObjects[globalObjectIdHash][sceneHandle];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -848,7 +884,6 @@ namespace Unity.Netcode
|
||||
/// Callback for the <see cref="SceneEventProgress.OnComplete"/> <see cref="SceneEventProgress.OnCompletedDelegate"/> handler
|
||||
/// </summary>
|
||||
/// <param name="sceneEventProgress"></param>
|
||||
/// <returns></returns>
|
||||
private bool OnSceneEventProgressCompleted(SceneEventProgress sceneEventProgress)
|
||||
{
|
||||
var sceneEventData = BeginSceneEvent();
|
||||
@@ -857,7 +892,7 @@ namespace Unity.Netcode
|
||||
sceneEventData.SceneEventType = sceneEventProgress.SceneEventType;
|
||||
sceneEventData.ClientsCompleted = sceneEventProgress.DoneClients;
|
||||
sceneEventData.LoadSceneMode = sceneEventProgress.LoadSceneMode;
|
||||
sceneEventData.ClientsTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList();
|
||||
sceneEventData.ClientsTimedOut = sceneEventProgress.ClientsThatStartedSceneEvent.Except(sceneEventProgress.DoneClients).ToList();
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -876,7 +911,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
SceneEventType = sceneEventProgress.SceneEventType,
|
||||
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
|
||||
ClientId = m_NetworkManager.ServerClientId,
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
LoadSceneMode = sceneEventProgress.LoadSceneMode,
|
||||
ClientsThatCompleted = sceneEventProgress.DoneClients,
|
||||
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
|
||||
@@ -900,7 +935,7 @@ namespace Unity.Netcode
|
||||
/// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene.
|
||||
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/>
|
||||
/// </summary>
|
||||
/// <param name="sceneName">scene name to unload</param>
|
||||
/// <param name="scene"></param>
|
||||
/// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns>
|
||||
public SceneEventProgressStatus UnloadScene(Scene scene)
|
||||
{
|
||||
@@ -934,11 +969,18 @@ namespace Unity.Netcode
|
||||
sceneEventProgress.SceneEventType = SceneEventType.UnloadEventCompleted;
|
||||
|
||||
ScenesLoaded.Remove(scene.handle);
|
||||
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded };
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventAction);
|
||||
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
|
||||
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||
// If integration testing, IntegrationTestSceneHandler returns null
|
||||
if (sceneUnload == null)
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
|
||||
}
|
||||
|
||||
// Notify local server that a scene is going to be unloaded
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -947,10 +989,10 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = sceneName,
|
||||
ClientId = m_NetworkManager.ServerClientId // Server can only invoke this
|
||||
ClientId = NetworkManager.ServerClientId // Server can only invoke this
|
||||
});
|
||||
|
||||
OnUnload?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneUnload);
|
||||
OnUnload?.Invoke(NetworkManager.ServerClientId, sceneName, sceneUnload);
|
||||
|
||||
//Return the status
|
||||
return sceneEventProgress.Status;
|
||||
@@ -1010,6 +1052,12 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private void OnSceneUnloaded(uint sceneEventId)
|
||||
{
|
||||
// If we are shutdown or about to shutdown, then ignore this event
|
||||
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
// First thing we do, if we are a server, is to send the unload scene event.
|
||||
if (m_NetworkManager.IsServer)
|
||||
@@ -1017,12 +1065,12 @@ namespace Unity.Netcode
|
||||
// Server sends the unload scene notification after unloading because it will despawn all scene relative in-scene NetworkObjects
|
||||
// If we send this event to all clients before the server is finished unloading they will get warning about an object being
|
||||
// despawned that no longer exists
|
||||
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray());
|
||||
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray());
|
||||
|
||||
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,7 +1083,7 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = SceneNameFromHash(sceneEventData.SceneHash),
|
||||
ClientId = m_NetworkManager.IsServer ? m_NetworkManager.ServerClientId : m_NetworkManager.LocalClientId
|
||||
ClientId = m_NetworkManager.IsServer ? NetworkManager.ServerClientId : m_NetworkManager.LocalClientId
|
||||
});
|
||||
|
||||
OnUnloadComplete?.Invoke(m_NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash));
|
||||
@@ -1043,7 +1091,7 @@ namespace Unity.Netcode
|
||||
// Clients send a notification back to the server they have completed the unload scene event
|
||||
if (!m_NetworkManager.IsServer)
|
||||
{
|
||||
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
|
||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||
}
|
||||
|
||||
EndSceneEvent(sceneEventId);
|
||||
@@ -1053,7 +1101,7 @@ namespace Unity.Netcode
|
||||
|
||||
private void EmptySceneUnloadedOperation(uint sceneEventId)
|
||||
{
|
||||
// Do nothing (this is a stub call since it is only used to flush all currently loaded scenes)
|
||||
// Do nothing (this is a stub call since it is only used to flush all additively loaded scenes)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1063,6 +1111,7 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
|
||||
{
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
|
||||
var currentActiveScene = SceneManager.GetActiveScene();
|
||||
foreach (var keyHandleEntry in ScenesLoaded)
|
||||
@@ -1072,15 +1121,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
|
||||
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = sceneUnload,
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = keyHandleEntry.Value.name,
|
||||
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
|
||||
ClientId = m_NetworkManager.ServerClientId
|
||||
});
|
||||
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
||||
}
|
||||
}
|
||||
// clear out our scenes loaded list
|
||||
@@ -1093,6 +1134,7 @@ namespace Unity.Netcode
|
||||
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/>
|
||||
/// </summary>
|
||||
/// <param name="sceneName">the name of the scene to be loaded</param>
|
||||
/// <param name="loadSceneMode">how the scene will be loaded (single or additive mode)</param>
|
||||
/// <returns><see cref="SceneEventProgressStatus"/> (<see cref="SceneEventProgressStatus.Started"/> means it was successful)</returns>
|
||||
public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSceneMode)
|
||||
{
|
||||
@@ -1113,12 +1155,12 @@ namespace Unity.Netcode
|
||||
sceneEventData.SceneEventType = SceneEventType.Load;
|
||||
sceneEventData.SceneHash = SceneHashFromNameOrPath(sceneName);
|
||||
sceneEventData.LoadSceneMode = loadSceneMode;
|
||||
|
||||
var sceneEventId = sceneEventData.SceneEventId;
|
||||
// This both checks to make sure the scene is valid and if not resets the active scene event
|
||||
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
|
||||
if (!m_IsSceneEventActive)
|
||||
{
|
||||
EndSceneEvent(sceneEventData.SceneEventId);
|
||||
EndSceneEvent(sceneEventId);
|
||||
return SceneEventProgressStatus.SceneFailedVerification;
|
||||
}
|
||||
|
||||
@@ -1131,14 +1173,24 @@ namespace Unity.Netcode
|
||||
MoveObjectsToDontDestroyOnLoad();
|
||||
|
||||
// Now Unload all currently additively loaded scenes
|
||||
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
|
||||
UnloadAdditivelyLoadedScenes(sceneEventId);
|
||||
|
||||
// Register the active scene for unload scene event notifications
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
}
|
||||
|
||||
// Now start loading the scene
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
|
||||
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded });
|
||||
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||
var sceneEventAction = new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded };
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventAction);
|
||||
// If integration testing, IntegrationTestSceneHandler returns null
|
||||
if (sceneLoad == null)
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneEventAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
|
||||
}
|
||||
|
||||
// Notify the local server that a scene loading event has begun
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
@@ -1147,15 +1199,123 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = sceneName,
|
||||
ClientId = m_NetworkManager.ServerClientId
|
||||
ClientId = NetworkManager.ServerClientId
|
||||
});
|
||||
|
||||
OnLoad?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
|
||||
//Return our scene progress instance
|
||||
return sceneEventProgress.Status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to handle "odd ball" scene unload event notification scenarios
|
||||
/// when scene switching.
|
||||
/// </summary>
|
||||
internal class SceneUnloadEventHandler
|
||||
{
|
||||
private static Dictionary<NetworkManager, List<SceneUnloadEventHandler>> s_Instances = new Dictionary<NetworkManager, List<SceneUnloadEventHandler>>();
|
||||
|
||||
internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||
{
|
||||
var networkManager = networkSceneManager.m_NetworkManager;
|
||||
if (!s_Instances.ContainsKey(networkManager))
|
||||
{
|
||||
s_Instances.Add(networkManager, new List<SceneUnloadEventHandler>());
|
||||
}
|
||||
var clientId = networkManager.IsServer ? NetworkManager.ServerClientId : networkManager.LocalClientId;
|
||||
s_Instances[networkManager].Add(new SceneUnloadEventHandler(networkSceneManager, scene, clientId, loadSceneMode, asyncOperation));
|
||||
}
|
||||
|
||||
private static void SceneUnloadComplete(SceneUnloadEventHandler sceneUnloadEventHandler)
|
||||
{
|
||||
if (sceneUnloadEventHandler == null || sceneUnloadEventHandler.m_NetworkSceneManager == null || sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var networkManager = sceneUnloadEventHandler.m_NetworkSceneManager.m_NetworkManager;
|
||||
if (s_Instances.ContainsKey(networkManager))
|
||||
{
|
||||
s_Instances[networkManager].Remove(sceneUnloadEventHandler);
|
||||
if (s_Instances[networkManager].Count == 0)
|
||||
{
|
||||
s_Instances.Remove(networkManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by NetworkSceneManager when it is disposing
|
||||
/// </summary>
|
||||
internal static void Shutdown()
|
||||
{
|
||||
foreach (var instanceEntry in s_Instances)
|
||||
{
|
||||
foreach (var instance in instanceEntry.Value)
|
||||
{
|
||||
instance.OnShutdown();
|
||||
}
|
||||
instanceEntry.Value.Clear();
|
||||
}
|
||||
s_Instances.Clear();
|
||||
}
|
||||
|
||||
private NetworkSceneManager m_NetworkSceneManager;
|
||||
private AsyncOperation m_AsyncOperation;
|
||||
private LoadSceneMode m_LoadSceneMode;
|
||||
private ulong m_ClientId;
|
||||
private Scene m_Scene;
|
||||
private bool m_ShuttingDown;
|
||||
|
||||
private void OnShutdown()
|
||||
{
|
||||
m_ShuttingDown = true;
|
||||
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||
}
|
||||
|
||||
private void SceneUnloaded(Scene scene)
|
||||
{
|
||||
if (m_Scene.handle == scene.handle && !m_ShuttingDown)
|
||||
{
|
||||
if (m_NetworkSceneManager != null && m_NetworkSceneManager.m_NetworkManager != null)
|
||||
{
|
||||
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = m_AsyncOperation,
|
||||
SceneEventType = SceneEventType.UnloadComplete,
|
||||
SceneName = m_Scene.name,
|
||||
LoadSceneMode = m_LoadSceneMode,
|
||||
ClientId = m_ClientId
|
||||
});
|
||||
m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name);
|
||||
}
|
||||
SceneManager.sceneUnloaded -= SceneUnloaded;
|
||||
SceneUnloadComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene scene, ulong clientId, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null)
|
||||
{
|
||||
m_LoadSceneMode = loadSceneMode;
|
||||
m_AsyncOperation = asyncOperation;
|
||||
m_NetworkSceneManager = networkSceneManager;
|
||||
m_ClientId = clientId;
|
||||
m_Scene = scene;
|
||||
SceneManager.sceneUnloaded += SceneUnloaded;
|
||||
// Send the initial unload event notification
|
||||
m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
AsyncOperation = m_AsyncOperation,
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = m_Scene.name,
|
||||
LoadSceneMode = m_LoadSceneMode,
|
||||
ClientId = clientId
|
||||
});
|
||||
|
||||
m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.m_NetworkManager.LocalClientId, m_Scene.name, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Client Side:
|
||||
/// Handles both forms of scene loading
|
||||
@@ -1187,10 +1347,13 @@ namespace Unity.Netcode
|
||||
// When it is set: Just before starting the asynchronous loading call
|
||||
// When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do
|
||||
// not destroy temporary scene are moved into the active scene
|
||||
// TODO: When Snapshot scene spawning is enabled this needs to be removed.
|
||||
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
||||
{
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
|
||||
// Register the active scene for unload scene event notifications
|
||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||
|
||||
}
|
||||
|
||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
|
||||
@@ -1208,13 +1371,18 @@ namespace Unity.Netcode
|
||||
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Client and Server:
|
||||
/// Generic on scene loaded callback method to be called upon a scene loading
|
||||
/// </summary>
|
||||
private void OnSceneLoaded(uint sceneEventId)
|
||||
{
|
||||
// If we are shutdown or about to shutdown, then ignore this event
|
||||
if (!m_NetworkManager.IsListening || m_NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
|
||||
if (!nextScene.isLoaded || !nextScene.IsValid())
|
||||
@@ -1278,7 +1446,9 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!keyValuePairBySceneHandle.Value.IsPlayerObject)
|
||||
{
|
||||
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, true);
|
||||
// All in-scene placed NetworkObjects default to being owned by the server
|
||||
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value,
|
||||
m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.ServerClientId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1290,7 +1460,7 @@ namespace Unity.Netcode
|
||||
for (int j = 0; j < m_NetworkManager.ConnectedClientsList.Count; j++)
|
||||
{
|
||||
var clientId = m_NetworkManager.ConnectedClientsList[j].ClientId;
|
||||
if (clientId != m_NetworkManager.ServerClientId)
|
||||
if (clientId != NetworkManager.ServerClientId)
|
||||
{
|
||||
sceneEventData.TargetClientId = clientId;
|
||||
var message = new SceneEventMessage
|
||||
@@ -1309,16 +1479,16 @@ namespace Unity.Netcode
|
||||
SceneEventType = SceneEventType.LoadComplete,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = SceneNameFromHash(sceneEventData.SceneHash),
|
||||
ClientId = m_NetworkManager.ServerClientId,
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
Scene = scene,
|
||||
});
|
||||
|
||||
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
|
||||
OnLoadComplete?.Invoke(NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
|
||||
|
||||
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
|
||||
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
|
||||
{
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
}
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
@@ -1333,7 +1503,7 @@ namespace Unity.Netcode
|
||||
sceneEventData.DeserializeScenePlacedObjects();
|
||||
|
||||
sceneEventData.SceneEventType = SceneEventType.LoadComplete;
|
||||
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
|
||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||
m_IsSceneEventActive = false;
|
||||
|
||||
// Notify local client that the scene was loaded
|
||||
@@ -1351,6 +1521,13 @@ namespace Unity.Netcode
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for integration testing, due to the complexities of having all clients loading scenes
|
||||
/// this is needed to "filter" out the scenes not loaded by NetworkSceneManager
|
||||
/// (i.e. we don't want a late joining player to load all of the other client scenes)
|
||||
/// </summary>
|
||||
internal Func<Scene, bool> ExcludeSceneFromSychronization;
|
||||
|
||||
/// <summary>
|
||||
/// Server Side:
|
||||
/// This is used for players that have just had their connection approved and will assure they are synchronized
|
||||
@@ -1377,6 +1554,13 @@ namespace Unity.Netcode
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
|
||||
// NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager
|
||||
// unless the scene in question is the currently active scene.
|
||||
if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
||||
|
||||
// This would depend upon whether we are additive or not
|
||||
@@ -1394,11 +1578,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sceneEventData.AddSceneToSynchronize(sceneHash, scene.handle);
|
||||
}
|
||||
|
||||
sceneEventData.AddSpawnedNetworkObjects();
|
||||
sceneEventData.AddDespawnedInSceneNetworkObjects();
|
||||
|
||||
var message = new SceneEventMessage
|
||||
{
|
||||
@@ -1434,12 +1618,9 @@ namespace Unity.Netcode
|
||||
|
||||
var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive;
|
||||
|
||||
// Always check to see if the scene needs to be validated
|
||||
if (!ValidateSceneBeforeLoading(sceneHash, loadSceneMode))
|
||||
{
|
||||
EndSceneEvent(sceneEventId);
|
||||
return;
|
||||
}
|
||||
// Store the sceneHandle and hash
|
||||
sceneEventData.NetworkSceneHandle = sceneHandle;
|
||||
sceneEventData.ClientSceneHash = sceneHash;
|
||||
|
||||
// If this is the beginning of the synchronization event, then send client a notification that synchronization has begun
|
||||
if (sceneHash == sceneEventData.SceneHash)
|
||||
@@ -1456,9 +1637,16 @@ namespace Unity.Netcode
|
||||
ScenePlacedObjects.Clear();
|
||||
}
|
||||
|
||||
// Store the sceneHandle and hash
|
||||
sceneEventData.ClientSceneHandle = sceneHandle;
|
||||
sceneEventData.ClientSceneHash = sceneHash;
|
||||
// Always check to see if the scene needs to be validated
|
||||
if (!ValidateSceneBeforeLoading(sceneHash, loadSceneMode))
|
||||
{
|
||||
HandleClientSceneEvent(sceneEventId);
|
||||
if (m_NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"Client declined to load the scene {sceneName}, continuing with synchronization.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var shouldPassThrough = false;
|
||||
var sceneLoad = (AsyncOperation)null;
|
||||
@@ -1520,9 +1708,9 @@ namespace Unity.Netcode
|
||||
SceneManager.SetActiveScene(nextScene);
|
||||
}
|
||||
|
||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle))
|
||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.NetworkSceneHandle))
|
||||
{
|
||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle);
|
||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.NetworkSceneHandle, nextScene.handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1544,9 +1732,9 @@ namespace Unity.Netcode
|
||||
{
|
||||
EventData = responseSceneEventData
|
||||
};
|
||||
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ServerClientId);
|
||||
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId);
|
||||
|
||||
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size);
|
||||
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size);
|
||||
|
||||
EndSceneEvent(responseSceneEventData.SceneEventId);
|
||||
|
||||
@@ -1600,7 +1788,7 @@ namespace Unity.Netcode
|
||||
sceneEventData.SynchronizeSceneNetworkObjects(m_NetworkManager);
|
||||
|
||||
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
|
||||
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
|
||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||
|
||||
// All scenes are synchronized, let the server know we are done synchronizing
|
||||
m_NetworkManager.IsConnectedClient = true;
|
||||
@@ -1627,7 +1815,7 @@ namespace Unity.Netcode
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
ClientId = m_NetworkManager.ServerClientId, // Server sent this to client
|
||||
ClientId = NetworkManager.ServerClientId, // Server sent this to client
|
||||
});
|
||||
|
||||
EndSceneEvent(sceneEventId);
|
||||
@@ -1642,7 +1830,7 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = SceneNameFromHash(sceneEventData.SceneHash),
|
||||
ClientId = m_NetworkManager.ServerClientId,
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
ClientsThatCompleted = sceneEventData.ClientsCompleted,
|
||||
ClientsThatTimedOut = sceneEventData.ClientsTimedOut,
|
||||
});
|
||||
@@ -1734,9 +1922,9 @@ namespace Unity.Netcode
|
||||
// NetworkObjects
|
||||
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
||||
|
||||
// TODO: This check and associated code can be removed once we determine all
|
||||
// snapshot destroy messages are being updated until the server receives ACKs
|
||||
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization)
|
||||
// Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
|
||||
// a potential crash within the MessageSystem (i.e. sending to a client that no longer exists)
|
||||
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization && m_NetworkManager.ConnectedClients.ContainsKey(clientId))
|
||||
{
|
||||
sceneEventData.SceneEventType = SceneEventType.ReSynchronize;
|
||||
SendSceneEventData(sceneEventId, new ulong[] { clientId });
|
||||
@@ -1830,7 +2018,7 @@ namespace Unity.Netcode
|
||||
/// Using the local scene relative Scene.handle as a sub-key to the root dictionary allows us to
|
||||
/// distinguish between duplicate in-scene placed NetworkObjects
|
||||
/// </summary>
|
||||
private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
|
||||
internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
|
||||
{
|
||||
if (clearScenePlacedObjects)
|
||||
{
|
||||
@@ -1845,25 +2033,25 @@ namespace Unity.Netcode
|
||||
// at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects
|
||||
foreach (var networkObjectInstance in networkObjects)
|
||||
{
|
||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (additive scenes)
|
||||
if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
|
||||
networkObjectInstance.gameObject.scene.handle == sceneToFilterBy.handle)
|
||||
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
||||
var sceneHandle = networkObjectInstance.GetSceneOriginHandle();
|
||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
|
||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && sceneHandle == sceneToFilterBy.handle)
|
||||
{
|
||||
if (!ScenePlacedObjects.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
|
||||
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
ScenePlacedObjects.Add(networkObjectInstance.GlobalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].ContainsKey(networkObjectInstance.gameObject.scene.handle))
|
||||
if (!ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
|
||||
{
|
||||
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].Add(networkObjectInstance.gameObject.scene.handle, networkObjectInstance);
|
||||
ScenePlacedObjects[globalObjectIdHash].Add(sceneHandle, networkObjectInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
var exitingEntryName = ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle] != null ?
|
||||
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle].name : "Null Entry";
|
||||
var exitingEntryName = ScenePlacedObjects[globalObjectIdHash][sceneHandle] != null ? ScenePlacedObjects[globalObjectIdHash][sceneHandle].name : "Null Entry";
|
||||
throw new Exception($"{networkObjectInstance.name} tried to registered with {nameof(ScenePlacedObjects)} which already contains " +
|
||||
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {networkObjectInstance.GlobalObjectIdHash} for {exitingEntryName}!");
|
||||
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {globalObjectIdHash} for {exitingEntryName}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace Unity.Netcode
|
||||
/// <b>Invocation:</b> Server Side<br/>
|
||||
/// <b>Message Flow:</b> Server to client<br/>
|
||||
/// <b>Event Notification:</b> Both server and client receive a local notification<br/>
|
||||
/// <em>Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point.</em>
|
||||
/// </summary>
|
||||
ReSynchronize,
|
||||
/// <summary>
|
||||
@@ -92,7 +91,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal SceneEventType SceneEventType;
|
||||
internal LoadSceneMode LoadSceneMode;
|
||||
internal Guid SceneEventProgressId;
|
||||
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
|
||||
internal uint SceneEventId;
|
||||
|
||||
|
||||
@@ -101,7 +100,7 @@ namespace Unity.Netcode
|
||||
|
||||
// Used by the client during synchronization
|
||||
internal uint ClientSceneHash;
|
||||
internal int ClientSceneHandle;
|
||||
internal int NetworkSceneHandle;
|
||||
|
||||
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
|
||||
/// NetworkVariable information. If that process changes, then we need to update this
|
||||
@@ -119,6 +118,9 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
private List<NetworkObject> m_NetworkObjectsSync = new List<NetworkObject>();
|
||||
|
||||
private List<NetworkObject> m_DespawnedInSceneObjectsSync = new List<NetworkObject>();
|
||||
private Dictionary<int, List<uint>> m_DespawnedInSceneObjects = new Dictionary<int, List<uint>>();
|
||||
|
||||
/// <summary>
|
||||
/// Server Side Re-Synchronization:
|
||||
/// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned,
|
||||
@@ -244,6 +246,19 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Sort(SortNetworkObjects);
|
||||
}
|
||||
|
||||
internal void AddDespawnedInSceneNetworkObjects()
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Clear();
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.NetworkManager == m_NetworkManager);
|
||||
foreach (var sobj in inSceneNetworkObjects)
|
||||
{
|
||||
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
|
||||
{
|
||||
m_DespawnedInSceneObjectsSync.Add(sobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server Side:
|
||||
/// Used during the synchronization process to associate NetworkObjects with scenes
|
||||
@@ -373,7 +388,6 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(ScenesToSynchronize.ToArray());
|
||||
writer.WriteValueSafe(SceneHandlesToSynchronize.ToArray());
|
||||
|
||||
|
||||
// Store our current position in the stream to come back and say how much data we have written
|
||||
var positionStart = writer.Position;
|
||||
|
||||
@@ -384,17 +398,31 @@ namespace Unity.Netcode
|
||||
int totalBytes = 0;
|
||||
|
||||
// Write the number of NetworkObjects we are serializing
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync.Count());
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count());
|
||||
// Serialize all NetworkObjects that are spawned
|
||||
for (var i = 0; i < m_NetworkObjectsSync.Count(); ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
writer.WriteValueSafe(m_NetworkObjectsSync[i].gameObject.scene.handle);
|
||||
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
|
||||
sceneObject.Serialize(writer);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Write the number of despawned in-scene placed NetworkObjects
|
||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync.Count());
|
||||
// Write the scene handle and GlobalObjectIdHash value
|
||||
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count(); ++i)
|
||||
{
|
||||
var noStart = writer.Position;
|
||||
var sceneObject = m_DespawnedInSceneObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
|
||||
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||
var noStop = writer.Position;
|
||||
totalBytes += (int)(noStop - noStart);
|
||||
}
|
||||
|
||||
// Size Place Holder -- End
|
||||
var positionEnd = writer.Position;
|
||||
var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint)));
|
||||
@@ -592,9 +620,6 @@ namespace Unity.Netcode
|
||||
networkObject.IsSpawned = false;
|
||||
if (m_NetworkManager.PrefabHandler.ContainsHandler(networkObject))
|
||||
{
|
||||
// Since this is the client side and we have missed the delete message, until the Snapshot system is in place for spawn and despawn handling
|
||||
// we have to remove this from the list of spawned objects manually or when a NetworkObjectId is recycled the client will throw an error
|
||||
// about the id already being assigned.
|
||||
if (m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
|
||||
{
|
||||
m_NetworkManager.SpawnManager.SpawnedObjects.Remove(networkObjectId);
|
||||
@@ -687,15 +712,16 @@ namespace Unity.Netcode
|
||||
{
|
||||
try
|
||||
{
|
||||
// Process all NetworkObjects for this scene
|
||||
InternalBuffer.ReadValueSafe(out int newObjectsCount);
|
||||
// Process all spawned NetworkObjects for this network session
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
|
||||
|
||||
|
||||
for (int i = 0; i < newObjectsCount; i++)
|
||||
{
|
||||
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
|
||||
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
|
||||
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
|
||||
InternalBuffer.ReadValueSafe(out int handle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
|
||||
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
|
||||
|
||||
var sceneObject = new NetworkObject.SceneObject();
|
||||
@@ -707,6 +733,73 @@ namespace Unity.Netcode
|
||||
m_NetworkObjectsSync.Add(spawnedNetworkObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Process all de-spawned in-scene NetworkObjects for this network session
|
||||
m_DespawnedInSceneObjects.Clear();
|
||||
InternalBuffer.ReadValueSafe(out int despawnedObjectsCount);
|
||||
var sceneCache = new Dictionary<int, Dictionary<uint, NetworkObject>>();
|
||||
|
||||
for (int i = 0; i < despawnedObjectsCount; i++)
|
||||
{
|
||||
// We just need to get the scene
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
|
||||
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
|
||||
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
|
||||
if (!sceneCache.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle))
|
||||
{
|
||||
var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle];
|
||||
if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle))
|
||||
{
|
||||
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
|
||||
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) =>
|
||||
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
|
||||
|
||||
foreach (var inSceneObject in inSceneNetworkObjects)
|
||||
{
|
||||
sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject);
|
||||
}
|
||||
// Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time
|
||||
sceneCache.Add(networkSceneHandle, sceneRelativeNetworkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative local scene handle {localSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) cannot find its relative NetworkSceneHandle {networkSceneHandle}!");
|
||||
}
|
||||
}
|
||||
else // Use the cached NetworkObjects if they exist
|
||||
{
|
||||
sceneRelativeNetworkObjects = sceneCache[networkSceneHandle];
|
||||
}
|
||||
|
||||
// Now find the in-scene NetworkObject with the current GlobalObjectIdHash we are looking for
|
||||
if (sceneRelativeNetworkObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
// Since this is a NetworkObject that was never spawned, we just need to send a notification
|
||||
// out that it was despawned so users can make adjustments
|
||||
sceneRelativeNetworkObjects[globalObjectIdHash].InvokeBehaviourNetworkDespawn();
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle()))
|
||||
{
|
||||
m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(sceneRelativeNetworkObjects[globalObjectIdHash].GetSceneOriginHandle(), sceneRelativeNetworkObjects[globalObjectIdHash]);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError($"In-Scene NetworkObject GlobalObjectIdHash ({globalObjectIdHash}) could not be found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -61,9 +61,9 @@ namespace Unity.Netcode
|
||||
internal List<ulong> DoneClients { get; } = new List<ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkTime at the moment the scene switch was initiated by the server.
|
||||
/// The local time when the scene event was "roughly started"
|
||||
/// </summary>
|
||||
internal NetworkTime TimeAtInitiation { get; }
|
||||
internal float TimeAtInitiation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for when the switch scene progress is completed. Either by all clients done loading the scene or by time out.
|
||||
@@ -105,22 +105,40 @@ namespace Unity.Netcode
|
||||
|
||||
internal LoadSceneMode LoadSceneMode;
|
||||
|
||||
internal List<ulong> ClientsThatStartedSceneEvent;
|
||||
|
||||
internal SceneEventProgress(NetworkManager networkManager, SceneEventProgressStatus status = SceneEventProgressStatus.Started)
|
||||
{
|
||||
if (status == SceneEventProgressStatus.Started)
|
||||
{
|
||||
// Track the clients that were connected when we started this event
|
||||
ClientsThatStartedSceneEvent = new List<ulong>(networkManager.ConnectedClientsIds);
|
||||
m_NetworkManager = networkManager;
|
||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||
TimeAtInitiation = networkManager.LocalTime;
|
||||
TimeAtInitiation = Time.realtimeSinceStartup;
|
||||
}
|
||||
Status = status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that checks to see if the scene event is complete every network tick period.
|
||||
/// This will handle completing the scene event when one or more client(s) disconnect(s)
|
||||
/// during a scene event and if it does not complete within the scene loading time out period
|
||||
/// it will time out the scene event.
|
||||
/// </summary>
|
||||
internal IEnumerator TimeOutSceneEventProgress()
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(m_NetworkManager.NetworkConfig.LoadSceneTimeOut);
|
||||
TimedOut = true;
|
||||
CheckCompletion();
|
||||
var waitForNetworkTick = new WaitForSeconds(1.0f / m_NetworkManager.NetworkConfig.TickRate);
|
||||
while (!TimedOut && !IsCompleted)
|
||||
{
|
||||
yield return waitForNetworkTick;
|
||||
|
||||
CheckCompletion();
|
||||
if (!IsCompleted)
|
||||
{
|
||||
TimedOut = TimeAtInitiation - Time.realtimeSinceStartup >= m_NetworkManager.NetworkConfig.LoadSceneTimeOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddClientAsDone(ulong clientId)
|
||||
@@ -141,19 +159,49 @@ namespace Unity.Netcode
|
||||
m_SceneLoadOperation.completed += operation => CheckCompletion();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called only on the server-side during integration test (NetcodeIntegrationTest specific)
|
||||
/// scene loading and unloading.
|
||||
///
|
||||
/// Note: During integration testing we must queue all scene loading and unloading requests for
|
||||
/// both the server and all clients so they can be processed in a FIFO/linear fashion to avoid
|
||||
/// conflicts when the <see cref="SceneManager.sceneLoaded"/> and <see cref="SceneManager.sceneUnloaded"/>
|
||||
/// events are triggered. The Completed action simulates the <see cref="AsyncOperation.completed"/> event.
|
||||
/// (See: Unity.Netcode.TestHelpers.Runtime.IntegrationTestSceneHandler)
|
||||
/// </summary>
|
||||
internal void SetSceneLoadOperation(ISceneManagerHandler.SceneEventAction sceneEventAction)
|
||||
{
|
||||
sceneEventAction.Completed = SetComplete;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the SceneEventProgress
|
||||
/// </summary>
|
||||
internal void SetComplete()
|
||||
{
|
||||
IsCompleted = true;
|
||||
AreAllClientsDoneLoading = true;
|
||||
|
||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||
{
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
}
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
|
||||
internal void CheckCompletion()
|
||||
{
|
||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && m_SceneLoadOperation.isDone) || (!IsCompleted && TimedOut))
|
||||
try
|
||||
{
|
||||
IsCompleted = true;
|
||||
AreAllClientsDoneLoading = true;
|
||||
|
||||
// If OnComplete is not registered or it is and returns true then remove this from the progress tracking
|
||||
if (OnComplete == null || (OnComplete != null && OnComplete.Invoke(this)))
|
||||
if ((!IsCompleted && DoneClients.Count == m_NetworkManager.ConnectedClientsList.Count && (m_SceneLoadOperation == null || m_SceneLoadOperation.isDone)) || (!IsCompleted && TimedOut))
|
||||
{
|
||||
m_NetworkManager.SceneManager.SceneEventProgressTracking.Remove(Guid);
|
||||
SetComplete();
|
||||
}
|
||||
m_NetworkManager.StopCoroutine(m_TimeOutCoroutine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to count the number of bytes or bits needed to serialize a value.
|
||||
/// </summary>
|
||||
public static class BitCounter
|
||||
{
|
||||
// Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest)
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Unity.Netcode
|
||||
|
||||
private const int k_BitsPerByte = 8;
|
||||
|
||||
private int BytePosition => m_BitPosition >> 3;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
|
||||
/// </summary>
|
||||
@@ -98,11 +100,6 @@ namespace Unity.Netcode
|
||||
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
|
||||
}
|
||||
|
||||
if (bitCount < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
|
||||
}
|
||||
|
||||
int checkPos = (int)(m_BitPosition + bitCount);
|
||||
if (checkPos > m_AllowedBitwiseReadMark)
|
||||
{
|
||||
@@ -165,7 +162,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
|
||||
int offset = m_BitPosition & 7;
|
||||
int pos = m_BitPosition >> 3;
|
||||
int pos = BytePosition;
|
||||
bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
|
||||
++m_BitPosition;
|
||||
}
|
||||
@@ -175,7 +172,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
var val = new T();
|
||||
byte* ptr = ((byte*)&val) + offsetBytes;
|
||||
byte* bufferPointer = m_BufferPointer + m_Position;
|
||||
byte* bufferPointer = m_BufferPointer + BytePosition;
|
||||
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
|
||||
|
||||
m_BitPosition += bytesToRead * k_BitsPerByte;
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Unity.Netcode
|
||||
get => (m_BitPosition & 7) == 0;
|
||||
}
|
||||
|
||||
private int BytePosition => m_BitPosition >> 3;
|
||||
|
||||
internal unsafe BitWriter(FastBufferWriter writer)
|
||||
{
|
||||
m_Writer = writer;
|
||||
@@ -181,7 +183,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
|
||||
int offset = m_BitPosition & 7;
|
||||
int pos = m_BitPosition >> 3;
|
||||
int pos = BytePosition;
|
||||
++m_BitPosition;
|
||||
m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
|
||||
}
|
||||
@@ -190,7 +192,7 @@ namespace Unity.Netcode
|
||||
private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
|
||||
{
|
||||
byte* ptr = ((byte*)&value) + offsetBytes;
|
||||
byte* bufferPointer = m_BufferPointer + m_Position;
|
||||
byte* bufferPointer = m_BufferPointer + BytePosition;
|
||||
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
|
||||
|
||||
m_BitPosition += bytesToWrite * k_BitsPerByte;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
|
||||
///
|
||||
///
|
||||
/// Implemented as a ref struct for two reasons:
|
||||
/// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
|
||||
/// 2. The BufferSerializer must always be passed by reference and can't be copied
|
||||
///
|
||||
/// Ref structs help enforce both of those rules: they can't out live the stack context in which they were
|
||||
/// Ref structs help enforce both of those rules: they can't ref live the stack context in which they were
|
||||
/// created, and they're always passed by reference no matter what.
|
||||
///
|
||||
/// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't.
|
||||
@@ -58,168 +62,526 @@ namespace Unity.Netcode
|
||||
return m_Implementation.GetFastBufferWriter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an INetworkSerializable
|
||||
///
|
||||
/// Throws OverflowException if the end of the buffer has been reached.
|
||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
||||
{
|
||||
m_Implementation.SerializeNetworkSerializable(ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a string.
|
||||
///
|
||||
/// Note: Will ALWAYS allocate a new string when reading.
|
||||
///
|
||||
/// Throws OverflowException if the end of the buffer has been reached.
|
||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
||||
/// Read or write a string
|
||||
/// </summary>
|
||||
/// <param name="s">Value to serialize</param>
|
||||
/// <param name="oneByteChars">
|
||||
/// If true, will truncate each char to one byte.
|
||||
/// This is slower than two-byte chars, but uses less bandwidth.
|
||||
/// </param>
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Implementation.SerializeValue(ref s, oneByteChars);
|
||||
}
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValue(ref s, oneByteChars);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array value.
|
||||
///
|
||||
/// Note: Will ALWAYS allocate a new array when reading.
|
||||
/// If you have a statically-sized array that you know is large enough, it's recommended to
|
||||
/// serialize the size yourself and iterate serializing array members.
|
||||
///
|
||||
/// (This is because C# doesn't allow setting an array's length value, so deserializing
|
||||
/// into an existing array of larger size would result in an array that doesn't have as many values
|
||||
/// as its Length indicates it should.)
|
||||
///
|
||||
/// Throws OverflowException if the end of the buffer has been reached.
|
||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
||||
/// Read or write a single byte
|
||||
/// </summary>
|
||||
/// <param name="array">Value to serialize</param>
|
||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValue(ref array);
|
||||
}
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref byte value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a single byte
|
||||
///
|
||||
/// Throws OverflowException if the end of the buffer has been reached.
|
||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
||||
/// Read or write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValue(ref byte value)
|
||||
{
|
||||
m_Implementation.SerializeValue(ref value);
|
||||
}
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an unmanaged type. Supports basic value types as well as structs.
|
||||
/// The provided type will be copied to/from the buffer as it exists in memory.
|
||||
///
|
||||
/// Throws OverflowException if the end of the buffer has been reached.
|
||||
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
|
||||
/// Read or write an array of primitive values (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValue(ref value);
|
||||
}
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Allows faster serialization by batching bounds checking.
|
||||
/// When you know you will be writing multiple fields back-to-back and you know the total size,
|
||||
/// you can call PreCheck() once on the total size, and then follow it with calls to
|
||||
/// SerializeValuePreChecked() for faster serialization. Write buffers will grow during PreCheck()
|
||||
/// if needed.
|
||||
///
|
||||
/// PreChecked serialization operations will throw OverflowException in editor and development builds if you
|
||||
/// go past the point you've marked using PreCheck(). In release builds, OverflowException will not be thrown
|
||||
/// for performance reasons, since the point of using PreCheck is to avoid bounds checking in the following
|
||||
/// operations in release builds.
|
||||
///
|
||||
/// To get the correct size to check for, use FastBufferWriter.GetWriteSize(value) or
|
||||
/// FastBufferWriter.GetWriteSize<type>()
|
||||
/// Read or write an enum value
|
||||
/// </summary>
|
||||
/// <param name="amount">Number of bytes you plan to read or write</param>
|
||||
/// <returns>True if the read/write can proceed, false otherwise.</returns>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of enum values
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct value implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct values implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct or class value implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct or class values implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector2 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector2Int value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector3Int value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector4 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector4 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Quaternion value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Quaternion values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Quaternion[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Color value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Color[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color32 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Color32 value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color32 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Color32[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Ray value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Ray[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray2D value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray2D values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpy'ing the whole thing even if
|
||||
// most of it isn't used.
|
||||
/// <summary>
|
||||
/// Read or write a FixedString value
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of FixedStrings</param>
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValue(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a NetworkSerializable value.
|
||||
/// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Performs an advance check to ensure space is available to read/write one or more values.
|
||||
/// This provides a performance benefit for serializing multiple values using the
|
||||
/// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be
|
||||
/// noticeable if serializing a very large number of items.
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <returns></returns>
|
||||
public bool PreCheck(int amount)
|
||||
{
|
||||
return m_Implementation.PreCheck(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a string.
|
||||
///
|
||||
/// Note: Will ALWAYS allocate a new string when reading.
|
||||
///
|
||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
||||
/// Serialize a string, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="s">Value to serialize</param>
|
||||
/// <param name="oneByteChars">
|
||||
/// If true, will truncate each char to one byte.
|
||||
/// This is slower than two-byte chars, but uses less bandwidth.
|
||||
/// </param>
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
|
||||
}
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array value.
|
||||
///
|
||||
/// Note: Will ALWAYS allocate a new array when reading.
|
||||
/// If you have a statically-sized array that you know is large enough, it's recommended to
|
||||
/// serialize the size yourself and iterate serializing array members.
|
||||
///
|
||||
/// (This is because C# doesn't allow setting an array's length value, so deserializing
|
||||
/// into an existing array of larger size would result in an array that doesn't have as many values
|
||||
/// as its Length indicates it should.)
|
||||
///
|
||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
||||
/// Serialize a byte, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="array">Value to serialize</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValuePreChecked(ref array);
|
||||
}
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref byte value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a single byte
|
||||
///
|
||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
||||
/// Serialize a primitive, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValuePreChecked(ref byte value)
|
||||
{
|
||||
m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an unmanaged type. Supports basic value types as well as structs.
|
||||
/// The provided type will be copied to/from the buffer as it exists in memory.
|
||||
///
|
||||
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
|
||||
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
|
||||
/// serialization operations in one function call instead of having to do bounds checking on every call.
|
||||
/// Serialize an array of primitives, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
/// <typeparam name="T">The network serializable types in an array</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of primitives</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an enum, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of enums</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of enums, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable types in an array</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of enums</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a struct, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of structs</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of structs, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable types in an array</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution of structs</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4Array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color32 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a FixedString, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution for fixed strings</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -24,54 +26,77 @@ namespace Unity.Netcode
|
||||
throw new InvalidOperationException("Cannot retrieve a FastBufferWriter from a serializer where IsWriter = false");
|
||||
}
|
||||
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Reader.ReadValueSafe(out s, oneByteChars);
|
||||
}
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Reader.ReadValueSafe(out s, oneByteChars);
|
||||
public void SerializeValue(ref byte value) => m_Reader.ReadByteSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Reader.ReadValue(out value);
|
||||
|
||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Reader.ReadValueSafe(out array);
|
||||
}
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValueSafe(out value);
|
||||
|
||||
public void SerializeValue(ref byte value)
|
||||
{
|
||||
m_Reader.ReadByteSafe(out value);
|
||||
}
|
||||
public void SerializeValue(ref Vector2 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Quaternion[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Color value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Color[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Color32 value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Color32[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Ray value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Ray[] value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Ray2D value) => m_Reader.ReadValueSafe(out value);
|
||||
public void SerializeValue(ref Ray2D[] value) => m_Reader.ReadValueSafe(out value);
|
||||
|
||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
||||
{
|
||||
m_Reader.ReadNetworkSerializable(out value);
|
||||
}
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Reader.ReadNetworkSerializable(out value);
|
||||
|
||||
public bool PreCheck(int amount)
|
||||
{
|
||||
return m_Reader.TryBeginRead(amount);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Reader.ReadValue(out s, oneByteChars);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Reader.ReadValue(out array);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked(ref byte value)
|
||||
{
|
||||
m_Reader.ReadValue(out value);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Reader.ReadValue(out value);
|
||||
}
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Reader.ReadValue(out s, oneByteChars);
|
||||
public void SerializeValuePreChecked(ref byte value) => m_Reader.ReadByte(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Color value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Color[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Color32 value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Color32[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Ray value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Ray[] value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Ray2D value) => m_Reader.ReadValue(out value);
|
||||
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Reader.ReadValue(out value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -24,25 +26,39 @@ namespace Unity.Netcode
|
||||
return m_Writer;
|
||||
}
|
||||
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Writer.WriteValueSafe(s, oneByteChars);
|
||||
}
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Writer.WriteValueSafe(s, oneByteChars);
|
||||
public void SerializeValue(ref byte value) => m_Writer.WriteByteSafe(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Writer.WriteValue(value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValueSafe(value);
|
||||
|
||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Writer.WriteValueSafe(array);
|
||||
}
|
||||
|
||||
public void SerializeValue(ref byte value)
|
||||
{
|
||||
m_Writer.WriteByteSafe(value);
|
||||
}
|
||||
|
||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Writer.WriteValueSafe(value);
|
||||
}
|
||||
public void SerializeValue(ref Vector2 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2Int value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector2Int[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3Int value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector3Int[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Quaternion[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Color value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Color[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Color32 value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Color32[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Ray value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Ray[] value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Ray2D value) => m_Writer.WriteValueSafe(value);
|
||||
public void SerializeValue(ref Ray2D[] value) => m_Writer.WriteValueSafe(value);
|
||||
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
|
||||
{
|
||||
@@ -54,24 +70,37 @@ namespace Unity.Netcode
|
||||
return m_Writer.TryBeginWrite(amount);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Writer.WriteValue(s, oneByteChars);
|
||||
}
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Writer.WriteValue(s, oneByteChars);
|
||||
public void SerializeValuePreChecked(ref byte value) => m_Writer.WriteByte(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => m_Writer.WriteValue(value);
|
||||
|
||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Writer.WriteValue(array);
|
||||
}
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes => m_Writer.WriteValue(value);
|
||||
|
||||
public void SerializeValuePreChecked(ref byte value)
|
||||
{
|
||||
m_Writer.WriteByte(value);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Writer.WriteValue(value);
|
||||
}
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector2Int[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector3Int[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Color value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Color[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Color32 value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Color32[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Ray value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Ray[] value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Ray2D value) => m_Writer.WriteValue(value);
|
||||
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Writer.WriteValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for packing values in serialization.
|
||||
/// <seealso cref="ByteUnpacker"/> to unpack packed values.
|
||||
/// </summary>
|
||||
public static class BytePacker
|
||||
{
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValuePacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
|
||||
#else
|
||||
@@ -277,10 +278,56 @@ namespace Unity.Netcode
|
||||
|
||||
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
|
||||
#else
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0)
|
||||
/// </summary>
|
||||
public const ushort BitPackedUshortMax = (1 << 15) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked short
|
||||
/// </summary>
|
||||
public const short BitPackedShortMax = (1 << 14) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked ushort
|
||||
/// </summary>
|
||||
public const short BitPackedShortMin = -(1 << 14);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0)
|
||||
/// </summary>
|
||||
public const uint BitPackedUintMax = (1 << 30) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked int
|
||||
/// </summary>
|
||||
public const int BitPackedIntMax = (1 << 29) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked int
|
||||
/// </summary>
|
||||
public const int BitPackedIntMin = -(1 << 29);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0)
|
||||
/// </summary>
|
||||
public const ulong BitPackedULongMax = (1L << 61) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum serializable value for a BitPacked long
|
||||
/// </summary>
|
||||
public const long BitPackedLongMax = (1L << 60) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum serializable value size for a BitPacked long
|
||||
/// </summary>
|
||||
public const long BitPackedLongMin = -(1L << 60);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
|
||||
/// The first bit indicates whether the value is 1 byte or 2.
|
||||
@@ -307,7 +354,7 @@ namespace Unity.Netcode
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= 0b1000_0000_0000_0000)
|
||||
if (value >= BitPackedUshortMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
|
||||
}
|
||||
@@ -356,7 +403,7 @@ namespace Unity.Netcode
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= 0b0100_0000_0000_0000_0000_0000_0000_0000)
|
||||
if (value > BitPackedUintMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked uints must be <= 30 bits");
|
||||
}
|
||||
@@ -396,7 +443,7 @@ namespace Unity.Netcode
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000)
|
||||
if (value > BitPackedULongMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
|
||||
}
|
||||
|
||||
@@ -4,14 +4,26 @@ using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Byte Unpacking Helper Class
|
||||
/// Use this class to unpack values during deserialization for values that were packed.
|
||||
/// <seealso cref="BytePacker"/> to pack unpacked values
|
||||
/// </summary>
|
||||
public static class ByteUnpacker
|
||||
{
|
||||
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValuePacked<T>(FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value);
|
||||
#else
|
||||
/// <summary>
|
||||
/// Read a packed enum value
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to read from</param>
|
||||
/// <param name="value">The value that's read</param>
|
||||
/// <typeparam name="TEnum">Type of enum to read</typeparam>
|
||||
/// <exception cref="InvalidOperationException">Throws InvalidOperationException if an enum somehow ends up not being the size of a byte, short, int, or long (which should be impossible)</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe void ReadValuePacked<TEnum>(FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum
|
||||
{
|
||||
@@ -302,7 +314,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueBitPacked<T>(FastBufferReader reader, T value) where T: unmanaged => reader.ReadValueSafe(out value);
|
||||
#else
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,16 @@ using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Optimized class used for writing values into a byte stream
|
||||
/// <seealso cref="FastBufferReader"/>
|
||||
/// <seealso cref="BytePacker"/>
|
||||
/// <seealso cref="ByteUnpacker"/>
|
||||
/// </summary>
|
||||
public struct FastBufferWriter : IDisposable
|
||||
{
|
||||
internal struct WriterHandle
|
||||
@@ -22,7 +29,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
internal readonly unsafe WriterHandle* Handle;
|
||||
internal unsafe WriterHandle* Handle;
|
||||
|
||||
private static byte[] s_ByteArrayCache = new byte[65535];
|
||||
|
||||
@@ -62,6 +69,11 @@ namespace Unity.Netcode
|
||||
get => Handle->Position > Handle->Length ? Handle->Position : Handle->Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the writer has been initialized and a handle allocated.
|
||||
/// </summary>
|
||||
public unsafe bool IsInitialized => Handle != null;
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void CommitBitwiseWrites(int amount)
|
||||
@@ -102,7 +114,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated buffer
|
||||
/// <see cref="IDisposable"/> implementation that frees the allocated buffer
|
||||
/// </summary>
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
@@ -111,6 +123,7 @@ namespace Unity.Netcode
|
||||
UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator);
|
||||
}
|
||||
UnsafeUtility.Free(Handle, Handle->Allocator);
|
||||
Handle = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,7 +220,7 @@ namespace Unity.Netcode
|
||||
/// When you know you will be writing multiple fields back-to-back and you know the total size,
|
||||
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to
|
||||
/// WriteValue() instead of WriteValueSafe() for faster serialization.
|
||||
///
|
||||
///
|
||||
/// Unsafe write operations will throw OverflowException in editor and development builds if you
|
||||
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
|
||||
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
|
||||
@@ -253,14 +266,15 @@ namespace Unity.Netcode
|
||||
/// When you know you will be writing multiple fields back-to-back and you know the total size,
|
||||
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to
|
||||
/// WriteValue() instead of WriteValueSafe() for faster serialization.
|
||||
///
|
||||
///
|
||||
/// Unsafe write operations will throw OverflowException in editor and development builds if you
|
||||
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
|
||||
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
|
||||
/// operations in release builds. Instead, attempting to write past the marked position in release builds
|
||||
/// will write to random memory and cause undefined behavior, likely including instability and crashes.
|
||||
/// </summary>
|
||||
/// <param name="value">The value you want to write</param>
|
||||
/// <typeparam name="T">The value type to write</typeparam>
|
||||
/// <param name="value">The value of the type `T` you want to write</param>
|
||||
/// <returns>True if the write is allowed, false otherwise</returns>
|
||||
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -522,60 +536,6 @@ namespace Unity.Netcode
|
||||
return sizeof(int) + sizeInBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unmanaged array
|
||||
/// </summary>
|
||||
/// <param name="array">The array to write</param>
|
||||
/// <param name="count">The amount of elements to write</param>
|
||||
/// <param name="offset">Where in the array to start</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValue<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
|
||||
{
|
||||
int sizeInTs = count != -1 ? count : array.Length - offset;
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
WriteValue(sizeInTs);
|
||||
fixed (T* native = array)
|
||||
{
|
||||
byte* bytes = (byte*)(native + offset);
|
||||
WriteBytes(bytes, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unmanaged array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to write</param>
|
||||
/// <param name="count">The amount of elements to write</param>
|
||||
/// <param name="offset">Where in the array to start</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValueSafe<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (Handle->InBitwiseContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
|
||||
}
|
||||
#endif
|
||||
|
||||
int sizeInTs = count != -1 ? count : array.Length - offset;
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
|
||||
if (!TryBeginWriteInternal(sizeInBytes + sizeof(int)))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
WriteValue(sizeInTs);
|
||||
fixed (T* native = array)
|
||||
{
|
||||
byte* bytes = (byte*)(native + offset);
|
||||
WriteBytes(bytes, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a partial value. The specified number of bytes is written from the value and the rest is ignored.
|
||||
/// </summary>
|
||||
@@ -763,15 +723,30 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the size required to write an unmanaged value
|
||||
/// Get the write size for any general unmanaged value
|
||||
/// The ForStructs value here makes this the lowest-priority overload so other versions
|
||||
/// will be prioritized over this if they match
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="unused"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetWriteSize<T>(in T value, ForStructs unused = default) where T : unmanaged
|
||||
{
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the write size for a FixedString
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe int GetWriteSize<T>(in T value) where T : unmanaged
|
||||
public static int GetWriteSize<T>(in T value)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
return sizeof(T);
|
||||
return value.Length + sizeof(int);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -784,68 +759,681 @@ namespace Unity.Netcode
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
|
||||
/// It will be copied into the buffer exactly as it exists in memory.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to copy</param>
|
||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValue<T>(in T value) where T : unmanaged
|
||||
internal unsafe void WriteUnmanaged<T>(in T value) where T : unmanaged
|
||||
{
|
||||
int len = sizeof(T);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (Handle->InBitwiseContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
|
||||
}
|
||||
if (Handle->Position + len > Handle->AllowedWriteMark)
|
||||
{
|
||||
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
|
||||
}
|
||||
#endif
|
||||
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
|
||||
byte* bytes = (byte*)ptr;
|
||||
WriteBytes(bytes, sizeof(T));
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void WriteUnmanagedSafe<T>(in T value) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
byte* bytes = (byte*)ptr;
|
||||
WriteBytesSafe(bytes, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void WriteUnmanaged<T>(T[] value) where T : unmanaged
|
||||
{
|
||||
WriteUnmanaged(value.Length);
|
||||
fixed (T* ptr = value)
|
||||
{
|
||||
byte* bytes = (byte*)ptr;
|
||||
WriteBytes(bytes, sizeof(T) * value.Length);
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void WriteUnmanagedSafe<T>(T[] value) where T : unmanaged
|
||||
{
|
||||
WriteUnmanagedSafe(value.Length);
|
||||
fixed (T* ptr = value)
|
||||
{
|
||||
byte* bytes = (byte*)ptr;
|
||||
WriteBytesSafe(bytes, sizeof(T) * value.Length);
|
||||
}
|
||||
Handle->Position += len;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
|
||||
/// It will be copied into the buffer exactly as it exists in memory.
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForPrimitives
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForEnums
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForStructs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForNetworkSerializable
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This empty struct exists to allow overloading WriteValue based on generic constraints.
|
||||
/// At the bytecode level, constraints aren't included in the method signature, so if multiple
|
||||
/// methods exist with the same signature, it causes a compile error because they would end up
|
||||
/// being emitted as the same method, even if the constraints are different.
|
||||
/// Adding an empty struct with a default value gives them different signatures in the bytecode,
|
||||
/// which then allows the compiler to do overload resolution based on the generic constraints
|
||||
/// without the user having to pass the struct in themselves.
|
||||
/// </summary>
|
||||
public struct ForFixedStrings
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable value
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to copy</param>
|
||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValueSafe<T>(in T value) where T : unmanaged
|
||||
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a NetworkSerializable array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a struct array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value array (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum array
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum value
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write an enum array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector2[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector3 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2Int value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector2Int[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector3Int value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int array
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3Int[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector4 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector4[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Quaternion value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Quaternion[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Color value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Color[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Color32 value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32 array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Color32[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Ray value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray[] value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Ray2D value) => WriteUnmanaged(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D array
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray2D[] value) => WriteUnmanaged(value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2Int value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector2Int array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector2Int[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector3Int value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector3Int array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3Int[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Vector4 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Quaternion array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Collor array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Color32 array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value);
|
||||
|
||||
/// <summary>
|
||||
/// Write a Ray2D array
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the values to write</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value);
|
||||
|
||||
// There are many FixedString types, but all of them share the interfaces INativeList<bool> and IUTF8Bytes.
|
||||
// INativeList<bool> provides the Length property
|
||||
// IUTF8Bytes provides GetUnsafePtr()
|
||||
// Those two are necessary to serialize FixedStrings efficiently
|
||||
// - otherwise we'd just be memcpying the whole thing even if
|
||||
// most of it isn't used.
|
||||
|
||||
/// <summary>
|
||||
/// Write a FixedString value. Writes only the part of the string that's actually used.
|
||||
/// When calling TryBeginWrite, ensure you calculate the write size correctly (preferably by calling
|
||||
/// FastBufferWriter.GetWriteSize())
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValue<T>(in T value, ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
int len = sizeof(T);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (Handle->InBitwiseContext)
|
||||
WriteUnmanaged(value.Length);
|
||||
// This avoids a copy on the string, which could be costly for FixedString4096Bytes
|
||||
// Otherwise, GetUnsafePtr() is an impure function call and will result in a copy
|
||||
// for `in` parameters.
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
|
||||
WriteBytes(ptr->GetUnsafePtr(), value.Length);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!TryBeginWriteInternal(len))
|
||||
|
||||
/// <summary>
|
||||
/// Write a FixedString value. Writes only the part of the string that's actually used.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple writes at once by calling TryBeginWrite.
|
||||
/// </summary>
|
||||
/// <param name="value">the value to write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||
{
|
||||
if (!TryBeginWriteInternal(sizeof(int) + value.Length))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
|
||||
}
|
||||
Handle->Position += len;
|
||||
WriteValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs
Normal file
69
Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a wrapper that adds `INetworkSerializeByMemcpy` support to existing structs that the developer
|
||||
/// doesn't have the ability to modify (for example, external structs like `Guid`).
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public struct ForceNetworkSerializeByMemcpy<T> : INetworkSerializeByMemcpy, IEquatable<ForceNetworkSerializeByMemcpy<T>> where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The wrapped value
|
||||
/// </summary>
|
||||
public T Value;
|
||||
|
||||
/// <summary>
|
||||
/// The default constructor for <see cref="ForceNetworkSerializeByMemcpy{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="value">sets the initial value of type `T`</param>
|
||||
public ForceNetworkSerializeByMemcpy(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert implicitly from the ForceNetworkSerializeByMemcpy wrapper to the underlying value
|
||||
/// </summary>
|
||||
/// <param name="container">The wrapper</param>
|
||||
/// <returns>The underlying value</returns>
|
||||
public static implicit operator T(ForceNetworkSerializeByMemcpy<T> container) => container.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Convert implicitly from a T value to a ForceNetworkSerializeByMemcpy wrapper
|
||||
/// </summary>
|
||||
/// <param name="underlyingValue">the value</param>
|
||||
/// <returns>a new wrapper</returns>
|
||||
public static implicit operator ForceNetworkSerializeByMemcpy<T>(T underlyingValue) => new ForceNetworkSerializeByMemcpy<T> { Value = underlyingValue };
|
||||
|
||||
/// <summary>
|
||||
/// Check if wrapped values are equal
|
||||
/// </summary>
|
||||
/// <param name="other">Other wrapper</param>
|
||||
/// <returns>true if equal</returns>
|
||||
public bool Equals(ForceNetworkSerializeByMemcpy<T> other)
|
||||
{
|
||||
return Value.Equals(other.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this value is equal to a boxed object value
|
||||
/// </summary>
|
||||
/// <param name="obj">The boxed value to check against</param>
|
||||
/// <returns>true if equal</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ForceNetworkSerializeByMemcpy<T> other && Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the wrapped value's hash code
|
||||
/// </summary>
|
||||
/// <returns>Wrapped value's hash code</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d56016695cd44430a345671f7d56b18e
|
||||
timeCreated: 1647635768
|
||||
15
Runtime/Serialization/INetworkSerializeByMemcpy.cs
Normal file
15
Runtime/Serialization/INetworkSerializeByMemcpy.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is a "tag" that can be applied to a struct to mark that struct as being serializable
|
||||
/// by memcpy. It's up to the developer of the struct to analyze the struct's contents and ensure it
|
||||
/// is actually serializable by memcpy. This requires all of the members of the struct to be
|
||||
/// `unmanaged` Plain-Old-Data values - if your struct contains a pointer (or a type that contains a pointer,
|
||||
/// like `NativeList<T>`), it should be serialized via `INetworkSerializable` or via
|
||||
/// `FastBufferReader`/`FastBufferWriter` extension methods.
|
||||
/// </summary>
|
||||
public interface INetworkSerializeByMemcpy
|
||||
{
|
||||
}
|
||||
}
|
||||
3
Runtime/Serialization/INetworkSerializeByMemcpy.cs.meta
Normal file
3
Runtime/Serialization/INetworkSerializeByMemcpy.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11b763f46b18465cbffb1972d737a83e
|
||||
timeCreated: 1647635592
|
||||
@@ -1,25 +1,539 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an implementation of one side of a two-way serializer
|
||||
/// </summary>
|
||||
public interface IReaderWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Check whether this implementation is a "reader" - if it's been constructed to deserialize data
|
||||
/// </summary>
|
||||
bool IsReader { get; }
|
||||
/// <summary>
|
||||
/// Check whether this implementation is a "writer" - if it's been constructed to serialize data
|
||||
/// </summary>
|
||||
bool IsWriter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the underlying FastBufferReader struct.
|
||||
/// Only valid when IsReader == true
|
||||
/// </summary>
|
||||
/// <returns>underlying FastBufferReader</returns>
|
||||
FastBufferReader GetFastBufferReader();
|
||||
/// <summary>
|
||||
/// Get the underlying FastBufferWriter struct.
|
||||
/// Only valid when IsWriter == true
|
||||
/// </summary>
|
||||
/// <returns>underlying FastBufferWriter</returns>
|
||||
FastBufferWriter GetFastBufferWriter();
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a string
|
||||
/// </summary>
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
void SerializeValue(ref string s, bool oneByteChars = false);
|
||||
void SerializeValue<T>(ref T[] array) where T : unmanaged;
|
||||
void SerializeValue(ref byte value);
|
||||
void SerializeValue<T>(ref T value) where T : unmanaged;
|
||||
|
||||
// Has to have a different name to avoid conflicting with "where T: unmananged"
|
||||
/// <summary>
|
||||
/// Read or write a single byte
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a primitive value (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of primitive values (int, bool, etc)
|
||||
/// Accepts any value that implements the given interfaces, but is not guaranteed to work correctly
|
||||
/// on values that are not primitives.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an enum value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of enum values
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct value implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct values implementing ISerializeByMemcpy
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a struct or class value implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of struct or class values implementing INetworkSerializable
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a FixedString value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter used for enabling overload resolution based on generic constraints</param>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector2 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector2[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector3 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector3[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector2Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector2Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector2Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector2Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector3Int value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector3Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector3Int values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector3Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Vector4 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Vector4 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Vector4 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Vector4[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Quaternion value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Quaternion value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Quaternion values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Quaternion[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Color value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Color[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Color32 value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Color32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Color32 values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Color32[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Ray value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Ray[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a Ray2D value
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValue(ref Ray2D value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write an array of Ray2D values
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValue(ref Ray2D[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Read or write a NetworkSerializable value.
|
||||
/// SerializeValue() is the preferred method to do this - this is provided for backward compatibility only.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <typeparam name="T">The network serializable type</typeparam>
|
||||
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
|
||||
|
||||
/// <summary>
|
||||
/// Performs an advance check to ensure space is available to read/write one or more values.
|
||||
/// This provides a performance benefit for serializing multiple values using the
|
||||
/// SerializeValuePreChecked methods. But note that the benefit is small and only likely to be
|
||||
/// noticeable if serializing a very large number of items.
|
||||
/// </summary>
|
||||
/// <param name="amount"></param>
|
||||
/// <returns></returns>
|
||||
bool PreCheck(int amount);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a string, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="s">The value to read/write</param>
|
||||
/// <param name="oneByteChars">If true, characters will be limited to one-byte ASCII characters</param>
|
||||
void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
|
||||
void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a byte, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref byte value);
|
||||
void SerializeValuePreChecked<T>(ref T value) where T : unmanaged;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a primitive, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of primitives, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an enum, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of enums, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a struct, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an array of structs, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a FixedString, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type being serialized</typeparam>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
/// <param name="unused">An unused parameter that can be used for enabling overload resolution based on generic constraints</param>
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForFixedStrings unused = default)
|
||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector2Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The values to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector2Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3Int value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector3Int array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector3Int[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector4 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Vector4Array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Vector4[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Quaternion value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Quaternion array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Quaternion[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color32 value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Color32 array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Color32[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray2D value);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a Ray2D array, "pre-checked", which skips buffer checks.
|
||||
/// In debug and editor builds, a check is made to ensure you've called "PreCheck" before
|
||||
/// calling this. In release builds, calling this without calling "PreCheck" may read or write
|
||||
/// past the end of the buffer, which will cause memory corruption and undefined behavior.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read/write</param>
|
||||
void SerializeValuePreChecked(ref Ray2D[] value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Unity.Netcode
|
||||
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
|
||||
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
|
||||
{
|
||||
networkBehaviour = (T)GetInternal(this, null);
|
||||
networkBehaviour = GetInternal(this, null) as T;
|
||||
return networkBehaviour != null;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,18 @@ namespace Unity.Netcode
|
||||
serializer.SerializeValue(ref m_NetworkBehaviourId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkBehaviourReference"/> to <see cref="NetworkBehaviour"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkBehaviourRef">The <see cref="NetworkBehaviourReference"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkBehaviour"/> this class is holding a reference to</returns>
|
||||
public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkBehaviour"/> to <see cref="NetworkBehaviourReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkBehaviourReference"/> created from the <see cref="NetworkBehaviour"/> passed in as a parameter</returns>
|
||||
public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,12 +120,32 @@ namespace Unity.Netcode
|
||||
serializer.SerializeValue(ref m_NetworkObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkObjectReference"/> to <see cref="NetworkObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkObject"/> the <see cref="NetworkObjectReference"/> is referencing</returns>
|
||||
public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkObject"/> to <see cref="NetworkObjectReference"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkObject">The <see cref="NetworkObject"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkObjectReference"/> created from the <see cref="NetworkObject"/> parameter</returns>
|
||||
public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="NetworkObjectReference"/> to <see cref="GameObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="networkObjectRef">The <see cref="NetworkObjectReference"/> to convert from.</param>
|
||||
/// <returns>This returns the <see cref="GameObject"/> that the <see cref="NetworkObject"/> is attached to and is referenced by the <see cref="NetworkObjectReference"/> passed in as a parameter</returns>
|
||||
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject;
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert <see cref="GameObject"/> to <see cref="NetworkObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The <see cref="GameObject"/> to convert from.</param>
|
||||
/// <returns>The <see cref="NetworkObjectReference"/> created from the <see cref="GameObject"/> parameter that has a <see cref="NetworkObject"/> component attached to it</returns>
|
||||
public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -21,21 +20,121 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>();
|
||||
|
||||
private struct TriggerData
|
||||
/// <summary>
|
||||
/// Use to get all NetworkObjects owned by a client
|
||||
/// Ownership to Objects Table Format:
|
||||
/// [ClientId][NetworkObjectId][NetworkObject]
|
||||
/// Server: Keeps track of all clients' ownership
|
||||
/// Client: Keeps track of only its ownership
|
||||
/// </summary>
|
||||
public readonly Dictionary<ulong, Dictionary<ulong, NetworkObject>> OwnershipToObjectsTable = new Dictionary<ulong, Dictionary<ulong, NetworkObject>>();
|
||||
|
||||
/// <summary>
|
||||
/// Object to Ownership Table:
|
||||
/// [NetworkObjectId][ClientId]
|
||||
/// Used internally to find the client Id that currently owns
|
||||
/// the NetworkObject
|
||||
/// </summary>
|
||||
private Dictionary<ulong, ulong> m_ObjectToOwnershipTable = new Dictionary<ulong, ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// Used to update a NetworkObject's ownership
|
||||
/// </summary>
|
||||
internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, bool isRemoving = false)
|
||||
{
|
||||
public FastBufferReader Reader;
|
||||
public MessageHeader Header;
|
||||
public ulong SenderId;
|
||||
public float Timestamp;
|
||||
public int SerializedHeaderSize;
|
||||
}
|
||||
private struct TriggerInfo
|
||||
{
|
||||
public float Expiry;
|
||||
public NativeList<TriggerData> TriggerData;
|
||||
var previousOwner = newOwner;
|
||||
|
||||
// Use internal lookup table to see if the NetworkObject has a previous owner
|
||||
if (m_ObjectToOwnershipTable.ContainsKey(networkObject.NetworkObjectId))
|
||||
{
|
||||
// Keep track of the previous owner's ClientId
|
||||
previousOwner = m_ObjectToOwnershipTable[networkObject.NetworkObjectId];
|
||||
|
||||
// We are either despawning (remove) or changing ownership (assign)
|
||||
if (isRemoving)
|
||||
{
|
||||
m_ObjectToOwnershipTable.Remove(networkObject.NetworkObjectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ObjectToOwnershipTable[networkObject.NetworkObjectId] = newOwner;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, just add a new lookup entry
|
||||
m_ObjectToOwnershipTable.Add(networkObject.NetworkObjectId, newOwner);
|
||||
}
|
||||
|
||||
// Check to see if we had a previous owner
|
||||
if (previousOwner != newOwner && OwnershipToObjectsTable.ContainsKey(previousOwner))
|
||||
{
|
||||
// Before updating the previous owner, assure this entry exists
|
||||
if (OwnershipToObjectsTable[previousOwner].ContainsKey(networkObject.NetworkObjectId))
|
||||
{
|
||||
// Remove the previous owner's entry
|
||||
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
|
||||
|
||||
// Server or Host alway invokes the lost ownership notification locally
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
// If we are removing the entry (i.e. despawning or client lost ownership)
|
||||
if (isRemoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Really, as long as UpdateOwnershipTable is invoked when ownership is gained or lost this should never happen
|
||||
throw new Exception($"Client-ID {previousOwner} had a partial {nameof(m_ObjectToOwnershipTable)} entry! Potentially corrupted {nameof(OwnershipToObjectsTable)}?");
|
||||
}
|
||||
}
|
||||
|
||||
// If the owner doesn't have an entry then create one
|
||||
if (!OwnershipToObjectsTable.ContainsKey(newOwner))
|
||||
{
|
||||
OwnershipToObjectsTable.Add(newOwner, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
|
||||
// Sanity check to make sure we don't already have this entry (we shouldn't)
|
||||
if (!OwnershipToObjectsTable[newOwner].ContainsKey(networkObject.NetworkObjectId))
|
||||
{
|
||||
// Add the new ownership entry
|
||||
OwnershipToObjectsTable[newOwner].Add(networkObject.NetworkObjectId, networkObject);
|
||||
|
||||
// Server or Host always invokes the gained ownership notification locally
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
}
|
||||
else if (isRemoving)
|
||||
{
|
||||
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
|
||||
}
|
||||
else if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"Setting ownership twice? Client-ID {previousOwner} already owns NetworkObject ID {networkObject.NetworkObjectId}!");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<ulong, TriggerInfo> m_Triggers = new Dictionary<ulong, TriggerInfo>();
|
||||
/// <summary>
|
||||
/// Returns a list of all NetworkObjects that belong to a client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param>
|
||||
/// <returns>returns the list of <see cref="NetworkObject"/>s owned by the client</returns>
|
||||
public List<NetworkObject> GetClientOwnedObjects(ulong clientId)
|
||||
{
|
||||
if (!OwnershipToObjectsTable.ContainsKey(clientId))
|
||||
{
|
||||
OwnershipToObjectsTable.Add(clientId, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
return OwnershipToObjectsTable[clientId].Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager associated with this SpawnManager.
|
||||
@@ -74,9 +173,11 @@ namespace Unity.Netcode
|
||||
return GetPlayerNetworkObject(NetworkManager.LocalClientId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the player object with a given clientId or null if one does not exist. This is only valid server side.
|
||||
/// </summary>
|
||||
/// <param name="clientId">the client identifier of the player</param>
|
||||
/// <returns>The player object with a given clientId or null if one does not exist</returns>
|
||||
public NetworkObject GetPlayerNetworkObject(ulong clientId)
|
||||
{
|
||||
@@ -93,88 +194,6 @@ namespace Unity.Netcode
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed, or with
|
||||
/// snapshot spawns enabled where the spawn is sent unreliably and not until the end of the frame.
|
||||
///
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
/// </summary>
|
||||
internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!m_Triggers.ContainsKey(networkObjectId))
|
||||
{
|
||||
m_Triggers[networkObjectId] = new TriggerInfo
|
||||
{
|
||||
Expiry = Time.realtimeSinceStartup + 1,
|
||||
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
||||
};
|
||||
}
|
||||
|
||||
m_Triggers[networkObjectId].TriggerData.Add(new TriggerData
|
||||
{
|
||||
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
|
||||
Header = context.Header,
|
||||
Timestamp = context.Timestamp,
|
||||
SenderId = context.SenderId,
|
||||
SerializedHeaderSize = context.SerializedHeaderSize
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
internal unsafe void CleanupStaleTriggers()
|
||||
{
|
||||
ulong* staleKeys = stackalloc ulong[m_Triggers.Count()];
|
||||
int index = 0;
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
if (kvp.Value.Expiry < Time.realtimeSinceStartup)
|
||||
{
|
||||
|
||||
staleKeys[index++] = kvp.Key;
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Deferred messages were received for {nameof(NetworkObject)} #{kvp.Key}, but it did not spawn within 1 second.");
|
||||
}
|
||||
|
||||
foreach (var data in kvp.Value.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
|
||||
kvp.Value.TriggerData.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < index; ++i)
|
||||
{
|
||||
m_Triggers.Remove(staleKeys[i]);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Cleans up any trigger that's existed for more than a second.
|
||||
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
|
||||
/// </summary>
|
||||
internal void CleanupAllTriggers()
|
||||
{
|
||||
foreach (var kvp in m_Triggers)
|
||||
{
|
||||
foreach (var data in kvp.Value.TriggerData)
|
||||
{
|
||||
data.Reader.Dispose();
|
||||
}
|
||||
|
||||
kvp.Value.TriggerData.Dispose();
|
||||
}
|
||||
|
||||
m_Triggers.Clear();
|
||||
}
|
||||
|
||||
internal void RemoveOwnership(NetworkObject networkObject)
|
||||
{
|
||||
if (!NetworkManager.IsServer)
|
||||
@@ -194,37 +213,21 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the connected client entry exists before trying to remove ownership.
|
||||
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
// Server removes the entry and takes over ownership before notifying
|
||||
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
|
||||
|
||||
networkObject.OwnerClientId = NetworkManager.ServerClientId;
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i] == networkObject)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
OwnerClientId = networkObject.OwnerClientId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
||||
|
||||
networkObject.OwnerClientIdInternal = null;
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
OwnerClientId = networkObject.OwnerClientId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
||||
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
|
||||
}
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,25 +268,10 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
{
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i] == networkObject)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
networkClient.OwnedObjects.Add(networkObject);
|
||||
}
|
||||
|
||||
networkObject.OwnerClientId = clientId;
|
||||
|
||||
if (TryGetNetworkClient(clientId, out NetworkClient newNetworkClient))
|
||||
{
|
||||
newNetworkClient.OwnedObjects.Add(networkObject);
|
||||
}
|
||||
// Server adds entries for all client ownership
|
||||
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
@@ -298,10 +286,37 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab))
|
||||
{
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
default:
|
||||
case NetworkPrefabOverride.None:
|
||||
return networkPrefab.Prefab != null;
|
||||
case NetworkPrefabOverride.Hash:
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
return networkPrefab.OverridingTargetPrefab != null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle);
|
||||
return networkObject != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should only run on the client
|
||||
/// </summary>
|
||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||
internal NetworkObject CreateLocalNetworkObject(bool isSceneObject, uint globalObjectIdHash, ulong ownerClientId, ulong? parentNetworkId, int? networkSceneHandle, Vector3? position, Quaternion? rotation, bool isReparented = false)
|
||||
{
|
||||
NetworkObject parentNetworkObject = null;
|
||||
|
||||
@@ -392,7 +407,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash);
|
||||
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, networkSceneHandle);
|
||||
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -414,7 +429,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// Ran on both server and client
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
|
||||
{
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -452,15 +467,12 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException("Object is already spawned");
|
||||
}
|
||||
|
||||
if (sceneObject.Header.HasNetworkVariables)
|
||||
{
|
||||
networkObject.SetNetworkVariableData(variableData);
|
||||
}
|
||||
networkObject.SetNetworkVariableData(variableData);
|
||||
|
||||
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
|
||||
}
|
||||
|
||||
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
|
||||
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
|
||||
{
|
||||
if (SpawnedObjects.ContainsKey(networkId))
|
||||
{
|
||||
@@ -468,41 +480,56 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// this initialization really should be at the bottom of the function
|
||||
networkObject.IsSpawned = true;
|
||||
|
||||
// this initialization really should be at the top of this function. If and when we break the
|
||||
// NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
|
||||
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there;
|
||||
// the current design banks on getting the network behaviour set and then only reading from it
|
||||
// after the below initialization code. However cowardice compels me to hold off on moving this until
|
||||
// that commit
|
||||
networkObject.IsSceneObject = sceneObject;
|
||||
|
||||
// Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects
|
||||
// Note: Always check SceneOriginHandle directly at this specific location.
|
||||
if (networkObject.IsSceneObject != false && networkObject.SceneOriginHandle == 0)
|
||||
{
|
||||
networkObject.SceneOrigin = networkObject.gameObject.scene;
|
||||
}
|
||||
|
||||
// For integration testing, this makes sure that the appropriate NetworkManager is assigned to
|
||||
// the NetworkObject since it uses the NetworkManager.Singleton when not set
|
||||
if (networkObject.NetworkManagerOwner != NetworkManager)
|
||||
{
|
||||
networkObject.NetworkManagerOwner = NetworkManager;
|
||||
}
|
||||
|
||||
networkObject.NetworkObjectId = networkId;
|
||||
|
||||
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
||||
|
||||
networkObject.OwnerClientIdInternal = ownerClientId;
|
||||
networkObject.OwnerClientId = ownerClientId;
|
||||
|
||||
networkObject.IsPlayerObject = playerObject;
|
||||
|
||||
SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject);
|
||||
SpawnedObjectsList.Add(networkObject);
|
||||
|
||||
if (ownerClientId != null)
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
if (NetworkManager.IsServer)
|
||||
if (playerObject)
|
||||
{
|
||||
if (playerObject)
|
||||
// If there was an already existing player object for this player, then mark it as no longer
|
||||
// a player object.
|
||||
if (NetworkManager.ConnectedClients[ownerClientId].PlayerObject != null)
|
||||
{
|
||||
NetworkManager.ConnectedClients[ownerClientId.Value].PlayerObject = networkObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.ConnectedClients[ownerClientId.Value].OwnedObjects.Add(networkObject);
|
||||
NetworkManager.ConnectedClients[ownerClientId].PlayerObject.IsPlayerObject = false;
|
||||
}
|
||||
NetworkManager.ConnectedClients[ownerClientId].PlayerObject = networkObject;
|
||||
}
|
||||
else if (playerObject && ownerClientId.Value == NetworkManager.LocalClientId)
|
||||
}
|
||||
else if (ownerClientId == NetworkManager.LocalClientId)
|
||||
{
|
||||
if (playerObject)
|
||||
{
|
||||
// If there was an already existing player object for this player, then mark it as no longer a player object.
|
||||
if (NetworkManager.LocalClient.PlayerObject != null)
|
||||
{
|
||||
NetworkManager.LocalClient.PlayerObject.IsPlayerObject = false;
|
||||
}
|
||||
NetworkManager.LocalClient.PlayerObject = networkObject;
|
||||
}
|
||||
}
|
||||
@@ -524,20 +551,7 @@ namespace Unity.Netcode
|
||||
|
||||
networkObject.InvokeBehaviourNetworkSpawn();
|
||||
|
||||
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
|
||||
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
|
||||
if (m_Triggers.ContainsKey(networkId))
|
||||
{
|
||||
var triggerInfo = m_Triggers[networkId];
|
||||
foreach (var trigger in triggerInfo.TriggerData)
|
||||
{
|
||||
// Reader will be disposed within HandleMessage
|
||||
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp, trigger.SerializedHeaderSize);
|
||||
}
|
||||
|
||||
triggerInfo.TriggerData.Dispose();
|
||||
m_Triggers.Remove(networkId);
|
||||
}
|
||||
NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnSpawn, networkId);
|
||||
|
||||
// propagate the IsSceneObject setting to child NetworkObjects
|
||||
var children = networkObject.GetComponentsInChildren<NetworkObject>();
|
||||
@@ -549,25 +563,20 @@ namespace Unity.Netcode
|
||||
|
||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
// If we are a host and sending to the host's client id, then we can skip sending ourselves the spawn message.
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
//Currently, if this is called and the clientId (destination) is the server's client Id, this case
|
||||
//will be checked within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer
|
||||
//placing this check here. [NSS]
|
||||
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new CreateObjectMessage
|
||||
{
|
||||
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
||||
|
||||
networkObject.MarkVariablesDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new CreateObjectMessage
|
||||
{
|
||||
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
||||
|
||||
networkObject.MarkVariablesDirty(true);
|
||||
}
|
||||
|
||||
internal ulong? GetSpawnParentId(NetworkObject networkObject)
|
||||
@@ -605,14 +614,12 @@ namespace Unity.Netcode
|
||||
// Makes scene objects ready to be reused
|
||||
internal void ServerResetShudownStateForSceneObjects()
|
||||
{
|
||||
foreach (var sobj in SpawnedObjectsList)
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
|
||||
foreach (var sobj in networkObjects)
|
||||
{
|
||||
if ((sobj.IsSceneObject != null && sobj.IsSceneObject == true) || sobj.DestroyWithScene)
|
||||
{
|
||||
sobj.IsSpawned = false;
|
||||
sobj.DestroyWithScene = false;
|
||||
sobj.IsSceneObject = null;
|
||||
}
|
||||
sobj.IsSpawned = false;
|
||||
sobj.DestroyWithScene = false;
|
||||
sobj.IsSceneObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,14 +660,12 @@ namespace Unity.Netcode
|
||||
else if (networkObjects[i].IsSpawned)
|
||||
{
|
||||
// If it is an in-scene placed NetworkObject then just despawn
|
||||
// and let it be destroyed when the scene is unloaded. Otherwise,
|
||||
// despawn and destroy it.
|
||||
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
|
||||
&& networkObjects[i].IsSceneObject.Value);
|
||||
// and let it be destroyed when the scene is unloaded. Otherwise, despawn and destroy it.
|
||||
var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
|
||||
|
||||
OnDespawnObject(networkObjects[i], shouldDestroy);
|
||||
}
|
||||
else
|
||||
else if (networkObjects[i].IsSceneObject != null && !networkObjects[i].IsSceneObject.Value)
|
||||
{
|
||||
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
|
||||
}
|
||||
@@ -711,9 +716,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var networkObject in networkObjectsToSpawn)
|
||||
{
|
||||
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, null, true);
|
||||
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,18 +763,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
{
|
||||
//Someone owns it.
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i].NetworkObjectId == networkObject.NetworkObjectId)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
networkObject.InvokeBehaviourNetworkDespawn();
|
||||
|
||||
if (NetworkManager != null && NetworkManager.IsServer)
|
||||
@@ -782,38 +776,32 @@ namespace Unity.Netcode
|
||||
});
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
if (networkObject != null)
|
||||
{
|
||||
networkObject.SnapshotDespawn();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (networkObject != null)
|
||||
// As long as we have any remaining clients, then notify of the object being destroy.
|
||||
if (NetworkManager.ConnectedClientsList.Count > 0)
|
||||
{
|
||||
// As long as we have any remaining clients, then notify of the object being destroy.
|
||||
if (NetworkManager.ConnectedClientsList.Count > 0)
|
||||
m_TargetClientIds.Clear();
|
||||
|
||||
// We keep only the client for which the object is visible
|
||||
// as the other clients have them already despawned
|
||||
foreach (var clientId in NetworkManager.ConnectedClientsIds)
|
||||
{
|
||||
m_TargetClientIds.Clear();
|
||||
|
||||
// We keep only the client for which the object is visible
|
||||
// as the other clients have them already despawned
|
||||
foreach (var clientId in NetworkManager.ConnectedClientsIds)
|
||||
if (networkObject.IsNetworkVisibleTo(clientId))
|
||||
{
|
||||
if (networkObject.IsNetworkVisibleTo(clientId))
|
||||
{
|
||||
m_TargetClientIds.Add(clientId);
|
||||
}
|
||||
m_TargetClientIds.Add(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||
foreach (var targetClientId in m_TargetClientIds)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
|
||||
}
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
DestroyGameObject = networkObject.IsSceneObject != false ? destroyGameObject : true
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||
foreach (var targetClientId in m_TargetClientIds)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -826,6 +814,9 @@ namespace Unity.Netcode
|
||||
SpawnedObjectsList.Remove(networkObject);
|
||||
}
|
||||
|
||||
// Always clear out the observers list when despawned
|
||||
networkObject.Observers.Clear();
|
||||
|
||||
var gobj = networkObject.gameObject;
|
||||
if (destroyGameObject && gobj != null)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user