[Add] FishNet

This commit is contained in:
2026-03-30 20:11:57 +07:00
parent ee793a3361
commit c22c08753a
1797 changed files with 197950 additions and 1 deletions
@@ -0,0 +1,28 @@
using FishNet.Broadcast;
using FishNet.Managing;
using FishNet.Transporting;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
/// <summary>
/// Sends a broadcast to Observers on this NetworkObject.
/// </summary>
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
/// <param name = "requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
/// <param name = "channel">Channel to send on.</param>
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
{
if (NetworkManager == null)
{
NetworkManager.LogWarning($"Cannot send broadcast from {gameObject.name}, NetworkManager reference is null. This may occur if the object is not spawned or initialized.");
return;
}
NetworkManager.ServerManager.Broadcast(Observers, message, requireAuthenticated, channel);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 55d793117b52da549affcc9ec30b05c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Broadcast.cs
uploadId: 866910
@@ -0,0 +1,182 @@
using FishNet.Connection;
using System.Runtime.CompilerServices;
using FishNet.Serializing;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
#region Private.
/// <summary>
/// True if OnStartServer was called.
/// </summary>
private bool _onStartServerCalled;
/// <summary>
/// True if OnStartClient was called.
/// </summary>
private bool _onStartClientCalled;
#endregion
// ReSharper disable Unity.PerformanceAnalysis
/// <summary>
/// Called after all data is synchronized with this NetworkObject.
/// </summary>
private void InvokeStartCallbacks(bool asServer, bool invokeSyncTypeCallbacks)
{
/* Note: When invoking OnOwnership here previous owner will
* always be an empty connection, since the object is just
* now initializing. */
// Invoke OnStartNetwork.
bool invokeOnNetwork = asServer || IsServerOnlyStarted || IsClientOnlyInitialized;
if (invokeOnNetwork)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].InvokeOnNetwork_Internal(start: true);
}
//As server.
if (asServer)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnStartServer_Internal();
_onStartServerCalled = true;
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnOwnershipServer_Internal(Managing.NetworkManager.EmptyConnection);
}
//As client.
else
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnStartClient_Internal();
_onStartClientCalled = true;
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnOwnershipClient_Internal(Managing.NetworkManager.EmptyConnection);
}
if (invokeSyncTypeCallbacks)
InvokeOnStartSyncTypeCallbacks(true);
InvokeStartCallbacks_Prediction(asServer);
}
/// <summary>
/// Invokes OnStartXXXX for synctypes, letting them know the NetworkBehaviour start cycle has been completed.
/// </summary>
internal void InvokeOnStartSyncTypeCallbacks(bool asServer)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].InvokeSyncTypeOnStartCallbacks(asServer);
}
/// <summary>
/// Invokes OnStopXXXX for synctypes, letting them know the NetworkBehaviour stop cycle is about to start.
/// </summary>
internal void InvokeOnStopSyncTypeCallbacks(bool asServer)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].InvokeSyncTypeOnStopCallbacks(asServer);
}
/// <summary>
/// Invokes events to be called after OnServerStart.
/// This is made one method to save instruction calls.
/// </summary>
/// <param name = ""></param>
internal void OnSpawnServer(NetworkConnection conn)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].SendBufferedRpcs(conn);
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnSpawnServer(conn);
}
/// <summary>
/// Called on the server before it sends a despawn message to a client.
/// </summary>
/// <param name = "conn">Connection spawn was sent to.</param>
internal void InvokeOnServerDespawn(NetworkConnection conn)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnDespawnServer(conn);
}
// ReSharper disable Unity.PerformanceAnalysis
/// <summary>
/// Invokes OnStop callbacks.
/// </summary>
internal void InvokeStopCallbacks(bool asServer, bool invokeSyncTypeCallbacks)
{
InvokeStopCallbacks_Prediction(asServer);
if (invokeSyncTypeCallbacks)
InvokeOnStopSyncTypeCallbacks(asServer);
if (asServer && _onStartServerCalled)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnStopServer_Internal();
if (!_onStartClientCalled)
InvokeOnNetwork();
_onStartServerCalled = false;
}
else if (!asServer && _onStartClientCalled)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnStopClient_Internal();
/* Only invoke OnNetwork if server start isn't called, otherwise
* that means this is still intialized on the server. This would
* happen if the object despawned for the clientHost but not on the
* server. */
if (!_onStartServerCalled)
InvokeOnNetwork();
_onStartClientCalled = false;
}
void InvokeOnNetwork()
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].InvokeOnNetwork_Internal(start: false);
}
}
/// <summary>
/// Invokes OnOwnership callbacks when ownership changes.
/// This is not to be called when assigning ownership during a spawn message.
/// </summary>
private void InvokeManualOwnershipChange(NetworkConnection prevOwner, bool asServer)
{
if (asServer)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnOwnershipServer_Internal(prevOwner);
WriteSyncTypesForManualOwnershipChange(prevOwner);
}
else
{
/* If local client is owner and not server then only
* invoke if the prevOwner is different. This prevents
* the owner change callback from happening twice when
* using TakeOwnership.
*
* Further explained, the TakeOwnership sets local client
* as owner client-side, which invokes the OnOwnership method.
* Then when the server approves the owner change it would invoke
* again, which is not needed. */
bool blockInvoke = IsOwner && !IsServerStarted && prevOwner == Owner;
if (!blockInvoke)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].OnOwnershipClient_Internal(prevOwner);
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8e9fbf0d6eb10e94d892dd4e817030bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs
uploadId: 866910
@@ -0,0 +1,296 @@
using FishNet.Component.Observing;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Observing;
using System;
using System.Collections.Generic;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
#region Public.
/// <summary>
/// Called when the clientHost gains or loses visibility of this object.
/// Boolean value will be true if clientHost has visibility.
/// </summary>
public event HostVisibilityUpdatedDelegate OnHostVisibilityUpdated;
/// <summary>
/// </summary>
/// <param name = "prevVisible">True if clientHost was known to have visibility of the object prior to this invoking.</param>
/// <param name = "nextVisible">True if the clientHost now has visibility of the object.</param>
public delegate void HostVisibilityUpdatedDelegate(bool prevVisible, bool nextVisible);
/// <summary>
/// Called when this NetworkObject losses all observers or gains observers while previously having none.
/// </summary>
public event Action<NetworkObject> OnObserversActive;
/// <summary>
/// NetworkObserver on this object.
/// </summary>
[HideInInspector]
public NetworkObserver NetworkObserver = null;
/// <summary>
/// Clients which can see and get messages from this NetworkObject.
/// </summary>
[HideInInspector]
public HashSet<NetworkConnection> Observers = new();
#endregion
#region Internal.
/// <summary>
/// Current HashGrid entry this belongs to.
/// </summary>
internal GridEntry HashGridEntry;
/// <summary>
/// Last tick an observer was added.
/// </summary>
internal uint ObserverAddedTick = TimeManager.UNSET_TICK;
#endregion
#region Private.
/// <summary>
/// True if NetworkObserver has been initialized.
/// </summary>
private bool _networkObserverInitiliazed = false;
/// <summary>
/// Found renderers on the NetworkObject and it's children. This is only used as clientHost to hide non-observers objects.
/// </summary>
[NonSerialized]
private List<Renderer> _renderers;
/// <summary>
/// True if renderers have been looked up.
/// </summary>
private bool _renderersPopulated;
/// <summary>
/// Last visibility value for clientHost on this object.
/// </summary>
private bool _lastClientHostVisibility;
/// <summary>
/// HashGrid for this object.
/// </summary>
private HashGrid _hashGrid;
/// <summary>
/// Next time this object may update it's position for HashGrid.
/// </summary>
private float _nextHashGridUpdateTime;
/// <summary>
/// True if this gameObject is static.
/// </summary>
private bool _isStatic;
/// <summary>
/// Current grid position.
/// </summary>
private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition;
#endregion
/// <summary>
/// Updates Objects positions in the HashGrid for this Networkmanager.
/// </summary>
internal void UpdateForNetworkObject(bool force)
{
if (_hashGrid == null)
return;
if (_isStatic)
return;
float unscaledTime = Time.unscaledTime;
// Not enough time has passed to update.
if (!force && unscaledTime < _nextHashGridUpdateTime)
return;
const float updateInterval = 1f;
_nextHashGridUpdateTime = unscaledTime + updateInterval;
Vector2Int newPosition = _hashGrid.GetHashGridPosition(this);
if (newPosition != _hashGridPosition)
{
_hashGridPosition = newPosition;
HashGridEntry = _hashGrid.GetGridEntry(newPosition);
}
}
/// <summary>
/// Updates cached renderers used to managing clientHost visibility.
/// </summary>
/// <param name = "updateVisibility">True to also update visibility if clientHost.</param>
public void UpdateRenderers(bool updateVisibility = true)
{
InitializeRendererCollection(force: true, updateVisibility);
}
/// <summary>
/// Sets the renderer visibility for clientHost.
/// </summary>
/// <param name = "visible">True if renderers are to be visibile.</param>
/// <param name = "force">True to skip blocking checks.</param>
public void SetRenderersVisible(bool visible, bool force = false)
{
if (!force && !NetworkObserver.UpdateHostVisibility)
return;
UpdateRenderVisibility(visible);
}
/// <summary>
/// Updates visibilites on renders without checks.
/// </summary>
/// <param name = "visible"></param>
private void UpdateRenderVisibility(bool visible)
{
InitializeRendererCollection(force: false, updateVisibility: false);
List<Renderer> rs = _renderers;
for (int i = 0; i < rs.Count; i++)
{
Renderer r = rs[i];
if (r == null)
{
_renderers.RemoveAt(i);
i--;
}
else
{
r.enabled = visible;
}
}
if (OnHostVisibilityUpdated != null)
OnHostVisibilityUpdated.Invoke(_lastClientHostVisibility, visible);
_lastClientHostVisibility = visible;
}
/// <summary>
/// If needed Renderers collection is initialized and populated.
/// </summary>
private void InitializeRendererCollection(bool force, bool updateVisibility)
{
if (!force && _renderersPopulated)
return;
List<Renderer> cache = CollectionCaches<Renderer>.RetrieveList();
GetComponentsInChildren<Renderer>(includeInactive: true, cache);
_renderers = new();
foreach (Renderer r in cache)
{
if (r.enabled)
_renderers.Add(r);
}
CollectionCaches<Renderer>.Store(cache);
/* Intentionally set before event call. This is to prevent
* a potential endless loop should the user make another call
* to this objects renderer API from the event, resulting in
* the population repeating. */
_renderersPopulated = true;
if (updateVisibility)
UpdateRenderVisibility(_lastClientHostVisibility);
}
/// <summary>
/// Adds the default NetworkObserver conditions using the ObserverManager.
/// </summary>
private void AddDefaultNetworkObserverConditions()
{
if (_networkObserverInitiliazed)
return;
NetworkObserver = NetworkManager.ObserverManager.AddDefaultConditions(this);
}
/// <summary>
/// Removes a connection from observers for this object returning if the connection was removed.
/// </summary>
/// <param name = "connection"></param>
internal bool RemoveObserver(NetworkConnection connection)
{
int startCount = Observers.Count;
bool removed = Observers.Remove(connection);
if (removed)
TryInvokeOnObserversActive(startCount);
return removed;
}
/// <summary>
/// Adds the connection to observers if conditions are met.
/// </summary>
/// <param name = "connection"></param>
/// <returns>True if added to Observers.</returns>
internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly)
{
// If not a valid connection.
if (!connection.IsValid)
{
NetworkManager.LogWarning($"An invalid connection was used when rebuilding observers.");
return ObserverStateChange.Unchanged;
}
// Valid not not active.
if (!connection.IsActive)
{
/* Just remove from observers since connection isn't active
* and return unchanged because nothing should process
* given the connection isnt active. */
Observers.Remove(connection);
return ObserverStateChange.Unchanged;
}
if (IsDeinitializing)
{
/* If object is deinitializing it's either being despawned
* this frame or it's not spawned. If we've made it this far,
* it's most likely being despawned. */
return ObserverStateChange.Unchanged;
}
// Update hashgrid if needed.
UpdateForNetworkObject(!timedOnly);
int startCount = Observers.Count;
ObserverStateChange osc = NetworkObserver.RebuildObservers(connection, timedOnly);
if (osc == ObserverStateChange.Added)
Observers.Add(connection);
else if (osc == ObserverStateChange.Removed)
Observers.Remove(connection);
if (osc != ObserverStateChange.Unchanged)
TryInvokeOnObserversActive(startCount);
return osc;
}
/// <summary>
/// Invokes OnObserversActive if observers are now 0 but previously were not, or if was previously 0 but now has observers.
/// </summary>
/// <param name = "startCount"></param>
private void TryInvokeOnObserversActive(int startCount)
{
if (TimeManager != null)
ObserverAddedTick = TimeManager.LocalTick;
if (OnObserversActive != null)
{
if ((Observers.Count > 0 && startCount == 0) || (Observers.Count == 0 && startCount > 0))
OnObserversActive.Invoke(this);
}
}
/// <summary>
/// Resets this object to starting values.
/// </summary>
private void ResetState_Observers(bool asServer)
{
// As server or client it's safe to reset this value.
ObserverAddedTick = TimeManager.UNSET_TICK;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 816dbea70a70ab949a44f485155f0087
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Observers.cs
uploadId: 866910
@@ -0,0 +1,692 @@
#define NEW_RECONCILE_TEST
using System;
using FishNet.Component.Prediction;
using FishNet.Component.Transforming;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Object.Prediction;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using FishNet.Connection;
using FishNet.Managing.Server;
using Unity.Profiling;
using UnityEngine;
#pragma warning disable CS0618 // Type or member is obsolete
// ReSharper disable once CheckNamespace
namespace FishNet.Object
{
public partial class NetworkObject
{
#region Types.
/// <summary>
/// Type of prediction movement being used.
/// </summary>
[Serializable]
internal enum PredictionType : byte
{
Other = 0,
Rigidbody = 1,
Rigidbody2D = 2
}
/// <summary>
/// How to correct, or reset a rigidbody transform after a reconcile when the reconcile state is local, and the rigidbody has near nil differences from when the reconcile started.
/// </summary>
/// <remarks>Due to physics not being deterministic a reconcile can cause a rigidbody to finish with different results than what it started it, even if the rigidbody did not experience any difference in forces. These options allow FishNet to reset the rigidbody to as it were before the reconcile if the differences are minor enough. By resetting values de-synchronization and subtly observed shaking can be prevented or significantly reduced.</remarks>
[Serializable]
internal enum RigidbodyLocalReconcileCorrectionType : byte
{
/// <summary>
/// Do not make corrections.
/// </summary>
Disabled = 0,
/// <summary>
/// Only reset the transform.
/// </summary>
TransformOnly = 1,
/* Velocities support will be available next release.
* To support velocities as well PreReconcilingTransformProperties must
* also store each rigidbody associated with the transform. This should not
* be too difficult given we already check for a rb to exist before adding
* the transform.
*
* When adding velocities support only add velocity data if feature
* it set to reset velocities; same applies when comparing and resetting.
* */
/// <summary>
/// Reset the transform and rigidbody velocities.
/// </summary>
/// <remarks>This setting is included even though it is not yet functional so that it becomes effective immediately on availability should it be the selected option.</remarks>
TransformAndVelocities = 2
}
/// <summary>
/// Properties of a Transform and properties associated with it.
/// </summary>
internal class PreReconcilingTransformProperties : IResettable
{
/// <summary>
/// NetworkBehaviours that are predicted on the transform.
/// </summary>
public readonly List<NetworkBehaviour> NetworkBehaviours = new();
/// <summary>
/// Transform on the first added NetworkBehaviour.
/// </summary>
public Transform Transform { get; private set; }
/// <summary>
/// Properties of the transform during PreReconcile.
/// </summary>
public TransformProperties Properties;
// ReSharper disable once EmptyConstructor
public PreReconcilingTransformProperties() { }
public void AddNetworkBehaviour(NetworkBehaviour networkBehaviour)
{
NetworkBehaviours.Add(networkBehaviour);
if (Transform == null)
Transform = networkBehaviour.transform;
}
public void ResetState()
{
NetworkBehaviours.Clear();
Transform = null;
}
public void InitializeState() { }
}
#endregion
#region Public.
/// <summary>
/// True if a reconcile is occuring on any NetworkBehaviour that is on or nested of this NetworkObject. Runtime NetworkBehaviours are not included, such as if you child a NetworkObject to another at runtime.
/// </summary>
public bool IsObjectReconciling { get; internal set; }
/// <summary>
/// Graphical smoother to use when using set for owner.
/// </summary>
[Obsolete("This field will be removed in v5. Instead reference NetworkTickSmoother on each graphical object used.")]
public TransformTickSmoother PredictionSmoother { get; private set; }
#endregion
#region Internal.
/// <summary>
/// Pauses and unpauses rigidbodies when they do not have data to reconcile to.
/// </summary>
public RigidbodyPauser RigidbodyPauser => _rigidbodyPauser;
private RigidbodyPauser _rigidbodyPauser;
/// <summary>
/// True if PredictionType is set to a rigidbody value.
/// </summary>
internal bool IsRigidbodyPredictionType;
#endregion
#region Serialized.
/// <summary>
/// True if this object uses prediciton methods.
/// </summary>
public bool EnablePrediction => _enablePrediction;
[Tooltip("True if this object uses prediction methods.")]
[SerializeField]
private bool _enablePrediction;
/// <summary>
/// What type of component is being used for prediction? If not using rigidbodies set to other.
/// </summary>
[Tooltip("What type of component is being used for prediction? If not using rigidbodies set to other.")]
[SerializeField]
private PredictionType _predictionType = PredictionType.Other;
/// <summary>
/// Object state corrections to apply after replaying from a local state when non-deterministic physics have possibly provided a different result under the same conditions.
/// </summary>
[Tooltip("Object state corrections to apply after replaying from a local state when non-deterministic physics have possibly provided a different result under the same conditions.")]
[SerializeField]
private RigidbodyLocalReconcileCorrectionType _localReconcileCorrectionType = RigidbodyLocalReconcileCorrectionType.TransformAndVelocities;
/// <summary>
/// Object containing graphics when using prediction. This should be child of the predicted root.
/// </summary>
[Tooltip("Object containing graphics when using prediction. This should be child of the predicted root.")]
[SerializeField]
private Transform _graphicalObject;
/// <summary>
/// Gets the current graphical object for prediction.
/// </summary>
/// <returns></returns>
public Transform GetGraphicalObject() => _graphicalObject;
/// <summary>
/// Sets a new graphical object for prediction.
/// </summary>
/// <param name = "t"></param>
public void SetGraphicalObject(Transform t)
{
_graphicalObject = t;
InitializeTickSmoother();
}
/// <summary>
/// True to detach and re-attach the graphical object at runtime when the client initializes/deinitializes the item.
/// This can resolve camera jitter or be helpful objects child of the graphical which do not handle reconiliation well, such as certain animation rigs.
/// Transform is detached after OnStartClient, and reattached before OnStopClient.
/// </summary>
[Tooltip("True to detach and re-attach the graphical object at runtime when the client initializes/deinitializes the item. This can resolve camera jitter or be helpful objects child of the graphical which do not handle reconiliation well, such as certain animation rigs. Transform is detached after OnStartClient, and reattached before OnStopClient.")]
[SerializeField]
private bool _detachGraphicalObject;
/// <summary>
/// True to forward replicate and reconcile states to all clients. This is ideal with games where you want all clients and server to run the same inputs. False to only use prediction on the owner, and synchronize to spectators using other means such as a NetworkTransform.
/// </summary>
public bool EnableStateForwarding => _enablePrediction && _enableStateForwarding;
[Tooltip("True to forward replicate and reconcile states to all clients. This is ideal with games where you want all clients and server to run the same inputs. False to only use prediction on the owner, and synchronize to spectators using other means such as a NetworkTransform.")]
[SerializeField]
private bool _enableStateForwarding = true;
/// <summary>
/// NetworkTransform to configure for prediction. Specifying this is optional.
/// </summary>
[Tooltip("NetworkTransform to configure for prediction. Specifying this is optional.")]
[SerializeField]
private NetworkTransform _networkTransform;
internal NetworkTransform PredictionNetworkTransform => _networkTransform;
/// <summary>
/// How many ticks to interpolate graphics on objects owned by the client. Typically low as 1 can be used to smooth over the frames between ticks.
/// </summary>
[Tooltip("How many ticks to interpolate graphics on objects owned by the client. Typically low as 1 can be used to smooth over the frames between ticks.")]
[Range(1, byte.MaxValue)]
[SerializeField]
private byte _ownerInterpolation = 1;
/// <summary>
/// Properties of the graphicalObject to smooth when owned.
/// </summary>
[SerializeField]
private TransformPropertiesFlag _ownerSmoothedProperties = (TransformPropertiesFlag)~(-1 << 8);
/// <summary>
/// Interpolation amount of adaptive interpolation to use on non-owned objects. Higher levels result in more interpolation. When off spectatorInterpolation is used; when on interpolation based on strength and local client latency is used.
/// </summary>
[Tooltip("Interpolation amount of adaptive interpolation to use on non-owned objects. Higher levels result in more interpolation. When off spectatorInterpolation is used; when on interpolation based on strength and local client latency is used.")]
[SerializeField]
private AdaptiveInterpolationType _adaptiveInterpolation = AdaptiveInterpolationType.Low;
/// <summary>
/// Properties of the graphicalObject to smooth when the object is spectated.
/// </summary>
[SerializeField]
private TransformPropertiesFlag _spectatorSmoothedProperties = (TransformPropertiesFlag)~(-1 << 8);
/// <summary>
/// How many ticks to interpolate graphics on objects when not owned by the client.
/// </summary>
[Tooltip("How many ticks to interpolate graphics on objects when not owned by the client.")]
[Range(1, byte.MaxValue)]
[SerializeField]
private byte _spectatorInterpolation = 2;
/// <summary>
/// True to enable teleport threshhold.
/// </summary>
[Tooltip("True to enable teleport threshhold.")]
[SerializeField]
private bool _enableTeleport;
/// <summary>
/// Distance the graphical object must move between ticks to teleport the transform properties.
/// </summary>
[Tooltip("Distance the graphical object must move between ticks to teleport the transform properties.")]
[Range(0.001f, ushort.MaxValue)]
[SerializeField]
private float _teleportThreshold = 1f;
#endregion
#region Private.
/// <summary>
/// True if prediction behaviours have already been registered.
/// </summary>
private bool _predictionBehavioursRegistered;
/// <summary>
/// NetworkBehaviours which use prediction.
/// </summary>
private HashSet<NetworkBehaviour> _predictionBehaviours;
/// <summary>
/// Properties of a transform before reconcile when the transform may be affected by a rigidbody.
/// </summary>
private Dictionary<Transform, PreReconcilingTransformProperties> _rigidbodyTransformsPreReconcileProperties;
/// <summary>
/// Values which were updated within <see cref="_rigidbodyTransformsPreReconcileProperties"/> during preReconcile.
/// </summary>
private List<PreReconcilingTransformProperties> _updatedPreReconcilingTransformProperties;
#endregion
#region Private Profiler Markers
private static readonly ProfilerMarker _pm_OnPreTick = new("NetworkObject.TimeManager_OnPreTick()");
private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new("NetworkObject.PredictionManager_OnPostReplicateReplay(uint, uint)");
private static readonly ProfilerMarker _pm_OnPostTick = new("NetworkObject.TimeManager_OnPostTick()");
private static readonly ProfilerMarker _pm_OnPreReconcile = new("NetworkObject.PredictionManager_OnPreReconcile(uint, uint)");
private static readonly ProfilerMarker _pm_OnReconcile = new("NetworkObject.PredictionManager_OnReconcile(uint, uint)");
private static readonly ProfilerMarker _pm_OnPostReconcile = new("NetworkObject.PredictionManager_OnPostReconcile(uint, uint)");
private static readonly ProfilerMarker _pm_OnReplicateReplay = new("NetworkObject.PredictionManager_OnReplicateReplay(uint, uint)");
#endregion
private void TimeManager_OnUpdate_Prediction()
{
if (!_enablePrediction)
return;
if (PredictionSmoother != null)
PredictionSmoother.OnUpdate();
}
private void InitializeEarly_Prediction(NetworkManager manager, bool asServer)
{
if (!_enablePrediction)
return;
if (!_enableStateForwarding && _networkTransform != null)
_networkTransform.ConfigureForPrediction(_predictionType);
IsRigidbodyPredictionType = _predictionType == PredictionType.Rigidbody || _predictionType == PredictionType.Rigidbody2D;
if (!_predictionBehavioursRegistered)
{
foreach (NetworkBehaviour behaviour in NetworkBehaviours)
{
TryRegisterPredictionBehaviour(behaviour);
RegisterPredictionRigidbodyTransform(behaviour);
}
_predictionBehavioursRegistered = true;
}
if (!asServer)
InitializeSmoothers();
ChangePredictionSubscriptions(true, manager, asServer);
}
private void Deinitialize_Prediction(bool asServer)
{
if (!_enablePrediction)
return;
DeinitializeSmoothers();
ChangePredictionSubscriptions(subscribe: false, NetworkManager, asServer);
}
/// <summary>
/// Changes subscriptions to use callbacks for prediction.
/// </summary>
private void ChangePredictionSubscriptions(bool subscribe, NetworkManager manager, bool asServer)
{
/* Only the client needs to unsubscribe from these but
* asServer may not invoke as false if the client is suddenly
* dropping their connection. */
if (asServer && subscribe)
return;
if (manager == null)
return;
if (_predictionBehaviours.Count == 0)
return;
if (subscribe)
{
manager.PredictionManager.OnPreReconcile += PredictionManager_OnPreReconcile;
manager.PredictionManager.OnReconcile += PredictionManager_OnReconcile;
manager.PredictionManager.OnReplicateReplay += PredictionManager_OnReplicateReplay;
manager.PredictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
manager.PredictionManager.OnPostReconcile += PredictionManager_OnPostReconcile;
manager.TimeManager.OnPreTick += TimeManager_OnPreTick;
manager.TimeManager.OnPostTick += TimeManager_OnPostTick;
}
else
{
manager.PredictionManager.OnPreReconcile -= PredictionManager_OnPreReconcile;
manager.PredictionManager.OnReconcile -= PredictionManager_OnReconcile;
manager.PredictionManager.OnReplicateReplay -= PredictionManager_OnReplicateReplay;
manager.PredictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
manager.PredictionManager.OnPostReconcile -= PredictionManager_OnPostReconcile;
manager.TimeManager.OnPreTick -= TimeManager_OnPreTick;
manager.TimeManager.OnPostTick -= TimeManager_OnPostTick;
}
}
/// <summary>
/// Initializes tick smoothing.
/// </summary>
private void InitializeSmoothers()
{
if (IsRigidbodyPredictionType)
{
_rigidbodyPauser = ResettableObjectCaches<RigidbodyPauser>.Retrieve();
RigidbodyType rbType = _predictionType == PredictionType.Rigidbody ? RigidbodyType.Rigidbody : RigidbodyType.Rigidbody2D;
_rigidbodyPauser.UpdateRigidbodies(transform, rbType, getInChildren: true);
}
if (_graphicalObject == null)
{
NetworkManager.Log($"GraphicalObject is null on {gameObject.name}. This may be intentional, and acceptable, if you are smoothing between ticks yourself. Otherwise consider assigning the GraphicalObject field.");
}
else
{
if (PredictionSmoother == null)
PredictionSmoother = ResettableObjectCaches<TransformTickSmoother>.Retrieve();
InitializeTickSmoother();
}
}
/// <summary>
/// Initializes the tick smoother.
/// </summary>
private void InitializeTickSmoother()
{
if (PredictionSmoother == null)
return;
float teleportT = _enableTeleport ? _teleportThreshold : MoveRates.UNSET_VALUE;
PredictionSmoother.InitializeNetworked(this, _graphicalObject, _detachGraphicalObject, teleportT, (float)TimeManager.TickDelta, _ownerInterpolation, _ownerSmoothedProperties, _spectatorInterpolation, _spectatorSmoothedProperties, _adaptiveInterpolation);
}
/// <summary>
/// Initializes tick smoothing.
/// </summary>
private void DeinitializeSmoothers()
{
if (PredictionSmoother != null)
{
PredictionSmoother.Deinitialize();
ResettableObjectCaches<TransformTickSmoother>.Store(PredictionSmoother);
PredictionSmoother = null;
ResettableObjectCaches<RigidbodyPauser>.StoreAndDefault(ref _rigidbodyPauser);
}
}
private void InvokeStartCallbacks_Prediction(bool asServer)
{
if (!asServer)
{
TimeManager.OnUpdate += TimeManager_Update;
if (PredictionSmoother != null)
PredictionSmoother.OnStartClient();
}
}
private void InvokeStopCallbacks_Prediction(bool asServer)
{
if (!asServer)
return;
if (TimeManager != null)
TimeManager.OnUpdate -= TimeManager_Update;
if (PredictionSmoother != null)
PredictionSmoother.OnStopClient();
}
private void TimeManager_OnPreTick()
{
using (_pm_OnPreTick.Auto())
{
if (PredictionSmoother != null)
PredictionSmoother.OnPreTick();
}
}
private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick)
{
using (_pm_OnPostReplicateReplay.Auto())
{
if (PredictionSmoother != null)
PredictionSmoother.OnPostReplicateReplay(clientTick);
}
}
private void TimeManager_OnPostTick()
{
using (_pm_OnPostTick.Auto())
{
if (PredictionSmoother != null)
PredictionSmoother.OnPostTick(NetworkManager.TimeManager.LocalTick);
}
}
private void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick)
{
using (_pm_OnPreReconcile.Auto())
{
if (IsClientInitialized)
{
/* Always call clear. It's cheap and will prevent possible issues
* should users be toggling related settings during testing. */
_updatedPreReconcilingTransformProperties.Clear();
//Rigidbody corrections.
if (_localReconcileCorrectionType != RigidbodyLocalReconcileCorrectionType.Disabled)
{
foreach (KeyValuePair<Transform, PreReconcilingTransformProperties> kvp in _rigidbodyTransformsPreReconcileProperties)
{
PreReconcilingTransformProperties tpc = kvp.Value;
bool addedEntry = false;
foreach (NetworkBehaviour nb in tpc.NetworkBehaviours)
{
//Only update transform data if reconciling using local data.
if (nb.IsBehaviourReconciling && !nb.IsReconcileRemote)
{
tpc.Properties.Update(kvp.Key);
_updatedPreReconcilingTransformProperties.Add(tpc);
addedEntry = true;
break;
}
}
//Can exit after updating when any NetworkBehaviour is reconciling for the Transform.
if (addedEntry)
break;
}
}
}
if (PredictionSmoother != null)
PredictionSmoother.OnPreReconcile();
}
}
private void PredictionManager_OnReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
using (_pm_OnReconcile.Auto())
{
if (!IsClientInitialized)
return;
/* Tell all prediction behaviours to set/validate their
* reconcile data now. This will use reconciles from the server
* whenever possible, and local reconciles if a server reconcile
* is not available. */
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
networkBehaviour.Reconcile_Client_Start();
/* If still not reconciling then pause rigidbody.
* This shouldn't happen unless the user is not calling
* reconcile at all. */
if (!IsObjectReconciling)
{
if (_rigidbodyPauser != null)
_rigidbodyPauser.Pause();
}
}
}
private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
foreach (NetworkBehaviour nbb in _predictionBehaviours)
nbb.IsReconcileRemote = false;
using (_pm_OnPostReconcile.Auto())
{
if (!IsClientInitialized)
return;
if (_localReconcileCorrectionType != RigidbodyLocalReconcileCorrectionType.Disabled)
{
/* Check changes in transform for every transform
* which utilizes prediction and a rigidbody, and
* may have changed since preReconcile. */
foreach (PreReconcilingTransformProperties prtp in _updatedPreReconcilingTransformProperties)
{
/* If transform has not changed enough to matter
* then reset values as they were before the reconcile. */
if (!LHasTransformChanged())
prtp.Properties.SetWorldProperties(prtp.Transform);
bool LHasTransformChanged()
{
const float v3Distance = 0.000025f;
const float angleDistance = 0.2f;
bool hasChanged = (transform.position - prtp.Properties.Position).sqrMagnitude >= v3Distance;
if (!hasChanged)
hasChanged = transform.rotation.Angle(prtp.Properties.Rotation, precise: true) >= angleDistance;
return hasChanged;
}
}
}
//This is cleared before the reconcile as well, but no point to keep behaviours in memory if not needed.
/* Always call clear. It's cheap and will prevent possible issues
* should users be toggling related settings during testing. */
_updatedPreReconcilingTransformProperties.Clear();
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
networkBehaviour.Reconcile_Client_End();
/* Unpause rigidbody pauser. It's okay to do that here rather
* than per NB, where the pausing occurs, because once here
* the entire object is out of the replay cycle so there's
* no reason to try and unpause per NB. */
if (_rigidbodyPauser != null)
_rigidbodyPauser.Unpause();
IsObjectReconciling = false;
}
}
private void PredictionManager_OnReplicateReplay(uint clientTick, uint serverTick)
{
using (_pm_OnReplicateReplay.Auto())
{
if (!IsClientInitialized)
return;
uint replayTick = IsOwner ? clientTick : serverTick;
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
networkBehaviour.Replicate_Replay_Start(replayTick);
}
}
/// <summary>
/// Registers a NetworkBehaviour if it uses prediction.
/// </summary>
/// <returns>True if behavior was registered or already registered.</returns>
// ReSharper disable once UnusedMethodReturnValue.Local
private bool TryRegisterPredictionBehaviour(NetworkBehaviour nb)
{
if (!nb.UsesPrediction)
return false;
_predictionBehaviours.Add(nb);
return true;
}
/// <summary>
/// Registers a NetworkBehaviour's Transform if the behaviour uses prediction and has a rigidbody on it.
/// </summary>
/// <returns>True if behavior was just registered, or already registered.</returns>
private void RegisterPredictionRigidbodyTransform(NetworkBehaviour nb)
{
if (!nb.UsesPrediction)
return;
Transform t = nb.transform;
/* Check if the transform is already registered. This will prevent
* checking for rigidbodies multiple times on the same transform if more
* than one prediction script exist on the same transform. */
if (!_rigidbodyTransformsPreReconcileProperties.TryGetValueIL2CPP(t, out PreReconcilingTransformProperties prtp))
{
prtp = ResettableObjectCaches<PreReconcilingTransformProperties>.Retrieve();
_rigidbodyTransformsPreReconcileProperties[t] = prtp;
}
//Only transforms with rigidbodies need to be registered.
if (t.TryGetComponent(out Rigidbody _) || t.TryGetComponent(out Rigidbody2D _))
prtp.AddNetworkBehaviour(nb);
}
/// <summary>
/// Clears replication queue inserting them into the past replicates history when possible.
/// This should only be called when client only.
/// </summary>
internal void EmptyReplicatesQueueIntoHistory()
{
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
networkBehaviour.EmptyReplicatesQueueIntoHistory_Start();
}
/// <summary>
/// Sets the last tick a NetworkBehaviour replicated with.
/// </summary>
/// <param name = "setUnordered">True to set unordered value, false to set ordered.</param>
internal void SetReplicateTick(uint value, bool createdReplicate)
{
if (createdReplicate && Owner.IsValid)
// ReSharper disable once RedundantArgumentDefaultValue
Owner.ReplicateTick.Update(NetworkManager.TimeManager, value, EstimatedTick.OldTickOption.Discard);
}
}
/// <summary>
/// Place this component on your NetworkManager object to remove ownership of objects for a disconnecting client.
/// This prevents any owned object from being despawned when the owner disconnects.
/// </summary>
public class GlobalPreserveOwnedObjects : MonoBehaviour
{
private void Awake()
{
ServerManager sm = GetComponent<ServerManager>();
sm.Objects.OnPreDestroyClientObjects += Objects_OnPreDestroyClientObjects;
}
protected virtual void Objects_OnPreDestroyClientObjects(NetworkConnection conn)
{
foreach (NetworkObject networkObject in conn.Objects)
networkObject.RemoveOwnership();
}
}
/// <summary>
/// Place this component on NetworkObjects you wish to remove ownership on for a disconnecting owner.
/// This prevents the object from being despawned when the owner disconnects.
/// </summary>
public class NetworkPreserveOwnedObjects : NetworkBehaviour
{
public override void OnStartServer()
{
ServerManager.Objects.OnPreDestroyClientObjects += OnPreDestroyClientObjects;
}
public override void OnStopServer()
{
if (ServerManager != null)
ServerManager.Objects.OnPreDestroyClientObjects -= OnPreDestroyClientObjects;
}
private void OnPreDestroyClientObjects(NetworkConnection conn)
{
if (conn == Owner)
RemoveOwnership();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 29401c9ff84500647a0d8718a39f28d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Prediction.cs
uploadId: 866910
@@ -0,0 +1,402 @@
using FishNet.CodeAnalysis.Annotations;
using FishNet.Component.ColliderRollback;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Client;
using FishNet.Managing.Observing;
using FishNet.Managing.Predicting;
using FishNet.Managing.Scened;
using FishNet.Managing.Server;
using FishNet.Managing.Timing;
using FishNet.Managing.Transporting;
using System;
using System.Collections.Generic;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
#region Public.
#region Obsoletes
[Obsolete("Use IsClientOnlyInitialized. Note the difference between IsClientOnlyInitialized and IsClientOnlyStarted.")]
public bool IsClientOnly => IsClientOnlyInitialized;
[Obsolete("Use IsServerOnlyInitialized. Note the difference between IsServerOnlyInitialized and IsServerOnlyStarted.")]
public bool IsServerOnly => IsServerOnlyInitialized;
[Obsolete("Use IsHostInitialized. Note the difference between IsHostInitialized and IsHostStarted.")]
public bool IsHost => IsHostInitialized;
[Obsolete("Use IsClientInitialized. Note the difference between IsClientInitialized and IsClientStarted.")]
public bool IsClient => IsClientInitialized;
[Obsolete("Use IsServerInitialized. Note the difference between IsServerInitialized and IsServerStarted.")]
public bool IsServer => IsServerInitialized;
#endregion
/// <summary>
/// True if despawning without object pooling, or if OnDestroy was invoked on this NetworkObject. As clientHost this value becomes true when previous criteria are met and server begins to deinitialize the object.
/// </summary>
/// <remarks>This can be useful for checking if you wish to perform certain actions within OnStopNetwork based on destroying status.</remarks>
public bool IsDestroying { get; private set; }
/// <summary>
/// Sets IsDestroying to true if DespawnType is not pooled. When DespawnType is not specified default DespawnType is checked.
/// </summary>
internal void SetIsDestroying(DespawnType? despawnType = null)
{
if (despawnType.HasValue)
{
if (despawnType.Value == DespawnType.Destroy)
IsDestroying = true;
}
else if (GetDefaultDespawnType() == DespawnType.Destroy)
{
IsDestroying = true;
}
}
/// <summary>
/// True if predicted spawning is allowed for this object.
/// </summary>
internal bool AllowPredictedSpawning => PredictedSpawn == null ? false : PredictedSpawn.GetAllowSpawning();
/// <summary>
/// True if predicted spawning is allowed for this object.
/// </summary>
internal bool AllowPredictedDespawning => PredictedSpawn == null ? false : PredictedSpawn.GetAllowDespawning();
/// <summary>
/// True if this object has been initialized on the client side.
/// This is set true right before client start callbacks and after stop callbacks.
/// </summary>
public bool IsClientInitialized { get; private set; }
/// <summary>
/// True if the client is started and authenticated. This will return true on clientHost even if the object has not initialized yet for the client.
/// To check if this object has been initialized for the client use IsClientInitialized.
/// </summary>
public bool IsClientStarted => NetworkManager == null ? false : NetworkManager.IsClientStarted;
/// <summary>
/// True if this object has been initialized only on the server side.
/// This is set true right before server start callbacks and after stop callbacks.
/// </summary>
public bool IsClientOnlyInitialized => !IsServerInitialized && IsClientInitialized;
/// <summary>
/// True if only the client is started and authenticated.
/// </summary>
public bool IsClientOnlyStarted => IsClientStarted && !IsServerStarted;
/// <summary>
/// True if this object has been initialized on the server side.
/// This is set true right before server start callbacks and after stop callbacks.
/// </summary>
public bool IsServerInitialized { get; private set; }
/// <summary>
/// True if the server is active. This will return true on clientHost even if the object is being deinitialized on the server.
/// To check if this object has been initialized for the server use IsServerInitialized.
/// </summary>
public bool IsServerStarted => NetworkManager == null ? false : NetworkManager.IsServerStarted;
/// <summary>
/// True if this object has been initialized only on the server side.
/// This is set true right before server start callbacks and after stop callbacks.
/// </summary>
public bool IsServerOnlyInitialized => IsServerInitialized && !IsClientInitialized;
/// <summary>
/// True if only the server is started.
/// </summary>
public bool IsServerOnlyStarted => IsServerStarted && !IsClientStarted;
/// <summary>
/// True if client and server are started.
/// </summary>
public bool IsHostStarted => IsClientStarted && IsServerStarted;
/// <summary>
/// True if this object has been initialized on the server and client side.
/// </summary>
public bool IsHostInitialized => IsClientInitialized && IsServerInitialized;
/// <summary>
/// True if client nor server are started.
/// </summary>
public bool IsOffline => !IsClientStarted && !IsServerStarted;
/// <summary>
/// True if a reconcile is occuring on the PredictionManager. Note the difference between this and IsBehaviourReconciling.
/// </summary>
public bool IsManagerReconciling => PredictionManager.IsReconciling;
/// <summary>
/// True if the local client is currently using a PredictedOwner component on this object to take ownership.
/// </summary>
public bool IsTakingOwnership => PredictedOwner != null && PredictedOwner.TakingOwnership;
/// <summary>
/// True if the local client is the owner of this object.
/// This will only return true if IsClientInitialized is also true. You may check ownership status regardless of client initialized state by using Owner.IsLocalClient.
/// </summary>
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartServer", "")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartNetwork", " Use base.Owner.IsLocalClient instead.")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Awake", "")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Start", "")]
public bool IsOwner
{
get
{
/* ClientInitialized becomes true when this
* NetworkObject has been initialized on the client side.
*
* This value is used to prevent IsOwner from returning true
* when running as host; primarily in Update or Tick callbacks
* where IsOwner would be true as host but OnStartClient has
* not called yet.
*
* EG: server will set owner when it spawns the object.
* If IsOwner is checked before the object spawns on the
* client-host then it would also return true, since the
* Owner reference would be the same as what was set by server.
*
* This is however bad when the client hasn't initialized the object
* yet because it gives a false sense of execution order.
* As a result, Update or Ticks may return IsOwner as true well before OnStartClient
* is called. Many users rightfully create code with the assumption the client has been
* initialized by the time IsOwner is true.
*
* This is a double edged sword though because now IsOwner would return true
* within OnStartNetwork for clients only, but not for host given the client
* side won't be initialized yet as host. As a work around CodeAnalysis will
* inform users to instead use base.Owner.IsLocalClient within OnStartNetwork. */
if (!IsClientInitialized)
return false;
return Owner.IsLocalClient;
}
}
/// <summary>
/// True if IsOwner, or if IsServerInitialized with no Owner.
/// </summary>
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartServer", "")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartNetwork", " Use (base.Owner.IsLocalClient || (base.IsServerInitialized && !Owner.Isvalid) instead.")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Awake", "")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Start", "")]
public bool IsController => IsOwner || (IsServerInitialized && !Owner.IsValid);
[Obsolete("Use IsController.")]
public bool HasAuthority => IsController;
/// <summary>
/// </summary>
private NetworkConnection _owner;
/// <summary>
/// Owner of this object.
/// </summary>
public NetworkConnection Owner
{
get
{
// Ensures a null Owner is never returned.
if (_owner == null)
return NetworkManager.EmptyConnection;
return _owner;
}
private set { _owner = value; }
}
/// <summary>
/// ClientId for this NetworkObject owner.
/// </summary>
public int OwnerId => !Owner.IsValid ? -1 : Owner.ClientId;
/// <summary>
/// True if the object is initialized for the network.
/// </summary>
public bool IsSpawned => !IsDeinitializing && ObjectId != UNSET_OBJECTID_VALUE;
/// <summary>
/// The local connection of the client calling this method.
/// </summary>
public NetworkConnection LocalConnection => NetworkManager == null ? new() : NetworkManager.ClientManager.Connection;
/// <summary>
/// NetworkManager for this object.
/// </summary>
public NetworkManager NetworkManager { get; private set; }
/// <summary>
/// ServerManager for this object.
/// </summary>
public ServerManager ServerManager { get; private set; }
/// <summary>
/// ClientManager for this object.
/// </summary>
public ClientManager ClientManager { get; private set; }
/// <summary>
/// ObserverManager for this object.
/// </summary>
public ObserverManager ObserverManager { get; private set; }
/// <summary>
/// TransportManager for this object.
/// </summary>
public TransportManager TransportManager { get; private set; }
/// <summary>
/// TimeManager for this object.
/// </summary>
public TimeManager TimeManager { get; private set; }
/// <summary>
/// SceneManager for this object.
/// </summary>
public SceneManager SceneManager { get; private set; }
/// <summary>
/// PredictionManager for this object.
/// </summary>
public PredictionManager PredictionManager { get; private set; }
/// <summary>
/// RollbackManager for this object.
/// </summary>
public RollbackManager RollbackManager { get; private set; }
#endregion
/// <summary>
/// Returns a NetworkBehaviour on this NetworkObject.
/// </summary>
/// <param name = "componentIndex">ComponentIndex of the NetworkBehaviour.</param>
/// <param name = "error">True to error if not found.</param>
/// <returns></returns>
public NetworkBehaviour GetNetworkBehaviour(byte componentIndex, bool error)
{
if (componentIndex >= NetworkBehaviours.Count)
{
if (error)
{
string message = $"ComponentIndex of {componentIndex} is out of bounds on {gameObject.name} [id {ObjectId}]. This may occur if you have modified your gameObject/prefab without saving it, or the scene.";
NetworkManager.LogError(message);
}
}
return NetworkBehaviours[componentIndex];
}
/// <summary>
/// Despawns a GameObject. Only call from the server.
/// </summary>
/// <param name = "go">GameObject to despawn.</param>
/// <param name = "despawnType">What happens to the object after being despawned.</param>
public void Despawn(GameObject go, DespawnType? despawnType = null)
{
if (NetworkManager != null)
NetworkManager.ServerManager.Despawn(go, despawnType);
}
/// <summary>
/// Despawns a NetworkObject. Only call from the server.
/// </summary>
/// <param name = "nob">NetworkObject to despawn.</param>
/// <param name = "despawnType">What happens to the object after being despawned.</param>
public void Despawn(NetworkObject nob, DespawnType? despawnType = null)
{
if (NetworkManager != null)
NetworkManager.ServerManager.Despawn(nob, despawnType);
}
/// <summary>
/// Despawns this NetworkObject. Only call from the server.
/// </summary>
/// <param name = "despawnType">What happens to the object after being despawned.</param>
public void Despawn(DespawnType? despawnType = null)
{
NetworkObject nob = this;
if (NetworkManager != null)
NetworkManager.ServerManager.Despawn(nob, despawnType);
}
/// <summary>
/// Spawns an object over the network. Only call from the server.
/// </summary>
public void Spawn(GameObject go, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
{
if (NetworkManager != null)
NetworkManager.ServerManager.Spawn(go, ownerConnection, scene);
}
/// <summary>
/// Spawns an object over the network. Only call from the server.
/// </summary>
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
{
if (NetworkManager != null)
NetworkManager.ServerManager.Spawn(nob, ownerConnection, scene);
}
[Obsolete("Use SetLocalOwnership(NetworkConnection, bool).")]
public void SetLocalOwnership(NetworkConnection caller) => SetLocalOwnership(caller, recursive: false);
/// <summary>
/// Takes ownership of this object and child network objects, allowing immediate control.
/// </summary>
/// <param name = "caller">Connection to give ownership to.</param>
public void SetLocalOwnership(NetworkConnection caller, bool recursive)
{
NetworkConnection prevOwner = Owner;
SetOwner(caller);
int count;
count = NetworkBehaviours.Count;
for (int i = 0; i < count; i++)
NetworkBehaviours[i].OnOwnershipClient_Internal(prevOwner);
if (recursive)
{
List<NetworkObject> allNested = GetNetworkObjects(GetNetworkObjectOption.AllNestedRecursive);
foreach (NetworkObject nob in allNested)
nob.SetLocalOwnership(caller, recursive: true);
CollectionCaches<NetworkObject>.Store(allNested);
}
}
#region Registered components
/// <summary>
/// Invokes an action when a specified component becomes registered. Action will invoke immediately if already registered.
/// </summary>
/// <typeparam name = "T">Component type.</typeparam>
/// <param name = "handler">Action to invoke.</param>
public void RegisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => NetworkManager.RegisterInvokeOnInstance<T>(handler);
/// <summary>
/// Removes an action to be invoked when a specified component becomes registered.
/// </summary>
/// <typeparam name = "T">Component type.</typeparam>
/// <param name = "handler">Action to invoke.</param>
public void UnregisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => NetworkManager.UnregisterInvokeOnInstance<T>(handler);
/// <summary>
/// Returns if an instance exists for type.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <returns></returns>
public bool HasInstance<T>() where T : UnityEngine.Component => NetworkManager.HasInstance<T>();
/// <summary>
/// Returns class of type if found within CodegenBase classes.
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <returns></returns>
public T GetInstance<T>() where T : UnityEngine.Component => NetworkManager.GetInstance<T>();
/// <summary>
/// Registers a new component to this NetworkManager.
/// </summary>
/// <typeparam name = "T">Type to register.</typeparam>
/// <param name = "component">Reference of the component being registered.</param>
/// <param name = "replace">True to replace existing references.</param>
public void RegisterInstance<T>(T component, bool replace = true) where T : UnityEngine.Component => NetworkManager.RegisterInstance(component, replace);
/// <summary>
/// Tries to registers a new component to this NetworkManager.
/// This will not register the instance if another already exists.
/// </summary>
/// <typeparam name = "T">Type to register.</typeparam>
/// <param name = "component">Reference of the component being registered.</param>
/// <returns>True if was able to register, false if an instance is already registered.</returns>
public bool TryRegisterInstance<T>(T component) where T : UnityEngine.Component => NetworkManager.TryRegisterInstance(component);
/// <summary>
/// Returns class of type from registered instances.
/// </summary>
/// <param name = "component">Outputted component.</param>
/// <typeparam name = "T">Type to get.</typeparam>
/// <returns>True if was able to get instance.</returns>
public bool TryGetInstance<T>(out T component) where T : UnityEngine.Component => NetworkManager.TryGetInstance(out component);
/// <summary>
/// Unregisters a component from this NetworkManager.
/// </summary>
/// <typeparam name = "T">Type to unregister.</typeparam>
public void UnregisterInstance<T>() where T : UnityEngine.Component => NetworkManager.UnregisterInstance<T>();
#endregion
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fd30b4b61d50d01499c94a63a6eeb863
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.QOL.cs
uploadId: 866910
@@ -0,0 +1,2 @@
// Remove in V5
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: be0a4b0a32b02f64495ba3b1d22f89c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.ReferenceIds.cs
uploadId: 866910
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
#region Private.
/// <summary>
/// RpcLinks being used within this NetworkObject.
/// </summary>
private List<ushort> _rpcLinkIndexes;
#endregion
/// <summary>
/// Sets rpcLinkIndexes to values.
/// </summary>
internal void SetRpcLinkIndexes(List<ushort> values)
{
_rpcLinkIndexes = values;
}
/// <summary>
/// Removes used link indexes from ClientObjects.
/// </summary>
internal void RemoveClientRpcLinkIndexes()
{
// if (NetworkManager != null)
NetworkManager.ClientManager.Objects.RemoveLinkIndexes(_rpcLinkIndexes);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8b2f6927cf3ef254d91b89e5f99a92b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.RpcLinks.cs
uploadId: 866910
@@ -0,0 +1,232 @@
// This file contains values serialized in editor or once at runtime.
using UnityEngine;
using System;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using FishNet.Managing;
using FishNet.Utility.Extension;
#if UNITY_EDITOR
using UnityEditor.Experimental.SceneManagement;
using UnityEditor.SceneManagement;
using UnityEditor;
#endif
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
#region Public.
/// <summary>
/// Networked PrefabId assigned to this Prefab.
/// </summary>
[field: SerializeField]
[field: HideInInspector]
public ushort PrefabId { get; internal set; } = UNSET_PREFABID_VALUE;
/// <summary>
/// Spawn collection to use assigned to this Prefab.
/// </summary>
[field: SerializeField]
[field: HideInInspector]
public ushort SpawnableCollectionId { get; internal set; } = 0;
/// <summary>
/// Sets SceneId value. This is not synchronized automatically.
/// </summary>
/// <param name = "sceneId"></param>
public void SetSceneId(ulong sceneId) => SceneId = sceneId;
/// <summary>
/// Hash for the path which this asset resides. This value is set during edit time.
/// </summary>
[field: SerializeField]
[field: HideInInspector]
public ulong AssetPathHash { get; private set; }
/// <summary>
/// Sets AssetPathhash value.
/// </summary>
/// <param name = "value">Value to use.</param>
public void SetAssetPathHash(ulong value) => AssetPathHash = value;
#endregion
#region Internal.
/// <summary>
/// NetworkId for this scene object.
/// </summary>
[field: SerializeField]
[field: HideInInspector]
internal ulong SceneId;
/// <summary>
/// Local properties of the transform during serialization.
/// </summary>
[SerializeField]
[HideInInspector]
internal TransformProperties SerializedTransformProperties = new();
#endregion
#region Private.
/// <summary>
/// Last time sceneIds were built automatically.
/// </summary>
[NonSerialized]
private static double _lastSceneIdAutomaticRebuildTime;
#endregion
/// <summary>
/// Removes SceneObject state.
/// This may only be called at runtime.
/// </summary>
internal void ClearRuntimeSceneObject()
{
if (!Application.isPlaying)
{
NetworkManager.LogError($"ClearRuntimeSceneObject may only be called at runtime.");
return;
}
SceneId = UNSET_SCENEID_VALUE;
}
#if UNITY_EDITOR
private void OnApplicationQuit()
{
_lastSceneIdAutomaticRebuildTime = 0;
}
/// <summary>
/// Tries to generate a SceneIds for NetworkObjects in a scene.
/// </summary>
internal static List<NetworkObject> CreateSceneId(UnityEngine.SceneManagement.Scene scene, bool force, out int changed)
{
changed = 0;
if (Application.isPlaying)
return new();
if (!scene.IsValid())
return new();
if (!scene.isLoaded)
return new();
HashSet<ulong> setIds = new();
uint scenePathHash = scene.path.GetStableHashU32();
List<NetworkObject> sceneNobs = new();
Scenes.GetSceneNetworkObjects(scene, firstOnly: false, errorOnDuplicates: false, ignoreUnsetSceneIds: false, ref sceneNobs);
System.Random rnd = new();
// NetworkObjects which need their Ids rebuilt.
List<NetworkObject> rebuildingNobs = new();
foreach (NetworkObject item in sceneNobs)
{
bool canGenerate = !item.IsSceneObject || !setIds.Add(item.SceneId);
/* If an Id has not been generated yet or if it
* already exist then rebuild for this object. */
if (force || canGenerate)
{
item.SceneId = UNSET_SCENEID_VALUE;
rebuildingNobs.Add(item);
}
}
foreach (NetworkObject item in rebuildingNobs)
{
ulong nextSceneId = UNSET_SCENEID_VALUE;
while (nextSceneId == UNSET_SCENEID_VALUE || setIds.Contains(nextSceneId))
{
uint rndId = (uint)(rnd.Next(int.MinValue, int.MaxValue) + int.MaxValue);
nextSceneId = CombineHashes(scenePathHash, rndId);
}
ulong CombineHashes(uint a, uint b)
{
return b | a;
}
setIds.Add(nextSceneId);
changed++;
item.SceneId = nextSceneId;
EditorUtility.SetDirty(item);
}
return sceneNobs;
}
/// <summary>
/// Tries to generate a SceneId.
/// </summary>
private void CreateSceneId(bool force)
{
if (Application.isPlaying)
return;
// Unity bug, sometimes this can be null depending on editor callback orders.
if (gameObject == null)
return;
// Not a scene object.
if (string.IsNullOrEmpty(gameObject.scene.name))
{
SceneId = UNSET_SCENEID_VALUE;
return;
}
/* If building then only check if
* scene networkobjects have their sceneIds
* missing. */
if (BuildPipeline.isBuildingPlayer)
{
// If prefab or part of a prefab, not a scene object.
if (PrefabUtility.IsPartOfPrefabAsset(this) || IsEditingInPrefabMode() ||
// Not in a scene, another prefab check.
!gameObject.scene.IsValid() ||
// Stored on disk, so is a prefab. Somehow prefabutility missed it.
EditorUtility.IsPersistent(this))
// If here this is a sceneObject, but sceneId is not set.
if (!IsSceneObject)
throw new InvalidOperationException($"Networked GameObject {gameObject.name} in scene {gameObject.scene.path} is missing a SceneId. Use the Fish-Networking menu -> Utility -> Reserialize NetworkObjects > Reserialize Scenes. If the problem persist ensures {gameObject.name} does not have any missing script references on it's prefab or in the scene. Also ensure that you have any prefab changes for the object applied.");
}
// If not building check to rebuild sceneIds this for object and the scene its in.
else
{
double realtime = EditorApplication.timeSinceStartup;
// Only do this once every Xms to prevent excessive rebiulds.
if (realtime - _lastSceneIdAutomaticRebuildTime < 0.250d)
return;
// Not in a scene, another prefab check.
// Stored on disk, so is a prefab. Somehow prefabutility missed it.
if (PrefabUtility.IsPartOfPrefabAsset(this) || IsEditingInPrefabMode() || !gameObject.scene.IsValid() || EditorUtility.IsPersistent(this))
return;
_lastSceneIdAutomaticRebuildTime = realtime;
CreateSceneId(gameObject.scene, force, out _);
}
}
private bool IsEditingInPrefabMode()
{
// if the game object is stored on disk, it is a prefab of some kind, despite not returning true for IsPartOfPrefabAsset =/
if (EditorUtility.IsPersistent(this))
return true;
// If the GameObject is not persistent let's determine which stage we are in first because getting Prefab info depends on it
StageHandle mainStage = StageUtility.GetMainStageHandle();
StageHandle currentStage = StageUtility.GetStageHandle(gameObject);
if (currentStage != mainStage)
{
PrefabStage prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
if (prefabStage != null)
return true;
}
return false;
}
private void ReferenceIds_Reset()
{
CreateSceneId(force: false);
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2d0b7f2fb91aa9243819ba0c5f783dd5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Serialized.cs
uploadId: 866910
@@ -0,0 +1,31 @@
using System.Runtime.CompilerServices;
using FishNet.Connection;
using FishNet.Object.Synchronizing;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
/// <summary>
/// Writes SyncTypes for previous and new owner where permissions apply.
/// </summary>
private void WriteSyncTypesForManualOwnershipChange(NetworkConnection prevOwner)
{
if (prevOwner.IsActive)
WriteForConnection(prevOwner, ReadPermission.ExcludeOwner);
if (Owner.IsActive)
WriteForConnection(Owner, ReadPermission.OwnerOnly);
void WriteForConnection(NetworkConnection conn, ReadPermission permission)
{
for (int i = 0; i < NetworkBehaviours.Count; i++)
NetworkBehaviours[i].WriteSyncTypesForConnection(conn, permission);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: aacec7a84fa0ebe45a6a385a33863782
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.SyncTypes.cs
uploadId: 866910
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 26b716c41e9b56b4baafaf13a523ba2e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.cs
uploadId: 866910
@@ -0,0 +1,75 @@
namespace FishNet.Object
{
/// <summary>
/// Action to take when despawning a NetworkObject.
/// </summary>
public enum DespawnType : byte
{
Destroy = 0,
Pool = 1
}
/// <summary>
/// Current state of the NetworkObject.
/// </summary>
internal enum NetworkObjectState : byte
{
/// <summary>
/// State has not been set. This occurs when the object has never been spawned or despawned.
/// </summary>
Unset = 0,
/// <summary>
/// Object is currently spawned.
/// </summary>
Spawned = 1,
/// <summary>
/// Object is currently despawned.
/// </summary>
Despawned = 2
}
/// <summary>
/// Options on retrieving nested NetworkObjects.
/// </summary>
[System.Flags]
internal enum GetNetworkObjectOption : int
{
/// <summary>
/// Include NetworkObject which nested are being returned for.
/// </summary>
Self = 1 << 0,
/// <summary>
/// Include initialize nested.
/// </summary>
InitializedNested = 1 << 1,
/// <summary>
/// Include runtime nested.
/// </summary>
RuntimeNested = 1 << 2,
/// <summary>
/// Recursively iterate nested includes.
/// </summary>
/// <remarks>This only functions if Initialized or Runtime is flagged.</remarks>
Recursive = 1 << 3,
/// <summary>
/// Uses InitializedNested and RuntimeNested flags.
/// </summary>
AllNested = InitializedNested | RuntimeNested,
/// <summary>
/// Uses InitializedNested, RuntimeNested, and Recursive flags.
/// </summary>
AllNestedRecursive = InitializedNested | RuntimeNested | Recursive,
/// <summary>
/// Sets all flags.
/// </summary>
All = ~0
}
internal static class GetNetworkObjectOptionExtensions
{
/// <summary>
/// True if whole contains part.
/// </summary>
public static bool FastContains(this GetNetworkObjectOption whole, GetNetworkObjectOption part) => (whole & part) == part;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 536f137f11dc6654eab9fbe94ca14cd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObjectData.cs
uploadId: 866910