com.unity.netcode.gameobjects@1.0.0-pre.8
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.0.0-pre.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) ### 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). ### Fixed - 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 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)
This commit is contained in:
@@ -5,6 +5,7 @@ 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")]
|
||||
|
||||
@@ -128,10 +128,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.
|
||||
|
||||
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
|
||||
@@ -122,7 +122,7 @@ namespace Unity.Netcode
|
||||
return !m_NetworkManager.m_StopProcessingMessages;
|
||||
}
|
||||
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType, FastBufferReader messageContent, ref NetworkContext context)
|
||||
{
|
||||
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
|
||||
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
|
||||
@@ -223,6 +223,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public NetworkSpawnManager SpawnManager { get; private set; }
|
||||
|
||||
internal IDeferredMessageManager DeferredMessageManager { get; private set; }
|
||||
|
||||
public CustomMessagingManager CustomMessagingManager { get; private set; }
|
||||
|
||||
public NetworkSceneManager SceneManager { get; private set; }
|
||||
@@ -484,6 +486,253 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Adds a new prefab to the network prefab list.
|
||||
/// This can be any GameObject with a NetworkObject component, from any source (addressables, asset
|
||||
/// bundles, Resource.Load, dynamically created, etc)
|
||||
///
|
||||
/// There are three limitations to this method:
|
||||
/// - If you have NetworkConfig.ForceSamePrefabs enabled, you can only do this before starting
|
||||
/// networking, and the server and all connected clients must all have the same exact set of prefabs
|
||||
/// added via this method before connecting
|
||||
/// - Adding a prefab on the server does not automatically add it on the client - it's up to you
|
||||
/// to make sure the client and server are synchronized via whatever method makes sense for your game
|
||||
/// (RPCs, configs, deterministic loading, etc)
|
||||
/// - If the server sends a Spawn message to a client that has not yet added a prefab for, the spawn message
|
||||
/// and any other relevant messages will be held for a configurable time (default 1 second, configured via
|
||||
/// NetworkConfig.SpawnTimeout) before an error is logged. This is intented to enable the SDK to gracefully
|
||||
/// handle unexpected conditions (slow disks, slow network, etc) that slow down asset loading. This timeout
|
||||
/// should not be relied on and code shouldn't be written around it - your code should be written so that
|
||||
/// the asset is expected to be loaded before it's needed.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void AddNetworkPrefab(GameObject prefab)
|
||||
{
|
||||
if (IsListening && NetworkConfig.ForceSamePrefabs)
|
||||
{
|
||||
throw new Exception($"All prefabs must be registered before starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
|
||||
}
|
||||
|
||||
var networkObject = prefab.GetComponent<NetworkObject>();
|
||||
if (!networkObject)
|
||||
{
|
||||
throw new Exception($"All {nameof(NetworkPrefab)}s must contain a {nameof(NetworkObject)} component.");
|
||||
}
|
||||
|
||||
var networkPrefab = new NetworkPrefab { Prefab = prefab };
|
||||
NetworkConfig.NetworkPrefabs.Add(networkPrefab);
|
||||
if (IsListening)
|
||||
{
|
||||
var sourcePrefabGlobalObjectIdHash = (uint)0;
|
||||
var targetPrefabGlobalObjectIdHash = (uint)0;
|
||||
if (!ShouldAddPrefab(networkPrefab, out sourcePrefabGlobalObjectIdHash, out targetPrefabGlobalObjectIdHash))
|
||||
{
|
||||
NetworkConfig.NetworkPrefabs.Remove(networkPrefab);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AddPrefabRegistration(networkPrefab, sourcePrefabGlobalObjectIdHash, targetPrefabGlobalObjectIdHash))
|
||||
{
|
||||
NetworkConfig.NetworkPrefabs.Remove(networkPrefab);
|
||||
return;
|
||||
}
|
||||
DeferredMessageManager.ProcessTriggers(IDeferredMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAddPrefab(NetworkPrefab networkPrefab, out uint sourcePrefabGlobalObjectIdHash, out uint targetPrefabGlobalObjectIdHash, int index = -1)
|
||||
{
|
||||
sourcePrefabGlobalObjectIdHash = 0;
|
||||
targetPrefabGlobalObjectIdHash = 0;
|
||||
var networkObject = (NetworkObject)null;
|
||||
if (networkPrefab == null || (networkPrefab.Prefab == null && networkPrefab.Override == NetworkPrefabOverride.None))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
$"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {index})");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (networkPrefab.Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} is missing " +
|
||||
$"a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise get the GlobalObjectIdHash value
|
||||
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
||||
}
|
||||
else // Validate Overrides
|
||||
{
|
||||
// Validate source prefab override values first
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
if (networkPrefab.SourceHashToOverride == 0)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourceHashToOverride)} is zero " +
|
||||
"(entry will be ignored).");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
sourcePrefabGlobalObjectIdHash = networkPrefab.SourceHashToOverride;
|
||||
break;
|
||||
}
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
if (networkPrefab.SourcePrefabToOverride == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourcePrefabToOverride)} is null (entry will be ignored).");
|
||||
}
|
||||
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {networkPrefab.SourceHashToOverride} will be removed and ignored.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject = networkPrefab.SourcePrefabToOverride.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({networkPrefab.SourcePrefabToOverride.name}) " +
|
||||
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||
}
|
||||
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry (\"{networkPrefab.SourcePrefabToOverride.name}\") will be removed and ignored.");
|
||||
return false;
|
||||
}
|
||||
|
||||
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate target prefab override values next
|
||||
if (networkPrefab.OverridingTargetPrefab == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.OverridingTargetPrefab)} is null!");
|
||||
}
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {networkPrefab.SourceHashToOverride} will be removed and ignored.");
|
||||
break;
|
||||
}
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({networkPrefab.SourcePrefabToOverride.name}) will be removed and ignored.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPrefabGlobalObjectIdHash = networkPrefab.OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool AddPrefabRegistration(NetworkPrefab networkPrefab, uint sourcePrefabGlobalObjectIdHash, uint targetPrefabGlobalObjectIdHash)
|
||||
{
|
||||
// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
|
||||
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(sourcePrefabGlobalObjectIdHash))
|
||||
{
|
||||
if (networkPrefab.Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetPrefabGlobalObjectIdHash))
|
||||
{
|
||||
switch (networkPrefab.Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
|
||||
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
||||
}
|
||||
break;
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, networkPrefab);
|
||||
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
|
||||
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {targetPrefabGlobalObjectIdHash}! Removing entry from list!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
|
||||
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {sourcePrefabGlobalObjectIdHash}! Removing entry from list!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitializePrefabs(int startIdx = 0)
|
||||
{
|
||||
// This is used to remove entries not needed or invalid
|
||||
var removeEmptyPrefabs = new List<int>();
|
||||
|
||||
// Build the NetworkPrefabOverrideLinks dictionary
|
||||
for (int i = startIdx; i < NetworkConfig.NetworkPrefabs.Count; i++)
|
||||
{
|
||||
var sourcePrefabGlobalObjectIdHash = (uint)0;
|
||||
var targetPrefabGlobalObjectIdHash = (uint)0;
|
||||
if (!ShouldAddPrefab(NetworkConfig.NetworkPrefabs[i], out sourcePrefabGlobalObjectIdHash, out targetPrefabGlobalObjectIdHash, i))
|
||||
{
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!AddPrefabRegistration(NetworkConfig.NetworkPrefabs[i], sourcePrefabGlobalObjectIdHash, targetPrefabGlobalObjectIdHash))
|
||||
{
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
|
||||
// Iterate backwards so indices don't shift as we remove
|
||||
for (int i = removeEmptyPrefabs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
NetworkConfig.NetworkPrefabs.RemoveAt(removeEmptyPrefabs[i]);
|
||||
}
|
||||
|
||||
removeEmptyPrefabs.Clear();
|
||||
}
|
||||
|
||||
private void Initialize(bool server)
|
||||
{
|
||||
@@ -494,6 +743,8 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentFactory.SetDefaults();
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo(nameof(Initialize));
|
||||
@@ -524,6 +775,8 @@ namespace Unity.Netcode
|
||||
// Create spawn manager instance
|
||||
SpawnManager = new NetworkSpawnManager(this);
|
||||
|
||||
DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this);
|
||||
|
||||
CustomMessagingManager = new CustomMessagingManager(this);
|
||||
|
||||
SceneManager = new NetworkSceneManager(this);
|
||||
@@ -573,171 +826,11 @@ namespace Unity.Netcode
|
||||
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
|
||||
|
||||
// This is used to remove entries not needed or invalid
|
||||
var removeEmptyPrefabs = new List<int>();
|
||||
|
||||
// Always clear our prefab override links before building
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
|
||||
NetworkConfig.OverrideToNetworkPrefab.Clear();
|
||||
|
||||
// Build the NetworkPrefabOverrideLinks dictionary
|
||||
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
|
||||
{
|
||||
var sourcePrefabGlobalObjectIdHash = (uint)0;
|
||||
var targetPrefabGlobalObjectIdHash = (uint)0;
|
||||
var networkObject = (NetworkObject)null;
|
||||
if (NetworkConfig.NetworkPrefabs[i] == null || (NetworkConfig.NetworkPrefabs[i].Prefab == null && NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
$"{nameof(NetworkPrefab)} cannot be null ({nameof(NetworkPrefab)} at index: {i})");
|
||||
}
|
||||
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
else if (NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
var networkPrefab = NetworkConfig.NetworkPrefabs[i];
|
||||
networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{PrefabDebugHelper(networkPrefab)} is missing " +
|
||||
$"a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||
}
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise get the GlobalObjectIdHash value
|
||||
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
||||
}
|
||||
else // Validate Overrides
|
||||
{
|
||||
// Validate source prefab override values first
|
||||
switch (NetworkConfig.NetworkPrefabs[i].Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
if (NetworkConfig.NetworkPrefabs[i].SourceHashToOverride == 0)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourceHashToOverride)} is zero " +
|
||||
"(entry will be ignored).");
|
||||
}
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
sourcePrefabGlobalObjectIdHash = NetworkConfig.NetworkPrefabs[i].SourceHashToOverride;
|
||||
break;
|
||||
}
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
if (NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.SourcePrefabToOverride)} is null (entry will be ignored).");
|
||||
}
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {NetworkConfig.NetworkPrefabs[i].SourceHashToOverride} will be removed and ignored.");
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkObject = NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} ({NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}) " +
|
||||
$"is missing a {nameof(NetworkObject)} component (entry will be ignored).");
|
||||
}
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry (\"{NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}\") will be removed and ignored.");
|
||||
removeEmptyPrefabs.Add(i);
|
||||
continue;
|
||||
}
|
||||
sourcePrefabGlobalObjectIdHash = networkObject.GlobalObjectIdHash;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate target prefab override values next
|
||||
if (NetworkConfig.NetworkPrefabs[i].OverridingTargetPrefab == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {nameof(NetworkPrefab.OverridingTargetPrefab)} is null!");
|
||||
}
|
||||
removeEmptyPrefabs.Add(i);
|
||||
switch (NetworkConfig.NetworkPrefabs[i].Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry {NetworkConfig.NetworkPrefabs[i].SourceHashToOverride} will be removed and ignored.");
|
||||
break;
|
||||
}
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
Debug.LogWarning($"{nameof(NetworkPrefab)} override entry ({NetworkConfig.NetworkPrefabs[i].SourcePrefabToOverride.name}) will be removed and ignored.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPrefabGlobalObjectIdHash = NetworkConfig.NetworkPrefabs[i].OverridingTargetPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the appropriate GlobalObjectIdHash to the appropriate NetworkPrefab
|
||||
if (!NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(sourcePrefabGlobalObjectIdHash))
|
||||
{
|
||||
if (NetworkConfig.NetworkPrefabs[i].Override == NetworkPrefabOverride.None)
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!NetworkConfig.OverrideToNetworkPrefab.ContainsKey(targetPrefabGlobalObjectIdHash))
|
||||
{
|
||||
switch (NetworkConfig.NetworkPrefabs[i].Override)
|
||||
{
|
||||
case NetworkPrefabOverride.Prefab:
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
|
||||
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
||||
}
|
||||
break;
|
||||
case NetworkPrefabOverride.Hash:
|
||||
{
|
||||
NetworkConfig.NetworkPrefabOverrideLinks.Add(sourcePrefabGlobalObjectIdHash, NetworkConfig.NetworkPrefabs[i]);
|
||||
NetworkConfig.OverrideToNetworkPrefab.Add(targetPrefabGlobalObjectIdHash, sourcePrefabGlobalObjectIdHash);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This can happen if a user tries to make several GlobalObjectIdHash values point to the same target
|
||||
Debug.LogError($"{nameof(NetworkPrefab)} (\"{networkObject.name}\") has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} target entry value of: {targetPrefabGlobalObjectIdHash}! Removing entry from list!");
|
||||
removeEmptyPrefabs.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
|
||||
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {sourcePrefabGlobalObjectIdHash}! Removing entry from list!");
|
||||
removeEmptyPrefabs.Add(i);
|
||||
}
|
||||
}
|
||||
InitializePrefabs();
|
||||
|
||||
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
|
||||
if (NetworkConfig.PlayerPrefab != null)
|
||||
@@ -764,15 +857,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out anything that is invalid or not used (for invalid entries we already logged warnings to the user earlier)
|
||||
// Iterate backwards so indices don't shift as we remove
|
||||
for (int i = removeEmptyPrefabs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
NetworkConfig.NetworkPrefabs.RemoveAt(removeEmptyPrefabs[i]);
|
||||
}
|
||||
|
||||
removeEmptyPrefabs.Clear();
|
||||
|
||||
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
|
||||
|
||||
NetworkConfig.NetworkTransport.Initialize(this);
|
||||
@@ -930,7 +1014,9 @@ namespace Unity.Netcode
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NetworkConfig.ConnectionApproval)
|
||||
// Only if it is starting as a server or host do we need to check this
|
||||
// Clients don't invoke the ConnectionApprovalCallback
|
||||
if (NetworkConfig.ConnectionApproval && type != StartType.Client)
|
||||
{
|
||||
if (ConnectionApprovalCallback == null)
|
||||
{
|
||||
@@ -985,6 +1071,7 @@ namespace Unity.Netcode
|
||||
private void Awake()
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||
NetworkVariableHelper.InitializeAllBaseDelegates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1081,8 +1168,13 @@ namespace Unity.Netcode
|
||||
NetworkLog.LogInfo(nameof(Shutdown));
|
||||
}
|
||||
|
||||
m_ShuttingDown = true;
|
||||
m_StopProcessingMessages = discardMessageQueue;
|
||||
// If we're not running, don't start shutting down, it would only cause an immediate
|
||||
// shutdown the next time the manager is started.
|
||||
if (IsServer || IsClient)
|
||||
{
|
||||
m_ShuttingDown = true;
|
||||
m_StopProcessingMessages = discardMessageQueue;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ShutdownInternal()
|
||||
@@ -1165,13 +1257,17 @@ namespace Unity.Netcode
|
||||
|
||||
if (SpawnManager != null)
|
||||
{
|
||||
SpawnManager.CleanupAllTriggers();
|
||||
SpawnManager.DespawnAndDestroyNetworkObjects();
|
||||
SpawnManager.ServerResetShudownStateForSceneObjects();
|
||||
|
||||
SpawnManager = null;
|
||||
}
|
||||
|
||||
if (DeferredMessageManager != null)
|
||||
{
|
||||
DeferredMessageManager.CleanupAllTriggers();
|
||||
}
|
||||
|
||||
if (SceneManager != null)
|
||||
{
|
||||
// Let the NetworkSceneManager clean up its two SceneEvenData instances
|
||||
@@ -1287,7 +1383,7 @@ namespace Unity.Netcode
|
||||
|
||||
NetworkObject.VerifyParentingStatus();
|
||||
}
|
||||
SpawnManager.CleanupStaleTriggers();
|
||||
DeferredMessageManager.CleanupStaleTriggers();
|
||||
|
||||
if (m_ShuttingDown)
|
||||
{
|
||||
@@ -1415,11 +1511,6 @@ namespace Unity.Netcode
|
||||
break;
|
||||
case NetworkEvent.Data:
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes");
|
||||
}
|
||||
|
||||
clientId = TransportIdToClientId(clientId);
|
||||
|
||||
HandleIncomingData(clientId, payload, receiveTime);
|
||||
@@ -1444,7 +1535,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
Shutdown();
|
||||
// We must pass true here and not process any sends messages
|
||||
// as we are no longer connected and thus there is no one to
|
||||
// send any messages to and this will cause an exception within
|
||||
// UnityTransport as the client ID is no longer valid.
|
||||
Shutdown(true);
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_TransportDisconnect.End();
|
||||
|
||||
@@ -826,7 +826,7 @@ namespace Unity.Netcode
|
||||
|
||||
internal struct SceneObject
|
||||
{
|
||||
public struct HeaderData
|
||||
public struct HeaderData : INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ulong OwnerClientId;
|
||||
@@ -845,7 +845,7 @@ namespace Unity.Netcode
|
||||
public ulong ParentObjectId;
|
||||
|
||||
//If(Metadata.HasTransform)
|
||||
public struct TransformData
|
||||
public struct TransformData : INetworkSerializeByMemcpy
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
|
||||
@@ -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.
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
ObjectInfo.Deserialize(reader);
|
||||
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo.Header.IsSceneObject, ObjectInfo.Header.Hash))
|
||||
{
|
||||
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
|
||||
return false;
|
||||
}
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct DestroyObjectMessage : INetworkMessage
|
||||
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
|
||||
@@ -16,7 +16,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,14 @@ namespace Unity.Netcode
|
||||
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)
|
||||
@@ -225,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct TimeSyncMessage : INetworkMessage
|
||||
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
|
||||
{
|
||||
public int Tick;
|
||||
|
||||
|
||||
@@ -136,6 +136,11 @@ namespace Unity.Netcode
|
||||
m_Hooks.Add(hooks);
|
||||
}
|
||||
|
||||
public void Unhook(INetworkHooks hooks)
|
||||
{
|
||||
m_Hooks.Remove(hooks);
|
||||
}
|
||||
|
||||
private void RegisterMessageType(MessageWithHandler messageWithHandler)
|
||||
{
|
||||
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
|
||||
@@ -208,11 +213,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;
|
||||
}
|
||||
@@ -240,7 +245,7 @@ namespace Unity.Netcode
|
||||
};
|
||||
|
||||
var type = m_ReverseTypeMap[header.MessageType];
|
||||
if (!CanReceive(senderId, type))
|
||||
if (!CanReceive(senderId, type, reader, ref context))
|
||||
{
|
||||
reader.Dispose();
|
||||
return;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Unity.Netcode
|
||||
/// Event based NetworkVariable container for syncing Lists
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the list</typeparam>
|
||||
public class NetworkList<T> : NetworkVariableBase where T : unmanaged, IEquatable<T>
|
||||
public class NetworkList<T> : NetworkVariableSerialization<T> where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
private NativeList<T> m_List = new NativeList<T>(64, Allocator.Persistent);
|
||||
private NativeList<NetworkListEvent<T>> m_DirtyEvents = new NativeList<NetworkListEvent<T>>(64, Allocator.Persistent);
|
||||
@@ -72,18 +72,18 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||
@@ -94,7 +94,7 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
writer.WriteValueSafe(m_DirtyEvents[i].Index);
|
||||
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
|
||||
Write(writer, m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
@@ -112,7 +112,7 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe((ushort)m_List.Length);
|
||||
for (int i = 0; i < m_List.Length; i++)
|
||||
{
|
||||
NetworkVariable<T>.Write(writer, m_List[i]);
|
||||
Write(writer, m_List[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace Unity.Netcode
|
||||
reader.ReadValueSafe(out ushort count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
Read(reader, out T value);
|
||||
m_List.Add(value);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
Read(reader, out T value);
|
||||
m_List.Add(value);
|
||||
|
||||
if (OnListChanged != null)
|
||||
@@ -166,7 +166,7 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
Read(reader, out T value);
|
||||
m_List.InsertRangeWithBeginEnd(index, index + 1);
|
||||
m_List[index] = value;
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Unity.Netcode
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
Read(reader, out T value);
|
||||
int index = m_List.IndexOf(value);
|
||||
if (index == -1)
|
||||
{
|
||||
@@ -253,7 +253,7 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
Read(reader, out T value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
|
||||
@@ -9,55 +9,8 @@ namespace Unity.Netcode
|
||||
/// A variable that can be synchronized over the network.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
|
||||
public class NetworkVariable<T> : NetworkVariableSerialization<T> 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>
|
||||
@@ -143,6 +96,11 @@ 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);
|
||||
|
||||
|
||||
@@ -94,6 +94,14 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the ClientId of the owning client
|
||||
/// </summary>
|
||||
internal ulong OwnerClientId()
|
||||
{
|
||||
return m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public class NetworkVariableHelper
|
||||
@@ -13,10 +16,62 @@ namespace Unity.Netcode
|
||||
// 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
|
||||
internal static void InitializeDelegatesNetworkSerializable<T>() where T : unmanaged, INetworkSerializable
|
||||
{
|
||||
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable;
|
||||
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable;
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteNetworkSerializable);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadNetworkSerializable);
|
||||
}
|
||||
internal static void InitializeDelegatesStruct<T>() where T : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteStruct);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadStruct);
|
||||
}
|
||||
internal static void InitializeDelegatesEnum<T>() where T : unmanaged, Enum
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WriteEnum);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadEnum);
|
||||
}
|
||||
internal static void InitializeDelegatesPrimitive<T>() where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>
|
||||
{
|
||||
NetworkVariableSerialization<T>.SetWriteDelegate(NetworkVariableSerialization<T>.WritePrimitive);
|
||||
NetworkVariableSerialization<T>.SetReadDelegate(NetworkVariableSerialization<T>.ReadPrimitive);
|
||||
}
|
||||
|
||||
internal static void InitializeAllBaseDelegates()
|
||||
{
|
||||
// Built-in C# types, serialized through a generic method
|
||||
InitializeDelegatesPrimitive<bool>();
|
||||
InitializeDelegatesPrimitive<byte>();
|
||||
InitializeDelegatesPrimitive<sbyte>();
|
||||
InitializeDelegatesPrimitive<char>();
|
||||
InitializeDelegatesPrimitive<decimal>();
|
||||
InitializeDelegatesPrimitive<float>();
|
||||
InitializeDelegatesPrimitive<double>();
|
||||
InitializeDelegatesPrimitive<short>();
|
||||
InitializeDelegatesPrimitive<ushort>();
|
||||
InitializeDelegatesPrimitive<int>();
|
||||
InitializeDelegatesPrimitive<uint>();
|
||||
InitializeDelegatesPrimitive<long>();
|
||||
InitializeDelegatesPrimitive<ulong>();
|
||||
|
||||
// Built-in Unity types, serialized with specific overloads because they're structs without ISerializeByMemcpy attached
|
||||
NetworkVariableSerialization<Vector2>.SetWriteDelegate((FastBufferWriter writer, in Vector2 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Vector3>.SetWriteDelegate((FastBufferWriter writer, in Vector3 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Vector4>.SetWriteDelegate((FastBufferWriter writer, in Vector4 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Quaternion>.SetWriteDelegate((FastBufferWriter writer, in Quaternion value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Color>.SetWriteDelegate((FastBufferWriter writer, in Color value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Color32>.SetWriteDelegate((FastBufferWriter writer, in Color32 value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Ray>.SetWriteDelegate((FastBufferWriter writer, in Ray value) => { writer.WriteValueSafe(value); });
|
||||
NetworkVariableSerialization<Ray2D>.SetWriteDelegate((FastBufferWriter writer, in Ray2D value) => { writer.WriteValueSafe(value); });
|
||||
|
||||
NetworkVariableSerialization<Vector2>.SetReadDelegate((FastBufferReader reader, out Vector2 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Vector3>.SetReadDelegate((FastBufferReader reader, out Vector3 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Vector4>.SetReadDelegate((FastBufferReader reader, out Vector4 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Quaternion>.SetReadDelegate((FastBufferReader reader, out Quaternion value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Color>.SetReadDelegate((FastBufferReader reader, out Color value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Color32>.SetReadDelegate((FastBufferReader reader, out Color32 value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Ray>.SetReadDelegate((FastBufferReader reader, out Ray value) => { reader.ReadValueSafe(out value); });
|
||||
NetworkVariableSerialization<Ray2D>.SetReadDelegate((FastBufferReader reader, out Ray2D value) => { reader.ReadValueSafe(out value); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
169
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
169
Runtime/NetworkVariable/NetworkVariableSerialization.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Support methods for reading/writing NetworkVariables
|
||||
/// Because there are multiple overloads of WriteValue/ReadValue based on different generic constraints,
|
||||
/// but there's no way to achieve the same thing with a class, this includes various read/write delegates
|
||||
/// based on which constraints are met by `T`. These constraints are set up by `NetworkVariableHelpers`,
|
||||
/// which is invoked by code generated by ILPP during module load.
|
||||
/// (As it turns out, IL has support for a module initializer that C# doesn't expose.)
|
||||
/// This installs the correct delegate for each `T` to ensure that each type is serialized properly.
|
||||
///
|
||||
/// Any type that inherits from `NetworkVariableSerialization<T>` will implicitly result in any `T`
|
||||
/// passed to it being picked up and initialized by ILPP.
|
||||
///
|
||||
/// The methods here, despite being static, are `protected` specifically to ensure that anything that
|
||||
/// wants access to them has to inherit from this base class, thus enabling ILPP to find and initialize it.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class NetworkVariableSerialization<T> : NetworkVariableBase where T : unmanaged
|
||||
{
|
||||
// Functions that know how to serialize INetworkSerializable
|
||||
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializable
|
||||
{
|
||||
writer.WriteNetworkSerializable(value);
|
||||
}
|
||||
|
||||
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializable
|
||||
{
|
||||
reader.ReadNetworkSerializable(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize structs
|
||||
internal static void WriteStruct<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
internal static void ReadStruct<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, INetworkSerializeByMemcpy
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize enums
|
||||
internal static void WriteEnum<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, Enum
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
internal static void ReadEnum<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, Enum
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Functions that serialize other types
|
||||
internal static void WritePrimitive<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
|
||||
internal static void ReadPrimitive<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged, IComparable, IConvertible, IComparable<TForMethod>, IEquatable<TForMethod>
|
||||
{
|
||||
reader.ReadValueSafe(out value);
|
||||
}
|
||||
|
||||
// Should never be reachable at runtime. All calls to this should be replaced with the correct
|
||||
// call above by ILPP.
|
||||
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
{
|
||||
if (value is INetworkSerializable)
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (value is INetworkSerializeByMemcpy)
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (value is Enum)
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
|
||||
|
||||
}
|
||||
NetworkVariableSerialization<TForMethod>.Write(writer, value);
|
||||
}
|
||||
|
||||
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
{
|
||||
if (typeof(INetworkSerializable).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesNetworkSerializable)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (typeof(INetworkSerializeByMemcpy).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesStruct)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else if (typeof(Enum).IsAssignableFrom(typeof(TForMethod)))
|
||||
{
|
||||
typeof(NetworkVariableHelper).GetMethod(nameof(NetworkVariableHelper.InitializeDelegatesEnum)).MakeGenericMethod(typeof(TForMethod)).Invoke(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Type {typeof(T).FullName} is not serializable - it must implement either INetworkSerializable or ISerializeByMemcpy");
|
||||
|
||||
}
|
||||
NetworkVariableSerialization<TForMethod>.Read(reader, out value);
|
||||
}
|
||||
|
||||
protected internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
|
||||
|
||||
protected internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
|
||||
|
||||
// These static delegates provide the right implementation for writing and reading a particular network variable type.
|
||||
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
|
||||
//
|
||||
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
|
||||
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
|
||||
//
|
||||
// In the future we may be able to use this to provide packing implementations for floats and integers to optimize bandwidth usage.
|
||||
//
|
||||
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
|
||||
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
|
||||
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
|
||||
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
|
||||
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
|
||||
// side, but it gets the best achievable user experience and performance.
|
||||
private static WriteDelegate<T> s_Write = WriteValue;
|
||||
private static ReadDelegate<T> s_Read = ReadValue;
|
||||
|
||||
protected static void Write(FastBufferWriter writer, in T value)
|
||||
{
|
||||
s_Write(writer, value);
|
||||
}
|
||||
|
||||
protected static void Read(FastBufferReader reader, out T value)
|
||||
{
|
||||
s_Read(reader, out value);
|
||||
}
|
||||
|
||||
internal static void SetWriteDelegate(WriteDelegate<T> write)
|
||||
{
|
||||
s_Write = write;
|
||||
}
|
||||
|
||||
internal static void SetReadDelegate(ReadDelegate<T> read)
|
||||
{
|
||||
s_Read = read;
|
||||
}
|
||||
|
||||
protected NetworkVariableSerialization(
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1446,12 +1446,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.ClientSceneHandle = 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)
|
||||
@@ -1468,9 +1465,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;
|
||||
@@ -1746,7 +1750,9 @@ namespace Unity.Netcode
|
||||
// NetworkObjects
|
||||
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
||||
|
||||
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 });
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
internal SceneEventType SceneEventType;
|
||||
internal LoadSceneMode LoadSceneMode;
|
||||
internal Guid SceneEventProgressId;
|
||||
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
|
||||
internal uint SceneEventId;
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using System;
|
||||
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 +61,63 @@ 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);
|
||||
}
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValue(ref s, oneByteChars);
|
||||
public void SerializeValue(ref byte value) => m_Implementation.SerializeValue(ref value);
|
||||
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);
|
||||
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);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector2 value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector2[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector3 value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector3[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector4 value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Vector4[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Quaternion value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Quaternion[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Color value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Color[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Color32 value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Color32[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Ray value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Ray[] value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Ray2D value) => m_Implementation.SerializeValue(ref value);
|
||||
public void SerializeValue(ref Ray2D[] value) => m_Implementation.SerializeValue(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.
|
||||
/// </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);
|
||||
}
|
||||
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new() => m_Implementation.SerializeNetworkSerializable(ref value);
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="array">Value to serialize</param>
|
||||
public void SerializeValue<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValue(ref array);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValue(ref byte value)
|
||||
{
|
||||
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.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValue<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
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>()
|
||||
/// </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>
|
||||
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.
|
||||
/// </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);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="array">Value to serialize</param>
|
||||
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValuePreChecked(ref array);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValuePreChecked(ref byte value)
|
||||
{
|
||||
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.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to serialize</param>
|
||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false) => m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
|
||||
public void SerializeValuePreChecked(ref byte value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
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);
|
||||
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);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector2 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector2[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector3 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector3[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector4 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Vector4[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Quaternion value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Quaternion[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Color value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Color[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Color32 value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Color32[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Ray value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Ray[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Ray2D value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
public void SerializeValuePreChecked(ref Ray2D[] value) => m_Implementation.SerializeValuePreChecked(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -24,54 +25,63 @@ 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(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 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[] array) where T : unmanaged
|
||||
{
|
||||
m_Reader.ReadValueSafe(out array);
|
||||
}
|
||||
|
||||
public void SerializeValue(ref byte value)
|
||||
{
|
||||
m_Reader.ReadByteSafe(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(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 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,5 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -24,25 +25,32 @@ namespace Unity.Netcode
|
||||
return m_Writer;
|
||||
}
|
||||
|
||||
public void SerializeValue(ref string s, bool oneByteChars = false)
|
||||
{
|
||||
m_Writer.WriteValueSafe(s, oneByteChars);
|
||||
}
|
||||
|
||||
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 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(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 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 +62,30 @@ 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(ref byte value)
|
||||
{
|
||||
m_Writer.WriteByte(value);
|
||||
}
|
||||
|
||||
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
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.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(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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -195,6 +196,30 @@ namespace Unity.Netcode
|
||||
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FastBufferReader from another existing FastBufferReader. This is typically used when you
|
||||
/// want to change the allocator that a reader is allocated to - for example, upgrading a Temp reader to
|
||||
/// a Persistent one to be processed later.
|
||||
///
|
||||
/// A new buffer will be created using the given allocator and the value will be copied in.
|
||||
/// FastBufferReader will then own the data.
|
||||
///
|
||||
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
|
||||
/// ownership of the data remains with the caller and the reader will point at it directly.
|
||||
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
|
||||
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
|
||||
/// the context in which it was created (it should neither be returned from that function nor
|
||||
/// stored anywhere in heap memory).
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to copy from</param>
|
||||
/// <param name="allocator">The allocator to use</param>
|
||||
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
|
||||
/// <param name="offset">The offset of the buffer to start copying from</param>
|
||||
public unsafe FastBufferReader(FastBufferReader reader, Allocator allocator, int length = -1, int offset = 0)
|
||||
{
|
||||
Handle = CreateHandle(reader.GetUnsafePtr(), length == -1 ? reader.Length : length, offset, allocator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the allocated buffer
|
||||
/// </summary>
|
||||
@@ -492,61 +517,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unmanaged array
|
||||
/// NOTE: ALLOCATES
|
||||
/// </summary>
|
||||
/// <param name="array">Stores the read array</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValue<T>(out T[] array) where T : unmanaged
|
||||
{
|
||||
ReadValue(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
array = new T[sizeInTs];
|
||||
fixed (T* native = array)
|
||||
{
|
||||
byte* bytes = (byte*)(native);
|
||||
ReadBytes(bytes, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unmanaged array
|
||||
/// NOTE: ALLOCATES
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="array">Stores the read array</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValueSafe<T>(out T[] array) where T : unmanaged
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (Handle->InBitwiseContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!TryBeginReadInternal(sizeof(int)))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
}
|
||||
ReadValue(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
if (!TryBeginReadInternal(sizeInBytes))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
}
|
||||
array = new T[sizeInTs];
|
||||
fixed (T* native = array)
|
||||
{
|
||||
byte* bytes = (byte*)(native);
|
||||
ReadBytes(bytes, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it.
|
||||
/// </summary>
|
||||
@@ -711,69 +681,155 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a value of any unmanaged type to the buffer.
|
||||
/// It will be copied from the buffer exactly as it existed in memory on the writing end.
|
||||
/// </summary>
|
||||
/// <param name="value">The read value</param>
|
||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValue<T>(out T value) where T : unmanaged
|
||||
private unsafe void ReadUnmanaged<T>(out T value) where T : unmanaged
|
||||
{
|
||||
int len = sizeof(T);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (Handle->InBitwiseContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
|
||||
}
|
||||
if (Handle->Position + len > Handle->AllowedReadMark)
|
||||
{
|
||||
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
|
||||
}
|
||||
#endif
|
||||
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len);
|
||||
byte* bytes = (byte*)ptr;
|
||||
ReadBytes(bytes, sizeof(T));
|
||||
}
|
||||
Handle->Position += len;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a value of any unmanaged type to the buffer.
|
||||
/// It will be copied from the buffer exactly as it existed in memory on the writing end.
|
||||
///
|
||||
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
|
||||
/// for multiple reads at once by calling TryBeginRead.
|
||||
/// </summary>
|
||||
/// <param name="value">The read value</param>
|
||||
/// <typeparam name="T">Any unmanaged type</typeparam>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void ReadValueSafe<T>(out T value) where T : unmanaged
|
||||
private unsafe void ReadUnmanagedSafe<T>(out T value) where T : unmanaged
|
||||
{
|
||||
int len = sizeof(T);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (Handle->InBitwiseContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!TryBeginReadInternal(len))
|
||||
{
|
||||
throw new OverflowException("Reading past the end of the buffer");
|
||||
}
|
||||
|
||||
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len);
|
||||
byte* bytes = (byte*)ptr;
|
||||
ReadBytesSafe(bytes, sizeof(T));
|
||||
}
|
||||
Handle->Position += len;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanaged<T>(out T[] value) where T : unmanaged
|
||||
{
|
||||
ReadUnmanaged(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
value = new T[sizeInTs];
|
||||
fixed (T* ptr = value)
|
||||
{
|
||||
byte* bytes = (byte*)ptr;
|
||||
ReadBytes(bytes, sizeInBytes);
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void ReadUnmanagedSafe<T>(out T[] value) where T : unmanaged
|
||||
{
|
||||
ReadUnmanagedSafe(out int sizeInTs);
|
||||
int sizeInBytes = sizeInTs * sizeof(T);
|
||||
value = new T[sizeInTs];
|
||||
fixed (T* ptr = value)
|
||||
{
|
||||
byte* bytes = (byte*)ptr;
|
||||
ReadBytesSafe(bytes, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => ReadUnmanagedSafe(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe<T>(out T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new() => ReadNetworkSerializable(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2 value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector2[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3 value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector3[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector4 value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Vector4[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Quaternion value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Quaternion[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color32 value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Color32[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray[] value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray2D value) => ReadUnmanaged(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValue(out Ray2D[] value) => ReadUnmanaged(out value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2 value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector2[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3 value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector3[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector4 value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Vector4[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Quaternion value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Quaternion[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color32 value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Color32[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray[] value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray2D value) => ReadUnmanagedSafe(out value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ReadValueSafe(out Ray2D[] value) => ReadUnmanagedSafe(out value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -528,60 +529,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>
|
||||
@@ -790,68 +737,170 @@ 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
|
||||
private 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));
|
||||
}
|
||||
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.
|
||||
///
|
||||
/// "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>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void WriteValueSafe<T>(in T value) where T : unmanaged
|
||||
private unsafe void WriteUnmanagedSafe<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.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!TryBeginWriteInternal(len))
|
||||
{
|
||||
throw new OverflowException("Writing past the end of the buffer");
|
||||
}
|
||||
|
||||
fixed (T* ptr = &value)
|
||||
{
|
||||
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
|
||||
byte* bytes = (byte*)ptr;
|
||||
WriteBytesSafe(bytes, sizeof(T));
|
||||
}
|
||||
Handle->Position += len;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private 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)]
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
// These structs enable overloading of WriteValue with different generic constraints.
|
||||
// The compiler's actually able to distinguish between overloads based on generic constraints.
|
||||
// But at the bytecode level, the constraints aren't included in the method signature.
|
||||
// By adding a second parameter with a defaulted value, the signatures of each generic are different,
|
||||
// thus allowing overloads of methods based on the first parameter meeting constraints.
|
||||
public struct ForPrimitives
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public struct ForEnums
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public struct ForStructs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public struct ForNetworkSerializable
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T> => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForEnums unused = default) where T : unmanaged, Enum => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(in T value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe<T>(T[] value, ForNetworkSerializable unused = default) where T : INetworkSerializable => WriteNetworkSerializable(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector2 value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector2[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector3 value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector3[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Vector4 value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Vector4[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Quaternion value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Quaternion[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Color value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Color[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Color32 value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Color32[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Ray value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(in Ray2D value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValue(Ray2D[] value) => WriteUnmanaged(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector2 value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector2[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector3 value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector3[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Vector4 value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Vector4[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Quaternion value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Quaternion[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Color value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Color[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Color32 value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Color32[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Ray value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray[] value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(in Ray2D value) => WriteUnmanagedSafe(value);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueSafe(Ray2D[] value) => WriteUnmanagedSafe(value);
|
||||
}
|
||||
}
|
||||
|
||||
37
Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs
Normal file
37
Runtime/Serialization/ForceNetworkSerializeByMemcpy.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
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>
|
||||
{
|
||||
public T Value;
|
||||
|
||||
public ForceNetworkSerializeByMemcpy(T value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static implicit operator T(ForceNetworkSerializeByMemcpy<T> container) => container.Value;
|
||||
public static implicit operator ForceNetworkSerializeByMemcpy<T>(T underlyingValue) => new ForceNetworkSerializeByMemcpy<T> { Value = underlyingValue };
|
||||
|
||||
public bool Equals(ForceNetworkSerializeByMemcpy<T> other)
|
||||
{
|
||||
return Value.Equals(other.Value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ForceNetworkSerializeByMemcpy<T> other && Equals(other);
|
||||
}
|
||||
|
||||
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,3 +1,6 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
public interface IReaderWriter
|
||||
@@ -9,17 +12,60 @@ namespace Unity.Netcode
|
||||
FastBufferWriter GetFastBufferWriter();
|
||||
|
||||
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;
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
void SerializeValue<T>(ref T value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
void SerializeValue<T>(ref T[] value, FastBufferWriter.ForNetworkSerializable unused = default) where T : INetworkSerializable, new();
|
||||
void SerializeValue(ref Vector2 value);
|
||||
void SerializeValue(ref Vector2[] value);
|
||||
void SerializeValue(ref Vector3 value);
|
||||
void SerializeValue(ref Vector3[] value);
|
||||
void SerializeValue(ref Vector4 value);
|
||||
void SerializeValue(ref Vector4[] value);
|
||||
void SerializeValue(ref Quaternion value);
|
||||
void SerializeValue(ref Quaternion[] value);
|
||||
void SerializeValue(ref Color value);
|
||||
void SerializeValue(ref Color[] value);
|
||||
void SerializeValue(ref Color32 value);
|
||||
void SerializeValue(ref Color32[] value);
|
||||
void SerializeValue(ref Ray value);
|
||||
void SerializeValue(ref Ray[] value);
|
||||
void SerializeValue(ref Ray2D value);
|
||||
void SerializeValue(ref Ray2D[] value);
|
||||
|
||||
// Has to have a different name to avoid conflicting with "where T: unmananged"
|
||||
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
|
||||
|
||||
bool PreCheck(int amount);
|
||||
void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
|
||||
void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged;
|
||||
void SerializeValuePreChecked(ref byte value);
|
||||
void SerializeValuePreChecked<T>(ref T value) where T : unmanaged;
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForPrimitives unused = default) where T : unmanaged, IComparable, IConvertible, IComparable<T>, IEquatable<T>;
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForEnums unused = default) where T : unmanaged, Enum;
|
||||
void SerializeValuePreChecked<T>(ref T value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
void SerializeValuePreChecked<T>(ref T[] value, FastBufferWriter.ForStructs unused = default) where T : unmanaged, INetworkSerializeByMemcpy;
|
||||
|
||||
void SerializeValuePreChecked(ref Vector2 value);
|
||||
void SerializeValuePreChecked(ref Vector2[] value);
|
||||
void SerializeValuePreChecked(ref Vector3 value);
|
||||
void SerializeValuePreChecked(ref Vector3[] value);
|
||||
void SerializeValuePreChecked(ref Vector4 value);
|
||||
void SerializeValuePreChecked(ref Vector4[] value);
|
||||
void SerializeValuePreChecked(ref Quaternion value);
|
||||
void SerializeValuePreChecked(ref Quaternion[] value);
|
||||
void SerializeValuePreChecked(ref Color value);
|
||||
void SerializeValuePreChecked(ref Color[] value);
|
||||
void SerializeValuePreChecked(ref Color32 value);
|
||||
void SerializeValuePreChecked(ref Color32[] value);
|
||||
void SerializeValuePreChecked(ref Ray value);
|
||||
void SerializeValuePreChecked(ref Ray[] value);
|
||||
void SerializeValuePreChecked(ref Ray2D value);
|
||||
void SerializeValuePreChecked(ref Ray2D[] value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -136,23 +135,6 @@ namespace Unity.Netcode
|
||||
return OwnershipToObjectsTable[clientId].Values.ToList();
|
||||
}
|
||||
|
||||
|
||||
private struct TriggerData
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private readonly Dictionary<ulong, TriggerInfo> m_Triggers = new Dictionary<ulong, TriggerInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager associated with this SpawnManager.
|
||||
/// </summary>
|
||||
@@ -209,87 +191,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
|
||||
///
|
||||
/// 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)
|
||||
@@ -382,6 +283,33 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasPrefab(bool isSceneObject, uint globalObjectIdHash)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !isSceneObject)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(globalObjectIdHash, 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(globalObjectIdHash);
|
||||
return networkObject != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should only run on the client
|
||||
/// </summary>
|
||||
@@ -612,20 +540,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>();
|
||||
|
||||
@@ -81,7 +81,10 @@ namespace Unity.Netcode
|
||||
: this(tickRate)
|
||||
{
|
||||
Assert.IsTrue(tickOffset < 1d / tickRate);
|
||||
this += tick * m_TickInterval + tickOffset;
|
||||
|
||||
m_CachedTickOffset = tickOffset;
|
||||
m_CachedTick = tick;
|
||||
m_TimeSec = tick * m_TickInterval + tickOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -205,8 +205,8 @@ namespace Unity.Netcode.Transports.UNET
|
||||
public override bool StartServer()
|
||||
{
|
||||
var topology = new HostTopology(GetConfig(), MaxConnections);
|
||||
UnityEngine.Networking.NetworkTransport.AddHost(topology, ServerListenPort, null);
|
||||
return true;
|
||||
// Undocumented, but AddHost returns -1 in case of any type of failure. See UNET::NetLibraryManager::AddHost
|
||||
return -1 != UnityEngine.Networking.NetworkTransport.AddHost(topology, ServerListenPort, null);
|
||||
}
|
||||
|
||||
public override void DisconnectRemoteClient(ulong clientId)
|
||||
|
||||
Reference in New Issue
Block a user