fishnet installed
This commit is contained in:
8
Assets/FishNet/Runtime/Managing/Client.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Client.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96f81f64930875143840663a13e78564
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,179 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Broadcast.Helping;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
public sealed partial class ClientManager : MonoBehaviour
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Delegate to read received broadcasts.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
private delegate void ServerBroadcastDelegate(PooledReader reader);
|
||||
/// <summary>
|
||||
/// Delegates for each key.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ushort, HashSet<ServerBroadcastDelegate>> _broadcastHandlers = new Dictionary<ushort, HashSet<ServerBroadcastDelegate>>();
|
||||
/// <summary>
|
||||
/// Delegate targets for each key.
|
||||
/// </summary>
|
||||
private Dictionary<ushort, HashSet<(int, ServerBroadcastDelegate)>> _handlerTargets = new Dictionary<ushort, HashSet<(int, ServerBroadcastDelegate)>>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a method to call when a Broadcast arrives.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of broadcast being registered.</typeparam>
|
||||
/// <param name="handler">Method to call.</param>
|
||||
public void RegisterBroadcast<T>(Action<T> handler) where T : struct, IBroadcast
|
||||
{
|
||||
ushort key = typeof(T).FullName.GetStableHash16();
|
||||
/* Create delegate and add for
|
||||
* handler method. */
|
||||
HashSet<ServerBroadcastDelegate> handlers;
|
||||
if (!_broadcastHandlers.TryGetValueIL2CPP(key, out handlers))
|
||||
{
|
||||
handlers = new HashSet<ServerBroadcastDelegate>();
|
||||
_broadcastHandlers.Add(key, handlers);
|
||||
}
|
||||
ServerBroadcastDelegate del = CreateBroadcastDelegate(handler);
|
||||
handlers.Add(del);
|
||||
|
||||
/* Add hashcode of target for handler.
|
||||
* This is so we can unregister the target later. */
|
||||
int handlerHashCode = handler.GetHashCode();
|
||||
HashSet<(int, ServerBroadcastDelegate)> targetHashCodes;
|
||||
if (!_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
|
||||
{
|
||||
targetHashCodes = new HashSet<(int, ServerBroadcastDelegate)>();
|
||||
_handlerTargets.Add(key, targetHashCodes);
|
||||
}
|
||||
|
||||
targetHashCodes.Add((handlerHashCode, del));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a method call from a Broadcast type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of broadcast being unregistered.</typeparam>
|
||||
/// <param name="handler">Method to unregister.</param>
|
||||
public void UnregisterBroadcast<T>(Action<T> handler) where T : struct, IBroadcast
|
||||
{
|
||||
ushort key = BroadcastHelper.GetKey<T>();
|
||||
|
||||
/* If key is found for T then look for
|
||||
* the appropriate handler to remove. */
|
||||
if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ServerBroadcastDelegate> handlers))
|
||||
{
|
||||
HashSet<(int, ServerBroadcastDelegate)> targetHashCodes;
|
||||
if (_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
|
||||
{
|
||||
int handlerHashCode = handler.GetHashCode();
|
||||
ServerBroadcastDelegate result = null;
|
||||
foreach ((int targetHashCode, ServerBroadcastDelegate del) in targetHashCodes)
|
||||
{
|
||||
if (targetHashCode == handlerHashCode)
|
||||
{
|
||||
result = del;
|
||||
targetHashCodes.Remove((targetHashCode, del));
|
||||
break;
|
||||
}
|
||||
}
|
||||
//If no more in targetHashCodes then remove from handlerTarget.
|
||||
if (targetHashCodes.Count == 0)
|
||||
_handlerTargets.Remove(key);
|
||||
|
||||
if (result != null)
|
||||
handlers.Remove(result);
|
||||
}
|
||||
|
||||
//If no more in handlers then remove broadcastHandlers.
|
||||
if (handlers.Count == 0)
|
||||
_broadcastHandlers.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ServerBroadcastDelegate.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="handler"></param>
|
||||
/// <param name="requireAuthentication"></param>
|
||||
/// <returns></returns>
|
||||
private ServerBroadcastDelegate CreateBroadcastDelegate<T>(Action<T> handler)
|
||||
{
|
||||
void LogicContainer(PooledReader reader)
|
||||
{
|
||||
T broadcast = reader.Read<T>();
|
||||
handler?.Invoke(broadcast);
|
||||
}
|
||||
return LogicContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received broadcast.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ParseBroadcast(PooledReader reader, Channel channel)
|
||||
{
|
||||
ushort key = reader.ReadUInt16();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.Broadcast, reader, channel);
|
||||
// try to invoke the handler for that message
|
||||
if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ServerBroadcastDelegate> handlers))
|
||||
{
|
||||
int readerStartPosition = reader.Position;
|
||||
/* //muchlater resetting the position could be better by instead reading once and passing in
|
||||
* the object to invoke with. */
|
||||
foreach (ServerBroadcastDelegate handler in handlers)
|
||||
{
|
||||
reader.Position = readerStartPosition;
|
||||
handler.Invoke(reader);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Skip(dataLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a Broadcast to the server.
|
||||
/// </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="channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(T message, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
//Check local connection state.
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to server because client is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
using (PooledWriter writer = WriterPool.GetWriter())
|
||||
{
|
||||
Broadcasts.WriteBroadcast<T>(writer, message, channel);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
|
||||
NetworkManager.TransportManager.SendToServer((byte)channel, segment);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97ef4be4cfcc6a54d80a65a5c8d325d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
197
Assets/FishNet/Runtime/Managing/Client/ClientManager.LOD.cs
Normal file
197
Assets/FishNet/Runtime/Managing/Client/ClientManager.LOD.cs
Normal file
@ -0,0 +1,197 @@
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
public sealed partial class ClientManager : MonoBehaviour
|
||||
{
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// How many ticks between each LOD update.
|
||||
/// </summary>
|
||||
public uint LevelOfDetailInterval => NetworkManager.TimeManager.TimeToTicks(0.5d, TickRounding.RoundUp);
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Positions of the player objects.
|
||||
/// </summary>
|
||||
private List<Vector3> _objectsPositionsCache = new List<Vector3>();
|
||||
/// <summary>
|
||||
/// Next index within Spawned to update the LOD on.
|
||||
/// </summary>
|
||||
private int _nextLodNobIndex;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Sends a level of update if conditions are met.
|
||||
/// </summary>
|
||||
/// <param name="forceFullUpdate">True to force send a full update immediately.
|
||||
/// This may be useful when teleporting clients.
|
||||
/// This must be called by on the server by using ServerManager.ForceLodUpdate(NetworkConnection).
|
||||
/// </param>
|
||||
internal void SendLodUpdate(bool forceFullUpdate)
|
||||
{
|
||||
if (!Connection.Authenticated)
|
||||
return;
|
||||
NetworkManager nm = NetworkManager;
|
||||
if (forceFullUpdate)
|
||||
{
|
||||
nm.LogError($"ForceFullUpdate is not yet implemented. Setting this true should not be possible.");
|
||||
return;
|
||||
}
|
||||
if (!nm.ObserverManager.GetUseNetworkLod())
|
||||
return;
|
||||
|
||||
//Interval check.
|
||||
uint localTick = nm.TimeManager.LocalTick;
|
||||
uint intervalRequirement = LevelOfDetailInterval;
|
||||
bool intervalMet = ((localTick - Connection.LastLevelOfDetailUpdate) >= intervalRequirement);
|
||||
if (!forceFullUpdate && !intervalMet)
|
||||
return;
|
||||
|
||||
//Set next tick.
|
||||
Connection.LastLevelOfDetailUpdate = localTick;
|
||||
|
||||
List<NetworkObject> localClientSpawned = nm.ClientManager.Objects.LocalClientSpawned;
|
||||
int spawnedCount = localClientSpawned.Count;
|
||||
if (spawnedCount == 0)
|
||||
return;
|
||||
|
||||
//Rebuild position cache for players objects.
|
||||
_objectsPositionsCache.Clear();
|
||||
foreach (NetworkObject playerObjects in Connection.Objects)
|
||||
_objectsPositionsCache.Add(playerObjects.transform.position);
|
||||
|
||||
/* Set the maximum number of entries per send.
|
||||
* Each send is going to be approximately 3 bytes
|
||||
* but sometimes can be 4. Calculate based off the maximum
|
||||
* possible bytes. */
|
||||
//int mtu = NetworkManager.TransportManager.GetMTU((byte)Channel.Reliable);
|
||||
const int estimatedMaximumIterations = ( 400 / 4);
|
||||
/* Aim to process all objects over at most 10 seconds.
|
||||
* To reduce the number of packets sent objects are
|
||||
* calculated ~twice a second. This means if the client had
|
||||
* 1000 objects visible to them they would need to process
|
||||
* 100 objects a second, so 50 objects every half a second.
|
||||
* This should be no problem even on slower mobile devices. */
|
||||
int iterations;
|
||||
//Normal update.
|
||||
if (!forceFullUpdate)
|
||||
{
|
||||
iterations = Mathf.Min(spawnedCount, estimatedMaximumIterations);
|
||||
}
|
||||
//Force does a full update.
|
||||
else
|
||||
{
|
||||
_nextLodNobIndex = 0;
|
||||
iterations = spawnedCount;
|
||||
}
|
||||
|
||||
//Cache a few more things.
|
||||
Dictionary<NetworkObject, byte> currentLods = Connection.LevelOfDetails;
|
||||
List<float> lodDistances = NetworkManager.ObserverManager.GetLevelOfDetailDistances();
|
||||
|
||||
//Index to use next is too high so reset it.
|
||||
if (_nextLodNobIndex >= spawnedCount)
|
||||
_nextLodNobIndex = 0;
|
||||
int nobIndex = _nextLodNobIndex;
|
||||
|
||||
PooledWriter tmpWriter = WriterPool.GetWriter(1000);
|
||||
int written = 0;
|
||||
|
||||
//Only check if player has objects.
|
||||
if (_objectsPositionsCache.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
NetworkObject nob = localClientSpawned[nobIndex];
|
||||
//Somehow went null. Can occur perhaps if client destroys objects between ticks maybe.
|
||||
if (nob == null)
|
||||
{
|
||||
IncreaseObjectIndex();
|
||||
continue;
|
||||
}
|
||||
//Only check objects not owned by the local client.
|
||||
if (!nob.IsOwner && !nob.IsDeinitializing)
|
||||
{
|
||||
Vector3 nobPosition = nob.transform.position;
|
||||
float closestDistance = float.MaxValue;
|
||||
foreach (Vector3 objPosition in _objectsPositionsCache)
|
||||
{
|
||||
float dist = Vector3.SqrMagnitude(nobPosition - objPosition);
|
||||
if (dist < closestDistance)
|
||||
closestDistance = dist;
|
||||
}
|
||||
|
||||
//If not within any distances then max lod will be used, the value below.
|
||||
byte lod = (byte)(lodDistances.Count - 1);
|
||||
for (byte z = 0; z < lodDistances.Count; z++)
|
||||
{
|
||||
//Distance is within range of this lod.
|
||||
if (closestDistance <= lodDistances[z])
|
||||
{
|
||||
lod = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool changed;
|
||||
/* See if value changed. Value is changed
|
||||
* if it's not the same of old or if
|
||||
* the nob has not yet been added to the
|
||||
* level of details collection.
|
||||
* Even if a forced update only delta
|
||||
* needs to send. */
|
||||
if (currentLods.TryGetValue(nob, out byte oldLod))
|
||||
changed = (oldLod != lod);
|
||||
else
|
||||
changed = true;
|
||||
|
||||
//If changed then set new value and write.
|
||||
if (changed)
|
||||
{
|
||||
currentLods[nob] = lod;
|
||||
tmpWriter.WriteNetworkObjectId(nob.ObjectId);
|
||||
tmpWriter.WriteByte(lod);
|
||||
written++;
|
||||
}
|
||||
}
|
||||
|
||||
IncreaseObjectIndex();
|
||||
|
||||
void IncreaseObjectIndex()
|
||||
{
|
||||
nobIndex++;
|
||||
if (nobIndex >= spawnedCount)
|
||||
nobIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Set next lod index to current nob index.
|
||||
_nextLodNobIndex = nobIndex;
|
||||
/* Send using the reliable channel since
|
||||
* we are using deltas. This is also why
|
||||
* updates are sent larger chunked twice a second rather
|
||||
* than smaller chunks regularly. */
|
||||
PooledWriter writer = WriterPool.GetWriter(1000);
|
||||
writer.WritePacketId(PacketId.NetworkLODUpdate);
|
||||
writer.WriteInt32(written);
|
||||
writer.WriteArraySegment(tmpWriter.GetArraySegment());
|
||||
NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment(), true);
|
||||
|
||||
//Dispose writers.
|
||||
writer.DisposeLength();
|
||||
tmpWriter.DisposeLength();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 249df1192f06801488309cd13fd2b8ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
503
Assets/FishNet/Runtime/Managing/Client/ClientManager.cs
Normal file
503
Assets/FishNet/Runtime/Managing/Client/ClientManager.cs
Normal file
@ -0,0 +1,503 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Debugging;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for local client data and actions.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/ClientManager")]
|
||||
public sealed partial class ClientManager : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called after local client has authenticated.
|
||||
/// </summary>
|
||||
public event Action OnAuthenticated;
|
||||
/// <summary>
|
||||
/// Called after the local client connection state changes.
|
||||
/// </summary>
|
||||
public event Action<ClientConnectionStateArgs> OnClientConnectionState;
|
||||
/// <summary>
|
||||
/// Called when a client other than self connects.
|
||||
/// This is only available when using ServerManager.ShareIds.
|
||||
/// </summary>
|
||||
public event Action<RemoteConnectionStateArgs> OnRemoteConnectionState;
|
||||
/// <summary>
|
||||
/// True if the client connection is connected to the server.
|
||||
/// </summary>
|
||||
public bool Started { get; private set; }
|
||||
/// <summary>
|
||||
/// NetworkConnection the local client is using to send data to the server.
|
||||
/// </summary>
|
||||
public NetworkConnection Connection = NetworkManager.EmptyConnection;
|
||||
/// <summary>
|
||||
/// Handling and information for objects known to the local client.
|
||||
/// </summary>
|
||||
public ClientObjects Objects { get; private set; }
|
||||
/// <summary>
|
||||
/// All currently connected clients. This field only contains data while ServerManager.ShareIds is enabled.
|
||||
/// </summary>
|
||||
public Dictionary<int, NetworkConnection> Clients = new Dictionary<int, NetworkConnection>();
|
||||
/// <summary>
|
||||
/// NetworkManager for client.
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public NetworkManager NetworkManager { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to automatically set the frame rate when the client connects.
|
||||
/// </summary>
|
||||
[Tooltip("True to automatically set the frame rate when the client connects.")]
|
||||
[SerializeField]
|
||||
private bool _changeFrameRate = true;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Maximum frame rate the client may run at. When as host this value runs at whichever is higher between client and server.")]
|
||||
[Range(1, NetworkManager.MAXIMUM_FRAMERATE)]
|
||||
[SerializeField]
|
||||
private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE;
|
||||
/// <summary>
|
||||
/// Maximum frame rate the client may run at. When as host this value runs at whichever is higher between client and server.
|
||||
/// </summary>
|
||||
internal ushort FrameRate => (_changeFrameRate) ? _frameRate : (ushort)0;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Used to read splits.
|
||||
/// </summary>
|
||||
private SplitReader _splitReader = new SplitReader();
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
/// <summary>
|
||||
/// Logs data about parser to help debug.
|
||||
/// </summary>
|
||||
private ParseLogger _parseLogger = new ParseLogger();
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Objects?.SubscribeToSceneLoaded(false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script for use.
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
internal void InitializeOnce_Internal(NetworkManager manager)
|
||||
{
|
||||
NetworkManager = manager;
|
||||
Objects = new ClientObjects(manager);
|
||||
Objects.SubscribeToSceneLoaded(true);
|
||||
/* Unsubscribe before subscribing.
|
||||
* Shouldn't be an issue but better safe than sorry. */
|
||||
SubscribeToEvents(false);
|
||||
SubscribeToEvents(true);
|
||||
//Listen for client connections from server.
|
||||
RegisterBroadcast<ClientConnectionChangeBroadcast>(OnClientConnectionBroadcast);
|
||||
RegisterBroadcast<ConnectedClientsBroadcast>(OnConnectedClientsBroadcast);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server sends a connection state change for any client.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
private void OnClientConnectionBroadcast(ClientConnectionChangeBroadcast args)
|
||||
{
|
||||
//If connecting invoke after added to clients, otherwise invoke before removed.
|
||||
RemoteConnectionStateArgs rcs = new RemoteConnectionStateArgs((args.Connected) ? RemoteConnectionState.Started : RemoteConnectionState.Stopped, args.Id, -1);
|
||||
|
||||
if (args.Connected)
|
||||
{
|
||||
Clients[args.Id] = new NetworkConnection(NetworkManager, args.Id, false);
|
||||
OnRemoteConnectionState?.Invoke(rcs);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnRemoteConnectionState?.Invoke(rcs);
|
||||
if (Clients.TryGetValue(args.Id, out NetworkConnection c))
|
||||
{
|
||||
c.Dispose();
|
||||
Clients.Remove(args.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server sends all currently connected clients.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
private void OnConnectedClientsBroadcast(ConnectedClientsBroadcast args)
|
||||
{
|
||||
NetworkManager.ClearClientsCollection(Clients);
|
||||
|
||||
List<int> collection = args.ListCache.Collection;// args.Ids;
|
||||
//No connected clients except self.
|
||||
if (collection == null)
|
||||
return;
|
||||
|
||||
int count = collection.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int id = collection[i];
|
||||
Clients[id] = new NetworkConnection(NetworkManager, id, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes subscription status to transport.
|
||||
/// </summary>
|
||||
/// <param name="subscribe"></param>
|
||||
private void SubscribeToEvents(bool subscribe)
|
||||
{
|
||||
if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
|
||||
return;
|
||||
|
||||
if (subscribe)
|
||||
{
|
||||
NetworkManager.TransportManager.OnIterateIncomingEnd += TransportManager_OnIterateIncomingEnd;
|
||||
NetworkManager.TransportManager.Transport.OnClientReceivedData += Transport_OnClientReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnClientConnectionState += Transport_OnClientConnectionState;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.TransportManager.OnIterateIncomingEnd -= TransportManager_OnIterateIncomingEnd;
|
||||
NetworkManager.TransportManager.Transport.OnClientReceivedData -= Transport_OnClientReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnClientConnectionState -= Transport_OnClientConnectionState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the local client connection.
|
||||
/// </summary>
|
||||
public void StopConnection()
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.StopConnection(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the local client connection.
|
||||
/// </summary>
|
||||
public void StartConnection()
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.StartConnection(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the transport address and starts the local client connection.
|
||||
/// </summary>
|
||||
public void StartConnection(string address)
|
||||
{
|
||||
StartConnection(address, NetworkManager.TransportManager.Transport.GetPort());
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the transport address and port, and starts the local client connection.
|
||||
/// </summary>
|
||||
public void StartConnection(string address, ushort port)
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.SetClientAddress(address);
|
||||
NetworkManager.TransportManager.Transport.SetPort(port);
|
||||
StartConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a connection state changes for the local client.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
private void Transport_OnClientConnectionState(ClientConnectionStateArgs args)
|
||||
{
|
||||
LocalConnectionState state = args.ConnectionState;
|
||||
Started = (state == LocalConnectionState.Started);
|
||||
Objects.OnClientConnectionState(args);
|
||||
|
||||
//Clear connection after so objects can update using current Connection value.
|
||||
if (!Started)
|
||||
{
|
||||
Connection = NetworkManager.EmptyConnection;
|
||||
NetworkManager.ClearClientsCollection(Clients);
|
||||
}
|
||||
|
||||
if (NetworkManager.CanLog(LoggingType.Common))
|
||||
{
|
||||
Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
|
||||
string tName = (t == null) ? "Unknown" : t.GetType().Name;
|
||||
Debug.Log($"Local client is {state.ToString().ToLower()} for {tName}.");
|
||||
}
|
||||
|
||||
NetworkManager.UpdateFramerate();
|
||||
OnClientConnectionState?.Invoke(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a socket receives data.
|
||||
/// </summary>
|
||||
private void Transport_OnClientReceivedData(ClientReceivedDataArgs args)
|
||||
{
|
||||
args.Data = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, true);
|
||||
ParseReceived(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after IterateIncoming has completed.
|
||||
/// </summary>
|
||||
private void TransportManager_OnIterateIncomingEnd(bool server)
|
||||
{
|
||||
/* Should the last packet received be a spawn or despawn
|
||||
* then the cache won't yet be iterated because it only
|
||||
* iterates when a packet is anything but those two. Because
|
||||
* of such if any object caches did come in they must be iterated
|
||||
* at the end of the incoming cycle. This isn't as clean as I'd
|
||||
* like but it does ensure there will be no missing network object
|
||||
* references on spawned objects. */
|
||||
if (Started && !server)
|
||||
Objects.IterateObjectCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses received data.
|
||||
/// </summary>
|
||||
private void ParseReceived(ClientReceivedDataArgs args)
|
||||
{
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
_parseLogger.Reset();
|
||||
#endif
|
||||
|
||||
ArraySegment<byte> segment = args.Data;
|
||||
NetworkManager.StatisticsManager.NetworkTraffic.LocalClientReceivedData((ulong)segment.Count);
|
||||
if (segment.Count <= TransportManager.TICK_BYTES)
|
||||
return;
|
||||
|
||||
PacketId packetId = PacketId.Unset;
|
||||
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
|
||||
try
|
||||
{
|
||||
#endif
|
||||
using (PooledReader reader = ReaderPool.GetReader(segment, NetworkManager))
|
||||
{
|
||||
NetworkManager.TimeManager.LastPacketTick = reader.ReadUInt32(AutoPackType.Unpacked);
|
||||
/* This is a special condition where a message may arrive split.
|
||||
* When this occurs buffer each packet until all packets are
|
||||
* received. */
|
||||
if (reader.PeekPacketId() == PacketId.Split)
|
||||
{
|
||||
//Skip packetId.
|
||||
reader.ReadPacketId();
|
||||
int expectedMessages;
|
||||
_splitReader.GetHeader(reader, out expectedMessages);
|
||||
_splitReader.Write(NetworkManager.TimeManager.LastPacketTick, reader, expectedMessages);
|
||||
/* If fullMessage returns 0 count then the split
|
||||
* has not written fully yet. Otherwise, if there is
|
||||
* data within then reinitialize reader with the
|
||||
* full message. */
|
||||
ArraySegment<byte> fullMessage = _splitReader.GetFullMessage();
|
||||
if (fullMessage.Count == 0)
|
||||
return;
|
||||
|
||||
//Initialize reader with full message.
|
||||
reader.Initialize(fullMessage, NetworkManager);
|
||||
}
|
||||
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
packetId = reader.ReadPacketId();
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
_parseLogger.AddPacket(packetId);
|
||||
#endif
|
||||
bool spawnOrDespawn = (packetId == PacketId.ObjectSpawn || packetId == PacketId.ObjectDespawn);
|
||||
/* Length of data. Only available if using unreliable. Unreliable packets
|
||||
* can arrive out of order which means object orientated messages such as RPCs may
|
||||
* arrive after the object for which they target has already been destroyed. When this happens
|
||||
* on lesser solutions they just dump the entire packet. However, since FishNet batches data.
|
||||
* it's very likely a packet will contain more than one packetId. With this mind, length is
|
||||
* sent as well so if any reason the data does have to be dumped it will only be dumped for
|
||||
* that single packetId but not the rest. Broadcasts don't need length either even if unreliable
|
||||
* because they are not object bound. */
|
||||
|
||||
//Is spawn or despawn; cache packet.
|
||||
if (spawnOrDespawn)
|
||||
{
|
||||
if (packetId == PacketId.ObjectSpawn)
|
||||
Objects.CacheSpawn(reader);
|
||||
else if (packetId == PacketId.ObjectDespawn)
|
||||
Objects.CacheDespawn(reader);
|
||||
}
|
||||
//Not spawn or despawn.
|
||||
else
|
||||
{
|
||||
/* Iterate object cache should any of the
|
||||
* incoming packets rely on it. Objects
|
||||
* in cache will always be received before any messages
|
||||
* that use them. */
|
||||
Objects.IterateObjectCache();
|
||||
//Then process packet normally.
|
||||
if ((ushort)packetId >= NetworkManager.StartingRpcLinkIndex)
|
||||
{
|
||||
Objects.ParseRpcLink(reader, (ushort)packetId, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.Reconcile)
|
||||
{
|
||||
Objects.ParseReconcileRpc(reader, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.ObserversRpc)
|
||||
{
|
||||
Objects.ParseObserversRpc(reader, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.TargetRpc)
|
||||
{
|
||||
Objects.ParseTargetRpc(reader, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.Broadcast)
|
||||
{
|
||||
ParseBroadcast(reader, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.PingPong)
|
||||
{
|
||||
ParsePingPong(reader);
|
||||
}
|
||||
else if (packetId == PacketId.SyncVar)
|
||||
{
|
||||
Objects.ParseSyncType(reader, false, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.SyncObject)
|
||||
{
|
||||
Objects.ParseSyncType(reader, true, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.PredictedSpawnResult)
|
||||
{
|
||||
Objects.ParsePredictedSpawnResult(reader);
|
||||
}
|
||||
else if (packetId == PacketId.TimingUpdate)
|
||||
{
|
||||
NetworkManager.TimeManager.ParseTimingUpdate();
|
||||
}
|
||||
else if (packetId == PacketId.OwnershipChange)
|
||||
{
|
||||
Objects.ParseOwnershipChange(reader);
|
||||
}
|
||||
else if (packetId == PacketId.Authenticated)
|
||||
{
|
||||
ParseAuthenticated(reader);
|
||||
}
|
||||
else if (packetId == PacketId.Disconnect)
|
||||
{
|
||||
reader.Clear();
|
||||
StopConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
NetworkManager.LogError($"Client received an unhandled PacketId of {(ushort)packetId}. Remaining data has been purged.");
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
_parseLogger.Print(NetworkManager);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Iterate cache when reader is emptied.
|
||||
* This is incase the last packet received
|
||||
* was a spawned, which wouldn't trigger
|
||||
* the above iteration. There's no harm
|
||||
* in doing this check multiple times as there's
|
||||
* an exit early check. */
|
||||
Objects.IterateObjectCache();
|
||||
}
|
||||
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (NetworkManager.CanLog(LoggingType.Error))
|
||||
Debug.LogError($"Client encountered an error while parsing data for packetId {packetId}. Message: {e.Message}.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a PingPong packet.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
private void ParsePingPong(PooledReader reader)
|
||||
{
|
||||
uint clientTick = reader.ReadUInt32(AutoPackType.Unpacked);
|
||||
NetworkManager.TimeManager.ModifyPing(clientTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received connectionId. This is received before client receives connection state change.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
private void ParseAuthenticated(PooledReader reader)
|
||||
{
|
||||
NetworkManager networkManager = NetworkManager;
|
||||
int connectionId = reader.ReadNetworkConnectionId();
|
||||
//If only a client then make a new connection.
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
Clients.TryGetValueIL2CPP(connectionId, out Connection);
|
||||
}
|
||||
/* If also the server then use the servers connection
|
||||
* for the connectionId. This is to resolve host problems
|
||||
* where LocalConnection for client differs from the server Connection
|
||||
* reference, which results in different field values. */
|
||||
else
|
||||
{
|
||||
if (networkManager.ServerManager.Clients.TryGetValueIL2CPP(connectionId, out NetworkConnection conn))
|
||||
{
|
||||
Connection = conn;
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.LogError($"Unable to lookup LocalConnection for {connectionId} as host.");
|
||||
Connection = new NetworkConnection(networkManager, connectionId, false);
|
||||
}
|
||||
}
|
||||
|
||||
//If predicted spawning is enabled also get reserved Ids.
|
||||
if (NetworkManager.PredictionManager.GetAllowPredictedSpawning())
|
||||
{
|
||||
byte count = reader.ReadByte();
|
||||
Queue<int> q = Connection.PredictedObjectIds;
|
||||
for (int i = 0; i < count; i++)
|
||||
q.Enqueue(reader.ReadNetworkObjectId());
|
||||
}
|
||||
|
||||
/* Set the TimeManager tick to lastReceivedTick.
|
||||
* This still doesn't account for latency but
|
||||
* it's the best we can do until the client gets
|
||||
* a ping response. */
|
||||
networkManager.TimeManager.Tick = networkManager.TimeManager.LastPacketTick;
|
||||
|
||||
//Mark as authenticated.
|
||||
Connection.ConnectionAuthenticated();
|
||||
OnAuthenticated?.Invoke();
|
||||
/* Register scene objects for all scenes
|
||||
* after being authenticated. This is done after
|
||||
* authentication rather than when the connection
|
||||
* is started because if also as server an online
|
||||
* scene may already be loaded on server, but not
|
||||
* for client. This means the sceneLoaded unity event
|
||||
* won't fire, and since client isn't authenticated
|
||||
* at the connection start phase objects won't be added. */
|
||||
Objects.RegisterAndDespawnSceneObjects();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Managing/Client/ClientManager.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/Client/ClientManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aca43cf6f20e77c4f8fcc078fd85081f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Client/Editor.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Client/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 934151ddc3910094daef3552e81ecf24
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,46 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client.Editing
|
||||
{
|
||||
|
||||
|
||||
[CustomEditor(typeof(ClientManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class ClientManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _changeFrameRate;
|
||||
private SerializedProperty _frameRate;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_changeFrameRate = serializedObject.FindProperty("_changeFrameRate");
|
||||
_frameRate = serializedObject.FindProperty("_frameRate");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ClientManager)target), typeof(ClientManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
|
||||
EditorGUILayout.PropertyField(_changeFrameRate);
|
||||
if (_changeFrameRate.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_frameRate);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a652d51a1efa8a442966e885e2736599
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Client/Object.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Client/Object.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd638699438c5194ca93b15a5121d0a8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,87 @@
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles objects and information about objects for the local client. See ManagedObjects for inherited options.
|
||||
/// </summary>
|
||||
public partial class ClientObjects : ManagedObjects
|
||||
{
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// RPCLinks of currently spawned objects.
|
||||
/// </summary>
|
||||
private Dictionary<ushort, RpcLink> _rpcLinks = new Dictionary<ushort, RpcLink>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received RPCLink.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="index"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ParseRpcLink(PooledReader reader, ushort index, Channel channel)
|
||||
{
|
||||
int dataLength = Packets.GetPacketLength(ushort.MaxValue, reader, channel);
|
||||
|
||||
//Link index isn't stored.
|
||||
if (!_rpcLinks.TryGetValueIL2CPP(index, out RpcLink link))
|
||||
{
|
||||
SkipDataLength(index, reader, dataLength);
|
||||
return;
|
||||
}
|
||||
else
|
||||
//Found NetworkObject for link.
|
||||
if (Spawned.TryGetValueIL2CPP(link.ObjectId, out NetworkObject nob))
|
||||
{
|
||||
NetworkBehaviour nb = nob.NetworkBehaviours[link.ComponentIndex];
|
||||
if (link.RpcType == RpcType.Target)
|
||||
nb.OnTargetRpc(link.RpcHash, reader, channel);
|
||||
else if (link.RpcType == RpcType.Observers)
|
||||
nb.OnObserversRpc(link.RpcHash, reader, channel);
|
||||
else if (link.RpcType == RpcType.Reconcile)
|
||||
nb.OnReconcileRpc(link.RpcHash, reader, channel);
|
||||
}
|
||||
//Could not find NetworkObject.
|
||||
else
|
||||
{
|
||||
SkipDataLength(index, reader, dataLength, link.ObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets link to rpcLinks key linkIndex.
|
||||
/// </summary>
|
||||
/// <param name="linkIndex"></param>
|
||||
/// <param name="link"></param>
|
||||
internal void SetRpcLink(ushort linkIndex, RpcLink link)
|
||||
{
|
||||
_rpcLinks[linkIndex] = link;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes link index keys from rpcLinks.
|
||||
/// </summary>
|
||||
internal void RemoveLinkIndexes(List<ushort> values)
|
||||
{
|
||||
if (values == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < values.Count; i++)
|
||||
_rpcLinks.Remove(values[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a20cc3f399aa614c931c9b45205937b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
688
Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs
Normal file
688
Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs
Normal file
@ -0,0 +1,688 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles objects and information about objects for the local client. See ManagedObjects for inherited options.
|
||||
/// </summary>
|
||||
public partial class ClientObjects : ManagedObjects
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// NetworkObjects which are cached to be spawned or despawned.
|
||||
/// </summary>
|
||||
private ClientObjectCache _objectCache;
|
||||
#endregion
|
||||
|
||||
internal ClientObjects(NetworkManager networkManager)
|
||||
{
|
||||
base.Initialize(networkManager);
|
||||
_objectCache = new ClientObjectCache(this, networkManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a connection state changes for the local server.
|
||||
/// </summary>
|
||||
internal void OnServerConnectionState(ServerConnectionStateArgs args)
|
||||
{
|
||||
//Nothing needs to be done if started.
|
||||
if (args.ConnectionState == LocalConnectionState.Started)
|
||||
return;
|
||||
|
||||
/* If not started and client is active then deinitialize
|
||||
* client objects first. This will let the deinit calls
|
||||
* perform before the server destroys them. Ideally this
|
||||
* would be done when the user shows intent to shutdown
|
||||
* the server, but realistically planning for server socket
|
||||
* drops is a much more universal solution.
|
||||
*
|
||||
* Calling StopConnection on the client will set it's local state
|
||||
* to Stopping which will result in a deinit. */
|
||||
if (NetworkManager.IsClient)
|
||||
base.NetworkManager.ClientManager.StopConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the connection state changes for the local client.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
internal void OnClientConnectionState(ClientConnectionStateArgs args)
|
||||
{
|
||||
/* If new state is not started then reset
|
||||
* environment. */
|
||||
if (args.ConnectionState != LocalConnectionState.Started)
|
||||
{
|
||||
_objectCache.Reset();
|
||||
|
||||
//If not server then deinitialize normally.
|
||||
if (!base.NetworkManager.IsServer)
|
||||
{
|
||||
base.DespawnWithoutSynchronization(false);
|
||||
}
|
||||
//Otherwise invoke stop callbacks only for client side.
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject n in Spawned.Values)
|
||||
n.InvokeStopCallbacks(false);
|
||||
}
|
||||
/* Clear spawned and scene objects as they will be rebuilt.
|
||||
* Spawned would have already be cleared if DespawnSpawned
|
||||
* was called but it won't hurt anything clearing an empty collection. */
|
||||
base.Spawned.Clear();
|
||||
base.SceneObjects.Clear();
|
||||
base.LocalClientSpawned.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when a scene is loaded.
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="arg1"></param>
|
||||
[APIExclude]
|
||||
protected internal override void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1)
|
||||
{
|
||||
base.SceneManager_sceneLoaded(s, arg1);
|
||||
|
||||
if (!base.NetworkManager.IsClient)
|
||||
return;
|
||||
/* When a scene first loads for a client it should disable
|
||||
* all network objects in that scene. The server will send
|
||||
* spawn messages once it's aware client has loaded the scene. */
|
||||
RegisterAndDespawnSceneObjects(s);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a predicted spawn to the server.
|
||||
/// </summary>
|
||||
internal void PredictedSpawn(NetworkObject networkObject, NetworkConnection ownerConnection)
|
||||
{
|
||||
//No more Ids to use.
|
||||
Queue<int> predictedObjectIds = NetworkManager.ClientManager.Connection.PredictedObjectIds;
|
||||
if (predictedObjectIds.Count == 0)
|
||||
{
|
||||
NetworkManager.LogError($"Predicted spawn for object {networkObject.name} failed because no more predicted ObjectIds remain. This usually occurs when the client is spawning excessively before the server can respond. Increasing ReservedObjectIds within the ServerManager component or reducing spawn rate could prevent this problem.");
|
||||
return;
|
||||
}
|
||||
|
||||
networkObject.PreinitializePredictedObject_Client(base.NetworkManager, predictedObjectIds.Dequeue(), ownerConnection, base.NetworkManager.ClientManager.Connection);
|
||||
NetworkManager.ClientManager.Objects.AddToSpawned(networkObject, false);
|
||||
networkObject.Initialize(false, true);
|
||||
|
||||
PooledWriter writer = WriterPool.GetWriter();
|
||||
WriteSpawn(networkObject, writer);
|
||||
base.NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a predicted spawn.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
public void WriteSpawn(NetworkObject nob, Writer writer)
|
||||
{
|
||||
PooledWriter headerWriter = WriterPool.GetWriter();
|
||||
headerWriter.WritePacketId(PacketId.ObjectSpawn);
|
||||
headerWriter.WriteNetworkObjectForSpawn(nob);
|
||||
headerWriter.WriteNetworkConnection(nob.Owner);
|
||||
|
||||
bool sceneObject = nob.IsSceneObject;
|
||||
//Write type of spawn.
|
||||
SpawnType st = SpawnType.Unset;
|
||||
if (sceneObject)
|
||||
st |= SpawnType.Scene;
|
||||
else
|
||||
st |= (nob.IsGlobal) ? SpawnType.InstantiatedGlobal : SpawnType.Instantiated;
|
||||
headerWriter.WriteByte((byte)st);
|
||||
|
||||
//ComponentIndex for the nob. 0 is root but more appropriately there's a IsNested boolean as shown above.
|
||||
headerWriter.WriteByte(nob.ComponentIndex);
|
||||
//Properties on the transform which diff from serialized value.
|
||||
base.WriteChangedTransformProperties(nob, sceneObject, false, headerWriter);
|
||||
/* Writing a scene object. */
|
||||
if (sceneObject)
|
||||
{
|
||||
headerWriter.WriteUInt64(nob.SceneId, AutoPackType.Unpacked);
|
||||
}
|
||||
/* Writing a spawned object. */
|
||||
else
|
||||
{
|
||||
//Nested predicted spawning will be added later.
|
||||
headerWriter.WriteByte((byte)SpawnParentType.Unset);
|
||||
headerWriter.WriteNetworkObjectId(nob.PrefabId);
|
||||
}
|
||||
|
||||
writer.WriteBytes(headerWriter.GetBuffer(), 0, headerWriter.Length);
|
||||
|
||||
//If allowed to write synctypes.
|
||||
if (nob.AllowPredictedSyncTypes)
|
||||
{
|
||||
PooledWriter tempWriter = WriterPool.GetWriter();
|
||||
WriteSyncTypes(writer, tempWriter, SyncTypeWriteType.All);
|
||||
void WriteSyncTypes(Writer finalWriter, PooledWriter tWriter, SyncTypeWriteType writeType)
|
||||
{
|
||||
tWriter.Reset();
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteSyncTypesForSpawn(tWriter, writeType);
|
||||
finalWriter.WriteBytesAndSize(tWriter.GetBuffer(), 0, tWriter.Length);
|
||||
}
|
||||
tempWriter.Dispose();
|
||||
}
|
||||
|
||||
//Dispose of writers created in this method.
|
||||
headerWriter.Dispose();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a predicted despawn to the server.
|
||||
/// </summary>
|
||||
internal void PredictedDespawn(NetworkObject networkObject)
|
||||
{
|
||||
PooledWriter writer = WriterPool.GetWriter();
|
||||
WriteDepawn(networkObject, writer);
|
||||
base.NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
|
||||
writer.Dispose();
|
||||
|
||||
//Deinitialize after writing despawn so all the right data is sent.
|
||||
networkObject.DeinitializePredictedObject_Client();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a predicted despawn.
|
||||
/// </summary>
|
||||
public void WriteDepawn(NetworkObject nob, Writer writer)
|
||||
{
|
||||
writer.WritePacketId(PacketId.ObjectDespawn);
|
||||
writer.WriteNetworkObject(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers NetworkObjects in all scenes and despawns them.
|
||||
/// </summary>
|
||||
internal void RegisterAndDespawnSceneObjects()
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
RegisterAndDespawnSceneObjects(SceneManager.GetSceneAt(i));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds NetworkObjects within s to SceneObjects, and despawns them.
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
private void RegisterAndDespawnSceneObjects(Scene s)
|
||||
{
|
||||
ListCache<NetworkObject> nobs;
|
||||
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
|
||||
|
||||
for (int i = 0; i < nobs.Written; i++)
|
||||
{
|
||||
NetworkObject nob = nobs.Collection[i];
|
||||
if (!nob.IsSceneObject)
|
||||
continue;
|
||||
|
||||
base.UpdateNetworkBehavioursForSceneObject(nob, false);
|
||||
if (nob.IsNetworked && nob.IsNetworked)
|
||||
{
|
||||
base.AddToSceneObjects(nob);
|
||||
//Only run if not also server, as this already ran on server.
|
||||
if (!base.NetworkManager.IsServer)
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
ListCaches.StoreCache(nobs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a NetworkObject runs Deactivate.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
internal override void NetworkObjectUnexpectedlyDestroyed(NetworkObject nob, bool asServer)
|
||||
{
|
||||
nob.RemoveClientRpcLinkIndexes();
|
||||
base.NetworkObjectUnexpectedlyDestroyed(nob, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an OwnershipChange packet.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal void ParseOwnershipChange(PooledReader reader)
|
||||
{
|
||||
NetworkObject nob = reader.ReadNetworkObject();
|
||||
NetworkConnection newOwner = reader.ReadNetworkConnection();
|
||||
if (nob != null)
|
||||
nob.GiveOwnership(newOwner, false);
|
||||
else
|
||||
NetworkManager.LogWarning($"NetworkBehaviour could not be found when trying to parse OwnershipChange packet.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received syncVar.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ParseSyncType(PooledReader reader, bool isSyncObject, Channel channel)
|
||||
{
|
||||
//cleanup this is unique to synctypes where length comes first.
|
||||
//this will change once I tidy up synctypes.
|
||||
ushort packetId = (isSyncObject) ? (ushort)PacketId.SyncObject : (ushort)PacketId.SyncVar;
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength(packetId, reader, channel);
|
||||
|
||||
if (nb != null)
|
||||
{
|
||||
/* Length of data to be read for syncvars.
|
||||
* This is important because syncvars are never
|
||||
* a set length and data must be read through completion.
|
||||
* The only way to know where completion of syncvar is, versus
|
||||
* when another packet starts is by including the length. */
|
||||
if (dataLength > 0)
|
||||
nb.OnSyncType(reader, dataLength, isSyncObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
SkipDataLength(packetId, reader, dataLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal void ParsePredictedSpawnResult(Reader reader)
|
||||
{
|
||||
int usedObjectId = reader.ReadNetworkObjectId();
|
||||
bool success = reader.ReadBoolean();
|
||||
if (success)
|
||||
{
|
||||
int nextObjectId = reader.ReadNetworkObjectId();
|
||||
if (nextObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
NetworkManager.ClientManager.Connection.PredictedObjectIds.Enqueue(nextObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ReconcileRpc.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ParseReconcileRpc(PooledReader reader, Channel channel)
|
||||
{
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel);
|
||||
|
||||
if (nb != null)
|
||||
nb.OnReconcileRpc(null, reader, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an ObserversRpc.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ParseObserversRpc(PooledReader reader, Channel channel)
|
||||
{
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel);
|
||||
|
||||
if (nb != null)
|
||||
nb.OnObserversRpc(null, reader, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength);
|
||||
}
|
||||
/// <summary>
|
||||
/// Parses a TargetRpc.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ParseTargetRpc(PooledReader reader, Channel channel)
|
||||
{
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel);
|
||||
|
||||
if (nb != null)
|
||||
nb.OnTargetRpc(null, reader, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.TargetRpc, reader, dataLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Caches a received spawn to be processed after all spawns and despawns are received for the tick.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal void CacheSpawn(PooledReader reader)
|
||||
{
|
||||
sbyte initializeOrder;
|
||||
ushort collectionId;
|
||||
int objectId = reader.ReadNetworkObjectForSpawn(out initializeOrder, out collectionId, out _);
|
||||
int ownerId = reader.ReadNetworkConnectionId();
|
||||
SpawnType st = (SpawnType)reader.ReadByte();
|
||||
byte componentIndex = reader.ReadByte();
|
||||
|
||||
//Read transform values which differ from serialized values.
|
||||
Vector3? localPosition;
|
||||
Quaternion? localRotation;
|
||||
Vector3? localScale;
|
||||
base.ReadTransformProperties(reader, out localPosition, out localRotation, out localScale);
|
||||
|
||||
bool nested = SpawnTypeEnum.Contains(st, SpawnType.Nested);
|
||||
int rootObjectId = (nested) ? reader.ReadNetworkObjectId() : 0;
|
||||
bool sceneObject = SpawnTypeEnum.Contains(st, SpawnType.Scene);
|
||||
|
||||
int? parentObjectId = null;
|
||||
byte? parentComponentIndex = null;
|
||||
int? prefabId = null;
|
||||
ulong sceneId = 0;
|
||||
|
||||
if (sceneObject)
|
||||
ReadSceneObject(reader, out sceneId);
|
||||
else
|
||||
ReadSpawnedObject(reader, out parentObjectId, out parentComponentIndex, out prefabId);
|
||||
|
||||
ArraySegment<byte> rpcLinks = reader.ReadArraySegmentAndSize();
|
||||
ArraySegment<byte> syncValues = reader.ReadArraySegmentAndSize();
|
||||
|
||||
/* If the objectId can be found as already spawned then check if it's predicted.
|
||||
* Should the spawn be predicted then no need to continue. Later on however
|
||||
* we may want to apply synctypes.
|
||||
*
|
||||
* Only check if not server, since if server the client doesnt need
|
||||
* to predicted spawn. */
|
||||
if (!base.NetworkManager.IsServerOnly && base.Spawned.TryGetValue(objectId, out NetworkObject nob))
|
||||
{
|
||||
//If not predicted the nob should not be in spawned.
|
||||
if (!nob.PredictedSpawner.IsValid)
|
||||
{
|
||||
NetworkManager.LogError($"Received a spawn objectId of {objectId} which was already found in spawned, and was not predicted.");
|
||||
}
|
||||
//Everything is proper, apply RPC links.
|
||||
else
|
||||
{
|
||||
PooledReader linkReader = ReaderPool.GetReader(rpcLinks, NetworkManager);
|
||||
ApplyRpcLinks(nob, linkReader);
|
||||
linkReader.Dispose();
|
||||
}
|
||||
//No further initialization needed when predicting.
|
||||
return;
|
||||
}
|
||||
|
||||
_objectCache.AddSpawn(base.NetworkManager, collectionId, objectId, initializeOrder, ownerId, st, componentIndex, rootObjectId, parentObjectId, parentComponentIndex, prefabId, localPosition, localRotation, localScale, sceneId, rpcLinks, syncValues);
|
||||
}
|
||||
/// <summary>
|
||||
/// Caches a received despawn to be processed after all spawns and despawns are received for the tick.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal void CacheDespawn(PooledReader reader)
|
||||
{
|
||||
DespawnType despawnType;
|
||||
int objectId = reader.ReadNetworkObjectForDepawn(out despawnType);
|
||||
_objectCache.AddDespawn(objectId, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates object cache which contains spawn and despawn messages.
|
||||
/// Parses the packets within the cache and ensures objects are spawned and despawned before their sync values are applied.
|
||||
/// This ensures there is no chance a sync value is referencing a spawned object which does not exist yet due to it normally being spawned later in the cache.
|
||||
/// </summary>
|
||||
internal void IterateObjectCache()
|
||||
{
|
||||
_objectCache.Iterate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a nested NetworkObject within it's root.
|
||||
/// </summary>
|
||||
/// <param name="cnob"></param>
|
||||
/// <returns></returns>
|
||||
internal NetworkObject GetNestedNetworkObject(CachedNetworkObject cnob)
|
||||
{
|
||||
NetworkObject rootNob;
|
||||
int rootObjectId = cnob.RootObjectId;
|
||||
byte componentIndex = cnob.ComponentIndex;
|
||||
|
||||
/* Spawns are processed after all spawns come in,
|
||||
* this ensures no reference race conditions. Turns out because of this
|
||||
* the parentNob may be in cache and not actually spawned, if it was spawned the same packet
|
||||
* as this one. So when not found in the spawned collection try to
|
||||
* find it in Spawning before throwing. */
|
||||
rootNob = _objectCache.GetSpawnedObject(rootObjectId);
|
||||
//If still null, that's not good.
|
||||
if (rootNob == null)
|
||||
{
|
||||
NetworkManager.LogError($"Nested spawned object with componentIndex of {componentIndex} and a parentId of {rootObjectId} could not be spawned because parent was not found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
NetworkObject nob = null;
|
||||
List<NetworkObject> childNobs = rootNob.ChildNetworkObjects;
|
||||
//Find nob with component index.
|
||||
for (int i = 0; i < childNobs.Count; i++)
|
||||
{
|
||||
if (childNobs[i].ComponentIndex == componentIndex)
|
||||
{
|
||||
nob = childNobs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
//If child nob was not found.
|
||||
if (nob == null)
|
||||
{
|
||||
NetworkManager.LogError($"Nested spawned object with componentIndex of {componentIndex} could not be found as a child NetworkObject of {rootNob.name}.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies RPCLinks to a NetworkObject.
|
||||
/// </summary>
|
||||
internal void ApplyRpcLinks(NetworkObject nob, Reader reader)
|
||||
{
|
||||
List<ushort> rpcLinkIndexes = new List<ushort>();
|
||||
//Apply rpcLinks.
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
{
|
||||
int length = reader.ReadInt32();
|
||||
|
||||
int readerStart = reader.Position;
|
||||
while (reader.Position - readerStart < length)
|
||||
{
|
||||
//Index of RpcLink.
|
||||
ushort linkIndex = reader.ReadUInt16();
|
||||
RpcLink link = new RpcLink(nob.ObjectId, nb.ComponentIndex,
|
||||
//RpcHash.
|
||||
reader.ReadUInt16(),
|
||||
//ObserverRpc.
|
||||
(RpcType)reader.ReadByte());
|
||||
//Add to links.
|
||||
SetRpcLink(linkIndex, link);
|
||||
|
||||
rpcLinkIndexes.Add(linkIndex);
|
||||
}
|
||||
}
|
||||
nob.SetRpcLinkIndexes(rpcLinkIndexes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a NetworkObject if required and sets transform values.
|
||||
/// </summary>
|
||||
internal NetworkObject GetInstantiatedNetworkObject(CachedNetworkObject cnob)
|
||||
{
|
||||
if (cnob.PrefabId == null)
|
||||
{
|
||||
NetworkManager.LogError($"PrefabId for {cnob.ObjectId} is null. Object will not spawn.");
|
||||
return null;
|
||||
}
|
||||
|
||||
NetworkManager networkManager = base.NetworkManager;
|
||||
int prefabId = cnob.PrefabId.Value;
|
||||
NetworkObject result;
|
||||
|
||||
if (prefabId == NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
{
|
||||
NetworkManager.LogError($"Spawned object has an invalid prefabId. Make sure all objects which are being spawned over the network are within SpawnableObjects on the NetworkManager.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ushort collectionId = cnob.CollectionId;
|
||||
//PrefabObjects to get the prefab from.
|
||||
PrefabObjects prefabObjects = networkManager.GetPrefabObjects<PrefabObjects>(collectionId, false);
|
||||
//Not found for collectionId > 0. This means the user likely did not setup the collection on client.
|
||||
if (prefabObjects == null && collectionId > 0)
|
||||
{
|
||||
networkManager.LogError($"PrefabObjects collection is not found for CollectionId {collectionId}. Be sure to add your addressables NetworkObject prefabs to the collection on server and client before attempting to spawn them over the network.");
|
||||
return null;
|
||||
}
|
||||
|
||||
//Only instantiate if not host.
|
||||
if (!networkManager.IsHost)
|
||||
{
|
||||
Transform parentTransform = null;
|
||||
bool hasParent = (cnob.ParentObjectId != null);
|
||||
//Set parentTransform if there's a parent object.
|
||||
if (hasParent)
|
||||
{
|
||||
int objectId = cnob.ParentObjectId.Value;
|
||||
NetworkObject nob = _objectCache.GetSpawnedObject(objectId);
|
||||
|
||||
if (nob == null)
|
||||
{
|
||||
NetworkObject prefab = prefabObjects.GetObject(false, prefabId);
|
||||
networkManager.LogError($"NetworkObject not found for ObjectId {objectId}. Prefab {prefab.name} will be instantiated without parent synchronization.");
|
||||
}
|
||||
else
|
||||
{
|
||||
//If parent object is a network behaviour then find the component.
|
||||
if (cnob.ParentIsNetworkBehaviour)
|
||||
{
|
||||
byte componentIndex = cnob.ComponentIndex;
|
||||
NetworkBehaviour nb = nob.GetNetworkBehaviour(componentIndex, false);
|
||||
if (nb != null)
|
||||
{
|
||||
parentTransform = nb.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkObject prefab = prefabObjects.GetObject(false, prefabId);
|
||||
networkManager.LogError($"NetworkBehaviour on index {componentIndex} could nto be found within NetworkObject {nob.name} with ObjectId {objectId}. Prefab {prefab.name} will be instantiated without parent synchronization.");
|
||||
}
|
||||
}
|
||||
//The networkObject is the parent.
|
||||
else
|
||||
{
|
||||
parentTransform = nob.transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = networkManager.GetPooledInstantiated(prefabId, collectionId, false);
|
||||
Transform t = result.transform;
|
||||
t.SetParent(parentTransform, true);
|
||||
//Only need to set IsGlobal also if not host.
|
||||
bool isGlobal = SpawnTypeEnum.Contains(cnob.SpawnType, SpawnType.InstantiatedGlobal);
|
||||
result.SetIsGlobal(isGlobal);
|
||||
}
|
||||
//If host then find server instantiated object.
|
||||
else
|
||||
{
|
||||
ServerObjects so = networkManager.ServerManager.Objects;
|
||||
if (!so.Spawned.TryGetValueIL2CPP(cnob.ObjectId, out result))
|
||||
result = so.GetFromPending(cnob.ObjectId);
|
||||
|
||||
if (result == null)
|
||||
networkManager.LogError($"ObjectId {cnob.ObjectId} could not be found in Server spawned, nor Server pending despawn.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a NetworkObject from Spawned, or object cache.
|
||||
/// </summary>
|
||||
/// <param name="cnob"></param>
|
||||
/// <returns></returns>
|
||||
internal NetworkObject GetSpawnedNetworkObject(CachedNetworkObject cnob)
|
||||
{
|
||||
NetworkObject nob;
|
||||
//Try checking already spawned objects first.
|
||||
if (base.Spawned.TryGetValueIL2CPP(cnob.ObjectId, out nob))
|
||||
{
|
||||
return nob;
|
||||
}
|
||||
/* If not found in already spawned objects see if
|
||||
* the networkObject is in the objectCache. It's possible the despawn
|
||||
* came immediately or shortly after the spawn message, before
|
||||
* the object has been initialized. */
|
||||
else
|
||||
{
|
||||
nob = _objectCache.GetInCached(cnob.ObjectId, ClientObjectCache.CacheSearchType.Any);
|
||||
/* Nob may be null if it's a child object being despawned, and the
|
||||
* parent despawn already occurred. */
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes reading a scene object.
|
||||
/// </summary>
|
||||
private void ReadSceneObject(PooledReader reader, out ulong sceneId)
|
||||
{
|
||||
sceneId = reader.ReadUInt64(AutoPackType.Unpacked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes reading a spawned object, and instantiates the object.
|
||||
/// </summary>
|
||||
private void ReadSpawnedObject(PooledReader reader, out int? parentObjectId, out byte? parentComponentIndex, out int? prefabId)
|
||||
{
|
||||
//Parent.
|
||||
SpawnParentType spt = (SpawnParentType)reader.ReadByte();
|
||||
//Defaults.
|
||||
parentObjectId = null;
|
||||
parentComponentIndex = null;
|
||||
|
||||
if (spt == SpawnParentType.NetworkObject)
|
||||
{
|
||||
int objectId = reader.ReadNetworkObjectId();
|
||||
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
parentObjectId = objectId;
|
||||
}
|
||||
else if (spt == SpawnParentType.NetworkBehaviour)
|
||||
{
|
||||
reader.ReadNetworkBehaviour(out int objectId, out byte componentIndex);
|
||||
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
{
|
||||
parentObjectId = objectId;
|
||||
parentComponentIndex = componentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
prefabId = (ushort)reader.ReadNetworkObjectId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da027fa27b0c0994ebfa317968862970
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
605
Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs
Normal file
605
Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs
Normal file
@ -0,0 +1,605 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Object;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Extension;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Information about cached network objects.
|
||||
/// </summary>
|
||||
internal class ClientObjectCache
|
||||
{
|
||||
#region Types.
|
||||
public enum CacheSearchType
|
||||
{
|
||||
Any = 0,
|
||||
Spawning = 1,
|
||||
Despawning = 2
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Objets which are being spawned during iteration.
|
||||
/// </summary>
|
||||
internal Dictionary<int, NetworkObject> SpawningObjects = new Dictionary<int, NetworkObject>();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Cached objects buffer. Contains spawns and despawns.
|
||||
/// </summary>
|
||||
private ListCache<CachedNetworkObject> _cachedObjects = new ListCache<CachedNetworkObject>();
|
||||
/// <summary>
|
||||
/// NetworkObjects which have been spawned already during the current iteration.
|
||||
/// </summary>
|
||||
private HashSet<NetworkObject> _iteratedSpawns = new HashSet<NetworkObject>();
|
||||
/// <summary>
|
||||
/// Despawns which are occurring the same tick as their spawn.
|
||||
/// </summary>
|
||||
private HashSet<int> _conflictingDespawns = new HashSet<int>();
|
||||
/// <summary>
|
||||
/// ClientObjects reference.
|
||||
/// </summary>
|
||||
private ClientObjects _clientObjects;
|
||||
/// <summary>
|
||||
/// NetworkManager for this cache.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// True if logged the warning about despawning on the same tick as the spawn.
|
||||
/// This exist to prevent excessive spam of the warning.
|
||||
/// </summary>
|
||||
private bool _loggedSameTickWarning;
|
||||
/// <summary>
|
||||
/// True if initializeOrder was not default for any spawned objects.
|
||||
/// </summary>
|
||||
private bool _initializeOrderChanged;
|
||||
#endregion
|
||||
|
||||
public ClientObjectCache(ClientObjects cobs, NetworkManager networkManager)
|
||||
{
|
||||
_clientObjects = cobs;
|
||||
_networkManager = networkManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a NetworkObject found in spawned cache using objectId.
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <returns></returns>
|
||||
public NetworkObject GetInCached(int objectId, CacheSearchType searchType)
|
||||
{
|
||||
int count = _cachedObjects.Written;
|
||||
List<CachedNetworkObject> collection = _cachedObjects.Collection;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
CachedNetworkObject cnob = collection[i];
|
||||
if (cnob.ObjectId == objectId)
|
||||
{
|
||||
//Any condition always returns.
|
||||
if (searchType == CacheSearchType.Any)
|
||||
return cnob.NetworkObject;
|
||||
|
||||
bool spawning = (searchType == CacheSearchType.Spawning);
|
||||
bool spawnAction = (cnob.Action == CachedNetworkObject.ActionType.Spawn);
|
||||
if (spawning == spawnAction)
|
||||
return cnob.NetworkObject;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Fall through.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes for a spawned NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
/// <param name="syncValues"></param>
|
||||
/// <param name="manager"></param>
|
||||
public void AddSpawn(NetworkManager manager, ushort collectionId, int objectId, sbyte initializeOrder, int ownerId, SpawnType ost, byte componentIndex, int rootObjectId, int? parentObjectId, byte? parentComponentIndex
|
||||
, int? prefabId, Vector3? localPosition, Quaternion? localRotation, Vector3? localScale, ulong sceneId, ArraySegment<byte> rpcLinks, ArraySegment<byte> syncValues)
|
||||
{
|
||||
//Set if initialization order has changed.
|
||||
_initializeOrderChanged |= (initializeOrder != 0);
|
||||
|
||||
CachedNetworkObject cnob = null;
|
||||
//If order has not changed then add normally.
|
||||
if (!_initializeOrderChanged)
|
||||
{
|
||||
cnob = _cachedObjects.AddReference();
|
||||
}
|
||||
//Otherwise see if values need to be sorted.
|
||||
else
|
||||
{
|
||||
/* Spawns will be ordered at the end of their nearest order.
|
||||
* If spawns arrived with Id order of 5, 7, 2 then the result
|
||||
* would be as shown below...
|
||||
* Id 5 / order -5
|
||||
* Id 7 / order -5
|
||||
* Id 2 / order 0
|
||||
* Not as if the values were inserted first such as...
|
||||
* Id 7 / order -5
|
||||
* Id 5 / order -5
|
||||
* Id 2 / order 0
|
||||
* This is to prevent the likeliness of child nobs being out of order
|
||||
* as well to preserve user spawn order if they spawned multiple
|
||||
* objects the same which, with the same order. */
|
||||
|
||||
int written = _cachedObjects.Written;
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
CachedNetworkObject item = _cachedObjects.Collection[i];
|
||||
/* If item order is larger then that means
|
||||
* initializeOrder has reached the last entry
|
||||
* of its value. Insert just before item index. */
|
||||
if (initializeOrder < item.InitializeOrder)
|
||||
{
|
||||
cnob = _cachedObjects.InsertReference(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//If here and cnob is null then it was not inserted; add to end.
|
||||
if (cnob == null)
|
||||
cnob = _cachedObjects.AddReference();
|
||||
}
|
||||
|
||||
cnob.InitializeSpawn(manager, collectionId, objectId, initializeOrder, ownerId, ost, componentIndex, rootObjectId, parentObjectId, parentComponentIndex
|
||||
, prefabId, localPosition, localRotation, localScale, sceneId, rpcLinks, syncValues);
|
||||
}
|
||||
|
||||
public void AddDespawn(int objectId, DespawnType despawnType)
|
||||
{
|
||||
CachedNetworkObject cnob = _cachedObjects.AddReference();
|
||||
cnob.InitializeDespawn(objectId, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates any written objects.
|
||||
/// </summary>
|
||||
public void Iterate()
|
||||
{
|
||||
int written = _cachedObjects.Written;
|
||||
if (written == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
//Indexes which have already been processed.
|
||||
HashSet<int> processedIndexes = new HashSet<int>();
|
||||
List<CachedNetworkObject> collection = _cachedObjects.Collection;
|
||||
_conflictingDespawns.Clear();
|
||||
/* The next iteration will set rpclinks,
|
||||
* synctypes, and so on. */
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
/* An index may already be processed if it was pushed ahead.
|
||||
* This can occur if a nested object spawn exists but the root
|
||||
* object has not spawned yet. In this situation the root spawn is
|
||||
* found and performed first. */
|
||||
if (processedIndexes.Contains(i))
|
||||
continue;
|
||||
CachedNetworkObject cnob = collection[i];
|
||||
bool spawn = (cnob.Action == CachedNetworkObject.ActionType.Spawn);
|
||||
|
||||
/* See if nested, and if so check if root is already spawned.
|
||||
* If parent is not spawned then find it and process the parent first. */
|
||||
if (spawn)
|
||||
{
|
||||
/* When an object is nested or has a parent it is
|
||||
* dependent upon either the root of nested, or the parent,
|
||||
* being spawned to setup properly.
|
||||
*
|
||||
* When either of these are true check spawned objects first
|
||||
* to see if the objects exist. If not check if they are appearing
|
||||
* later in the cache. Root or parent objects can appear later
|
||||
* in the cache depending on the order of which observers are rebuilt.
|
||||
* While it is possible to have the server ensure spawns always send
|
||||
* root/parents first, that's a giant can of worms that's not worth getting into.
|
||||
* Not only are there many scenarios to cover, but it also puts more work
|
||||
* on the server. It's more effective to have the client handle the sorting. */
|
||||
|
||||
//Nested.
|
||||
if (cnob.IsNested || cnob.HasParent)
|
||||
{
|
||||
bool nested = cnob.IsNested;
|
||||
//It's not possible to be nested and have a parent. Set the Id to look for based on if nested or parented.
|
||||
int targetObjectId = (nested) ? cnob.RootObjectId : cnob.ParentObjectId.Value;
|
||||
NetworkObject nob = GetSpawnedObject(targetObjectId);
|
||||
//If not spawned yet.
|
||||
if (nob == null)
|
||||
{
|
||||
bool found = false;
|
||||
string errMsg;
|
||||
for (int z = (i + 1); z < written; z++)
|
||||
{
|
||||
CachedNetworkObject zCnob = collection[z];
|
||||
if (zCnob.ObjectId == targetObjectId)
|
||||
{
|
||||
found = true;
|
||||
if (cnob.Action != CachedNetworkObject.ActionType.Spawn)
|
||||
{
|
||||
errMsg = (nested)
|
||||
? $"ObjectId {targetObjectId} was found for a nested spawn, but ActionType is not spawn. ComponentIndex {cnob.ComponentIndex} will not be spawned."
|
||||
: $"ObjectId {targetObjectId} was found for a parented spawn, but ActionType is not spawn. ObjectId {cnob.ObjectId} will not be spawned.";
|
||||
_networkManager.LogError(errMsg);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessObject(zCnob, true, z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Root nob could not be found.
|
||||
if (!found)
|
||||
{
|
||||
errMsg = (nested)
|
||||
? $"ObjectId {targetObjectId} could not be found for a nested spawn. ComponentIndex {cnob.ComponentIndex} will not be spawned."
|
||||
: $"ObjectId {targetObjectId} was found for a parented spawn. ObjectId {cnob.ObjectId} will not be spawned.";
|
||||
_networkManager.LogError(errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessObject(cnob, spawn, i);
|
||||
}
|
||||
|
||||
void ProcessObject(CachedNetworkObject cnob, bool spawn, int index)
|
||||
{
|
||||
processedIndexes.Add(index);
|
||||
|
||||
if (spawn)
|
||||
{
|
||||
if (cnob.IsSceneObject)
|
||||
cnob.NetworkObject = _clientObjects.GetSceneNetworkObject(cnob.SceneId);
|
||||
else if (cnob.IsNested)
|
||||
cnob.NetworkObject = _clientObjects.GetNestedNetworkObject(cnob);
|
||||
else
|
||||
cnob.NetworkObject = _clientObjects.GetInstantiatedNetworkObject(cnob);
|
||||
|
||||
/* Apply transform changes but only if not host.
|
||||
* These would have already been applied server side. */
|
||||
if (!_networkManager.IsHost && cnob.NetworkObject != null)
|
||||
{
|
||||
Transform t = cnob.NetworkObject.transform;
|
||||
_clientObjects.GetTransformProperties(cnob.LocalPosition, cnob.LocalRotation, cnob.LocalScale, t, out Vector3 pos, out Quaternion rot, out Vector3 scale);
|
||||
t.SetLocalPositionRotationAndScale(pos, rot, scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cnob.NetworkObject = _clientObjects.GetSpawnedNetworkObject(cnob);
|
||||
/* Do not log unless not nested. Nested nobs sometimes
|
||||
* could be destroyed if parent was first. */
|
||||
if (!_networkManager.IsHost && cnob.NetworkObject == null && !cnob.IsNested)
|
||||
_networkManager.Log($"NetworkObject for ObjectId of {cnob.ObjectId} was found null. Unable to despawn object. This may occur if a nested NetworkObject had it's parent object unexpectedly destroyed. This incident is often safe to ignore.");
|
||||
}
|
||||
NetworkObject nob = cnob.NetworkObject;
|
||||
//No need to error here, the other Gets above would have.
|
||||
if (nob == null)
|
||||
return;
|
||||
|
||||
if (spawn)
|
||||
{
|
||||
//If not also server then object also has to be preinitialized.
|
||||
if (!_networkManager.IsServer)
|
||||
{
|
||||
int ownerId = cnob.OwnerId;
|
||||
//If local client is owner then use localconnection reference.
|
||||
NetworkConnection localConnection = _networkManager.ClientManager.Connection;
|
||||
NetworkConnection owner;
|
||||
//If owner is self.
|
||||
if (ownerId == localConnection.ClientId)
|
||||
{
|
||||
owner = localConnection;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If owner cannot be found then share owners
|
||||
* is disabled */
|
||||
if (!_networkManager.ClientManager.Clients.TryGetValueIL2CPP(ownerId, out owner))
|
||||
owner = NetworkManager.EmptyConnection;
|
||||
}
|
||||
nob.Preinitialize_Internal(_networkManager, cnob.ObjectId, owner, false);
|
||||
}
|
||||
|
||||
_clientObjects.AddToSpawned(cnob.NetworkObject, false);
|
||||
SpawningObjects.Add(cnob.ObjectId, cnob.NetworkObject);
|
||||
|
||||
IterateSpawn(cnob);
|
||||
_iteratedSpawns.Add(cnob.NetworkObject);
|
||||
|
||||
/* Enable networkObject here if client only.
|
||||
* This is to ensure Awake fires in the same order
|
||||
* as InitializeOrder settings. There is no need
|
||||
* to perform this action if server because server
|
||||
* would have already spawned in order. */
|
||||
if (!_networkManager.IsServer && cnob.NetworkObject != null)
|
||||
cnob.NetworkObject.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If spawned already this iteration then the nob
|
||||
* must be initialized so that the start/stop cycles
|
||||
* complete normally. Otherwise, the despawn callbacks will
|
||||
* fire immediately while the start callbacks will run after all
|
||||
* spawns have been iterated.
|
||||
* The downside to this is that synctypes
|
||||
* for spawns later in this iteration will not be initialized
|
||||
* yet, and if the nob being spawned/despawned references
|
||||
* those synctypes the values will be default.
|
||||
*
|
||||
* The alternative is to delay the despawning until after
|
||||
* all spawns are iterated, but that will break the order
|
||||
* reliability. This is unfortunately a lose/lose situation so
|
||||
* the best we can do is let the user know the risk. */
|
||||
if (_iteratedSpawns.Contains(cnob.NetworkObject))
|
||||
{
|
||||
if (!_loggedSameTickWarning)
|
||||
{
|
||||
_loggedSameTickWarning = true;
|
||||
_networkManager.LogWarning($"NetworkObject {cnob.NetworkObject.name} is being despawned on the same tick it's spawned." +
|
||||
$" When this occurs SyncTypes will not be set on other objects during the time of this despawn." +
|
||||
$" In result, if NetworkObject {cnob.NetworkObject.name} is referencing a SyncType of another object being spawned this tick, the returned values will be default.");
|
||||
}
|
||||
|
||||
_conflictingDespawns.Add(cnob.ObjectId);
|
||||
cnob.NetworkObject.gameObject.SetActive(true);
|
||||
cnob.NetworkObject.Initialize(false, true);
|
||||
}
|
||||
//Now being initialized, despawn the object.
|
||||
IterateDespawn(cnob);
|
||||
}
|
||||
}
|
||||
|
||||
/* Activate the objects after all data
|
||||
* has been synchronized. This will apply synctypes. */
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
CachedNetworkObject cnob = collection[i];
|
||||
if (cnob.Action == CachedNetworkObject.ActionType.Spawn && cnob.NetworkObject != null)
|
||||
{
|
||||
/* Apply syncTypes. It's very important to do this after all
|
||||
* spawns have been processed and added to the manager.Objects collection.
|
||||
* Otherwise, the synctype may reference an object spawning the same tick
|
||||
* and the result would be null due to said object not being in spawned.
|
||||
*
|
||||
* At this time the NetworkObject is not initialized so by calling
|
||||
* OnSyncType the changes are cached to invoke callbacks after initialization,
|
||||
* not during the time of this action. */
|
||||
foreach (NetworkBehaviour nb in cnob.NetworkObject.NetworkBehaviours)
|
||||
{
|
||||
PooledReader reader = cnob.SyncValuesReader;
|
||||
//SyncVars.
|
||||
int length = reader.ReadInt32();
|
||||
nb.OnSyncType(reader, length, false);
|
||||
//SyncObjects
|
||||
length = reader.ReadInt32();
|
||||
nb.OnSyncType(reader, length, true);
|
||||
}
|
||||
|
||||
/* Only continue with the initialization if it wasn't initialized
|
||||
* early to prevent a despawn conflict. */
|
||||
bool canInitialize = (!_conflictingDespawns.Contains(cnob.ObjectId) || !_iteratedSpawns.Contains(cnob.NetworkObject));
|
||||
if (canInitialize)
|
||||
cnob.NetworkObject.Initialize(false, false);
|
||||
}
|
||||
}
|
||||
//Invoke synctype callbacks.
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
CachedNetworkObject cnob = collection[i];
|
||||
if (cnob.Action == CachedNetworkObject.ActionType.Spawn && cnob.NetworkObject != null)
|
||||
cnob.NetworkObject.InvokeSyncTypeCallbacks(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Once all have been iterated reset.
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an object on clients and spawns the NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="cnob"></param>
|
||||
private void IterateSpawn(CachedNetworkObject cnob)
|
||||
{
|
||||
/* All nob spawns have been added to spawned before
|
||||
* they are processed. This ensures they will be found if
|
||||
* anything is referencing them before/after initialization. */
|
||||
/* However, they have to be added again here should an ItereteDespawn
|
||||
* had removed them. This can occur if an object is set to be spawned,
|
||||
* thus added to spawned before iterations, then a despawn runs which
|
||||
* removes it from spawn. */
|
||||
_clientObjects.AddToSpawned(cnob.NetworkObject, false);
|
||||
_clientObjects.ApplyRpcLinks(cnob.NetworkObject, cnob.RpcLinkReader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deinitializes an object on clients and despawns the NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="cnob"></param>
|
||||
private void IterateDespawn(CachedNetworkObject cnob)
|
||||
{
|
||||
_clientObjects.Despawn(cnob.NetworkObject, cnob.DespawnType, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a NetworkObject found in spawn cache, or Spawned.
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
internal NetworkObject GetSpawnedObject(int objectId)
|
||||
{
|
||||
NetworkObject result;
|
||||
//If not found in Spawning then check Spawned.
|
||||
if (!SpawningObjects.TryGetValue(objectId, out result))
|
||||
{
|
||||
Dictionary<int, NetworkObject> spawned = (_networkManager.IsHost) ?
|
||||
_networkManager.ServerManager.Objects.Spawned
|
||||
: _networkManager.ClientManager.Objects.Spawned;
|
||||
spawned.TryGetValue(objectId, out result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets cache.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_initializeOrderChanged = false;
|
||||
_cachedObjects.Reset();
|
||||
_iteratedSpawns.Clear();
|
||||
SpawningObjects.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cached network object which exist in world but has not been Initialized yet.
|
||||
/// </summary>
|
||||
[Preserve]
|
||||
internal class CachedNetworkObject
|
||||
{
|
||||
#region Types.
|
||||
public enum ActionType
|
||||
{
|
||||
Unset = 0,
|
||||
Spawn = 1,
|
||||
Despawn = 2,
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// True if cached object is nested.
|
||||
/// </summary>
|
||||
public bool IsNested => (ComponentIndex > 0);
|
||||
/// <summary>
|
||||
/// True if a scene object.
|
||||
/// </summary>
|
||||
public bool IsSceneObject => (SceneId > 0);
|
||||
/// <summary>
|
||||
/// True if this object has a parent.
|
||||
/// </summary>
|
||||
public bool HasParent => (ParentObjectId != null);
|
||||
/// <summary>
|
||||
/// True if the parent object is a NetworkBehaviour.
|
||||
/// </summary>
|
||||
public bool ParentIsNetworkBehaviour => (HasParent && (ParentComponentIndex != null));
|
||||
|
||||
public ushort CollectionId;
|
||||
public int ObjectId;
|
||||
public sbyte InitializeOrder;
|
||||
public int OwnerId;
|
||||
public SpawnType SpawnType;
|
||||
public DespawnType DespawnType;
|
||||
public byte ComponentIndex;
|
||||
public int RootObjectId;
|
||||
public int? ParentObjectId;
|
||||
public byte? ParentComponentIndex;
|
||||
public int? PrefabId;
|
||||
public Vector3? LocalPosition;
|
||||
public Quaternion? LocalRotation;
|
||||
public Vector3? LocalScale;
|
||||
public ulong SceneId;
|
||||
public ArraySegment<byte> RpcLinks;
|
||||
public ArraySegment<byte> SyncValues;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// True if spawning.
|
||||
/// </summary>
|
||||
public ActionType Action { get; private set; }
|
||||
/// <summary>
|
||||
/// Cached NetworkObject.
|
||||
/// </summary>
|
||||
#pragma warning disable 0649
|
||||
public NetworkObject NetworkObject;
|
||||
/// <summary>
|
||||
/// Reader containing rpc links for the network object.
|
||||
/// </summary>
|
||||
public PooledReader RpcLinkReader { get; private set; }
|
||||
/// <summary>
|
||||
/// Reader containing sync values for the network object.
|
||||
/// </summary>
|
||||
public PooledReader SyncValuesReader { get; private set; }
|
||||
#pragma warning restore 0649
|
||||
|
||||
public void InitializeSpawn(NetworkManager manager, ushort collectionId, int objectId, sbyte initializeOrder, int ownerId, SpawnType objectSpawnType, byte componentIndex, int rootObjectId, int? parentObjectId, byte? parentComponentIndex
|
||||
, int? prefabId, Vector3? localPosition, Quaternion? localRotation, Vector3? localScale, ulong sceneId, ArraySegment<byte> rpcLinks, ArraySegment<byte> syncValues)
|
||||
{
|
||||
ResetValues();
|
||||
Action = ActionType.Spawn;
|
||||
CollectionId = collectionId;
|
||||
ObjectId = objectId;
|
||||
InitializeOrder = initializeOrder;
|
||||
OwnerId = ownerId;
|
||||
SpawnType = objectSpawnType;
|
||||
ComponentIndex = componentIndex;
|
||||
RootObjectId = rootObjectId;
|
||||
ParentObjectId = parentObjectId;
|
||||
ParentComponentIndex = parentComponentIndex;
|
||||
PrefabId = prefabId;
|
||||
LocalPosition = localPosition;
|
||||
LocalRotation = localRotation;
|
||||
LocalScale = localScale;
|
||||
SceneId = sceneId;
|
||||
RpcLinks = rpcLinks;
|
||||
SyncValues = syncValues;
|
||||
|
||||
RpcLinkReader = ReaderPool.GetReader(rpcLinks, manager);
|
||||
SyncValuesReader = ReaderPool.GetReader(syncValues, manager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes for a despawned NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
public void InitializeDespawn(int objectId, DespawnType despawnType)
|
||||
{
|
||||
ResetValues();
|
||||
Action = ActionType.Despawn;
|
||||
DespawnType = despawnType;
|
||||
ObjectId = objectId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values which could malform identify the cached object.
|
||||
/// </summary>
|
||||
private void ResetValues()
|
||||
{
|
||||
NetworkObject = null;
|
||||
}
|
||||
|
||||
~CachedNetworkObject()
|
||||
{
|
||||
if (RpcLinkReader != null)
|
||||
RpcLinkReader.Dispose();
|
||||
if (SyncValuesReader != null)
|
||||
SyncValuesReader.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54cb2af8ab4557d479acb7fed98bd0c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Debugging.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Debugging.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae208c57eb1f4414896575607893b48d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
20
Assets/FishNet/Runtime/Managing/Debugging/DebugManager.cs
Normal file
20
Assets/FishNet/Runtime/Managing/Debugging/DebugManager.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Debugging
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for debugging.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/DebugManager")]
|
||||
public class DebugManager : MonoBehaviour
|
||||
{
|
||||
public bool ObserverRpcLinks = true;
|
||||
public bool TargetRpcLinks = true;
|
||||
public bool ReplicateRpcLinks = true;
|
||||
public bool ReconcileRpcLinks = true;
|
||||
public bool ServerRpcLinks = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d0962ead4b02a34aae248fccce671ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
73
Assets/FishNet/Runtime/Managing/Debugging/ParseLogger.cs
Normal file
73
Assets/FishNet/Runtime/Managing/Debugging/ParseLogger.cs
Normal file
@ -0,0 +1,73 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Debugging
|
||||
{
|
||||
|
||||
internal class ParseLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the last several non-split packets to arrive. This is used for debugging.
|
||||
/// </summary>
|
||||
private Queue<PacketId> _incomingPacketIds = new Queue<PacketId>();
|
||||
/// <summary>
|
||||
/// Maximum number of packets allowed to be queued.
|
||||
/// </summary>
|
||||
private const int PACKET_COUNT = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Resets data.
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
_incomingPacketIds.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a packet to data.
|
||||
/// </summary>
|
||||
/// <param name="pId"></param>
|
||||
internal void AddPacket(PacketId pId)
|
||||
{
|
||||
_incomingPacketIds.Enqueue(pId);
|
||||
if (_incomingPacketIds.Count > PACKET_COUNT)
|
||||
_incomingPacketIds.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints current data.
|
||||
/// </summary>
|
||||
internal void Print(NetworkManager nm)
|
||||
{
|
||||
if (nm == null)
|
||||
nm = InstanceFinder.NetworkManager;
|
||||
|
||||
//Only log if a NM was found.
|
||||
if (nm != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (PacketId item in _incomingPacketIds)
|
||||
sb.Insert(0, $"{item.ToString()}{Environment.NewLine}");
|
||||
|
||||
NetworkObject lastNob = Reader.LastNetworkObject;
|
||||
string nobData = (lastNob == null) ? "Unset" : $"Id {lastNob.ObjectId} on gameObject {lastNob.name}";
|
||||
NetworkBehaviour lastNb = Reader.LastNetworkBehaviour;
|
||||
string nbData = (lastNb == null) ? "Unset" : lastNb.GetType().Name;
|
||||
|
||||
nm.LogError($"The last {_incomingPacketIds.Count} packets to arrive are: {Environment.NewLine}{sb.ToString()}");
|
||||
nm.LogError($"The last parsed NetworkObject is {nobData}, and NetworkBehaviour {nbData}.");
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afc241e869d97a44f8339510586dce73
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Editor.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30f99c38769dfc548b3b57c6866f6a44
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,71 @@
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Managing.Object;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Editing
|
||||
{
|
||||
[CustomEditor(typeof(NetworkManager))]
|
||||
public class NetworkManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _logging;
|
||||
private SerializedProperty _refreshDefaultPrefabs;
|
||||
private SerializedProperty _runInBackground;
|
||||
private SerializedProperty _dontDestroyOnLoad;
|
||||
private SerializedProperty _persistence;
|
||||
private SerializedProperty _spawnablePrefabs;
|
||||
private SerializedProperty _objectPool;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_logging = serializedObject.FindProperty("_logging");
|
||||
_refreshDefaultPrefabs = serializedObject.FindProperty("_refreshDefaultPrefabs");
|
||||
_runInBackground = serializedObject.FindProperty("_runInBackground");
|
||||
_dontDestroyOnLoad = serializedObject.FindProperty("_dontDestroyOnLoad");
|
||||
_persistence = serializedObject.FindProperty("_persistence");
|
||||
_spawnablePrefabs = serializedObject.FindProperty("_spawnablePrefabs");
|
||||
_objectPool = serializedObject.FindProperty("_objectPool");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
NetworkManager networkManager = (NetworkManager)target;
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(networkManager), typeof(NetworkManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
//EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
//EditorGUILayout.EndVertical();
|
||||
|
||||
|
||||
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_runInBackground);
|
||||
EditorGUILayout.PropertyField(_dontDestroyOnLoad);
|
||||
EditorGUILayout.PropertyField(_persistence);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.LabelField("Logging", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_logging);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.LabelField("Prefabs", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_spawnablePrefabs);
|
||||
EditorGUILayout.PropertyField(_objectPool);
|
||||
EditorGUILayout.PropertyField(_refreshDefaultPrefabs);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e8e16b3e97106a4980b954c56d7bbc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Logging.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Logging.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 756fcf2bc72a3214baf43099a0be9799
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,142 @@
|
||||
using FishNet.Documenting;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Logging
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Configuration ScriptableObject specifying which data to log. Used in conjuction with NetworkManager.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "New LevelLoggingConfiguration", menuName = "FishNet/Logging/Level Logging Configuration")]
|
||||
public class LevelLoggingConfiguration : LoggingConfiguration
|
||||
{
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// Type of logging to use for development builds and editor.
|
||||
/// </summary>
|
||||
[Tooltip("Type of logging to use for development builds and editor.")]
|
||||
[SerializeField]
|
||||
private LoggingType _developmentLogging = LoggingType.Common;
|
||||
/// <summary>
|
||||
/// Type of logging to use for GUI builds.
|
||||
/// </summary>
|
||||
[Tooltip("Type of logging to use for GUI builds.")]
|
||||
[SerializeField]
|
||||
private LoggingType _guiLogging = LoggingType.Warning;
|
||||
/// <summary>
|
||||
/// Type of logging to use for headless builds.
|
||||
/// </summary>
|
||||
[Tooltip("Type of logging to use for headless builds.")]
|
||||
[SerializeField]
|
||||
private LoggingType _headlessLogging = LoggingType.Error;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True when initialized.
|
||||
/// </summary>
|
||||
private bool _initialized;
|
||||
/// <summary>
|
||||
/// Highest type which can be logged.
|
||||
/// </summary>
|
||||
private LoggingType _highestLoggingType = LoggingType.Off;
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public void LoggingConstructor(bool loggingEnabled, LoggingType development, LoggingType gui, LoggingType headless)
|
||||
{
|
||||
base.LoggingEnabled = loggingEnabled;
|
||||
_developmentLogging = development;
|
||||
_guiLogging = gui;
|
||||
_headlessLogging = headless;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes script for use.
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
public override void InitializeOnce()
|
||||
{
|
||||
byte currentHighest = (byte)LoggingType.Off;
|
||||
#if UNITY_SERVER //if headless.
|
||||
currentHighest = Math.Max(currentHighest, (byte)_headlessLogging);
|
||||
#endif
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD //if editor or development.
|
||||
currentHighest = Math.Max(currentHighest, (byte)_developmentLogging);
|
||||
#endif
|
||||
#if !UNITY_EDITOR && !UNITY_SERVER //if a build.
|
||||
currentHighest = Math.Max(currentHighest, (byte)_guiLogging);
|
||||
#endif
|
||||
_highestLoggingType = (LoggingType)currentHighest;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name="loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
public override bool CanLog(LoggingType loggingType)
|
||||
{
|
||||
if (!base.LoggingEnabled)
|
||||
return false;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
if (Application.isPlaying)
|
||||
Debug.LogError("CanLog called before being initialized.");
|
||||
else
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((byte)loggingType <= (byte)_highestLoggingType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a common value if can log.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Log(string value)
|
||||
{
|
||||
if (CanLog(LoggingType.Common))
|
||||
Debug.Log(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning value if can log.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void LogWarning(string value)
|
||||
{
|
||||
if (CanLog(LoggingType.Warning))
|
||||
Debug.LogWarning(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error value if can log.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void LogError(string value)
|
||||
{
|
||||
if (CanLog(LoggingType.Error))
|
||||
Debug.LogError(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones this logging configuration.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override LoggingConfiguration Clone()
|
||||
{
|
||||
LevelLoggingConfiguration copy = ScriptableObject.CreateInstance<LevelLoggingConfiguration>();
|
||||
copy.LoggingConstructor(base.LoggingEnabled, _developmentLogging, _guiLogging, _headlessLogging);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 925fc096350b81f4f82f4fe4ac0c4dda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,57 @@
|
||||
using FishNet.Documenting;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Logging
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Base for logging configurations.
|
||||
/// </summary>
|
||||
public abstract class LoggingConfiguration : ScriptableObject
|
||||
{
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to use logging features. False to disable all logging.
|
||||
/// </summary>
|
||||
[Tooltip("True to use logging features. False to disable all logging.")]
|
||||
public bool LoggingEnabled = true;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes script for use.
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
public virtual void InitializeOnce() { }
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name="loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool CanLog(LoggingType loggingType);
|
||||
|
||||
/// <summary>
|
||||
/// Logs a common value if can log.
|
||||
/// </summary>
|
||||
public abstract void Log(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning value if can log.
|
||||
/// </summary>
|
||||
public abstract void LogWarning(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error value if can log.
|
||||
/// </summary>
|
||||
public abstract void LogError(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Clones this logging configuration.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract LoggingConfiguration Clone();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 438d7e99b7655114891d4fa6e9f68c7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
25
Assets/FishNet/Runtime/Managing/Logging/LoggingType.cs
Normal file
25
Assets/FishNet/Runtime/Managing/Logging/LoggingType.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace FishNet.Managing.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of logging being filtered.
|
||||
/// </summary>
|
||||
public enum LoggingType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable logging.
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
/// <summary>
|
||||
/// Only log errors.
|
||||
/// </summary>
|
||||
Error = 1,
|
||||
/// <summary>
|
||||
/// Log warnings and errors.
|
||||
/// </summary>
|
||||
Warning = 2,
|
||||
/// <summary>
|
||||
/// Log all common activities, warnings, and errors.
|
||||
/// </summary>
|
||||
Common = 3
|
||||
}
|
||||
}
|
11
Assets/FishNet/Runtime/Managing/Logging/LoggingType.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/Logging/LoggingType.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bf0a7ab3f60fe44984fcfd16d8fe7b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
121
Assets/FishNet/Runtime/Managing/NetworkManager.Logging.cs
Normal file
121
Assets/FishNet/Runtime/Managing/NetworkManager.Logging.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// Logging configuration to use. When empty default logging settings will be used.
|
||||
/// </summary>
|
||||
[Tooltip("Logging configuration to use. When empty default logging settings will be used.")]
|
||||
[SerializeField]
|
||||
private LoggingConfiguration _logging;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
private const string ERROR_LOGGING_PREFIX = "Error - ";
|
||||
private const string WARNING_LOGGING_PREFIX = "Warning - ";
|
||||
private const string COMMON_LOGGING_PREFIX = "Log - ";
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes logging settings.
|
||||
/// </summary>
|
||||
private void InitializeLogging()
|
||||
{
|
||||
if (_logging == null)
|
||||
_logging = ScriptableObject.CreateInstance<LevelLoggingConfiguration>();
|
||||
else
|
||||
_logging = _logging.Clone();
|
||||
|
||||
_logging.InitializeOnce();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name="loggingType"></param>
|
||||
/// <returns></returns>
|
||||
[APIExclude]
|
||||
public static bool StaticCanLog(LoggingType loggingType)
|
||||
{
|
||||
NetworkManager nm = InstanceFinder.NetworkManager;
|
||||
return (nm == null) ? false : nm.CanLog(loggingType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name="loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
public bool CanLog(LoggingType loggingType)
|
||||
{
|
||||
return _logging.CanLog(loggingType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Performs a common log, should logging settings permit it.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StaticLog(string value) => InstanceFinder.NetworkManager?.Log(value);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a common log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public void Log(string value)
|
||||
{
|
||||
_logging.Log(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a log using the loggingType, should logging settings permit it.
|
||||
/// </summary>
|
||||
public void Log(LoggingType loggingType, string value)
|
||||
{
|
||||
if (loggingType == LoggingType.Common)
|
||||
_logging.Log(value);
|
||||
else if (loggingType == LoggingType.Warning)
|
||||
_logging.LogWarning(value);
|
||||
else if (loggingType == LoggingType.Error)
|
||||
_logging.LogError(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a warning log, should logging settings permit it.
|
||||
/// </summary>
|
||||
///
|
||||
[APIExclude]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StaticLogWarning(string value) => InstanceFinder.NetworkManager?.LogWarning(value);
|
||||
/// <summary>
|
||||
/// Performs a warning log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public void LogWarning(string value)
|
||||
{
|
||||
_logging.LogWarning(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an error log, should logging settings permit it.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StaticLogError(string value) => InstanceFinder.NetworkManager?.LogError(value);
|
||||
/// <summary>
|
||||
/// Performs an error log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public void LogError(string value)
|
||||
{
|
||||
_logging.LogError(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4200d74f31ee8144fb606ce88ad1b747
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
20
Assets/FishNet/Runtime/Managing/NetworkManager.Pro.cs
Normal file
20
Assets/FishNet/Runtime/Managing/NetworkManager.Pro.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using FishNet.Component.ColliderRollback;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public sealed partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// RollbackManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public RollbackManager RollbackManager { get; private set; }
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Managing/NetworkManager.Pro.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/NetworkManager.Pro.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb7fdb186794b674788273f3b211ec5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
259
Assets/FishNet/Runtime/Managing/NetworkManager.QOL.cs
Normal file
259
Assets/FishNet/Runtime/Managing/NetworkManager.QOL.cs
Normal file
@ -0,0 +1,259 @@
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Object;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityComponent = UnityEngine.Component;
|
||||
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Collection to use for spawnable objects.")]
|
||||
[SerializeField]
|
||||
private PrefabObjects _spawnablePrefabs;
|
||||
/// <summary>
|
||||
/// Collection to use for spawnable objects.
|
||||
/// </summary>
|
||||
public PrefabObjects SpawnablePrefabs { get => _spawnablePrefabs; set => _spawnablePrefabs = value; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private Dictionary<ushort, PrefabObjects> _runtimeSpawnablePrefabs = new Dictionary<ushort, PrefabObjects>();
|
||||
/// <summary>
|
||||
/// Collection to use for spawnable objects added at runtime, such as addressables.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ushort, PrefabObjects> RuntimeSpawnablePrefabs => _runtimeSpawnablePrefabs;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Delegates waiting to be invoked when a component is registered.
|
||||
/// </summary>
|
||||
private Dictionary<string, List<Action<UnityComponent>>> _pendingInvokes = new Dictionary<string, List<Action<UnityComponent>>>();
|
||||
/// <summary>
|
||||
/// Currently registered components.
|
||||
/// </summary>
|
||||
private Dictionary<string, UnityComponent> _registeredComponents = new Dictionary<string, UnityComponent>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PrefabObjects to use for spawnableCollectionId.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of PrefabObjects to return. This is also used to create an instance of type when createIfMissing is true.</typeparam>
|
||||
/// <param name="spawnableCollectionId">Id to use. 0 will return the configured SpawnablePrefabs.</param>
|
||||
/// <param name="createIfMissing">True to create and assign a PrefabObjects if missing for the collectionId.</param>
|
||||
/// <returns></returns>
|
||||
public PrefabObjects GetPrefabObjects<T>(ushort spawnableCollectionId, bool createIfMissing) where T : PrefabObjects
|
||||
{
|
||||
if (spawnableCollectionId == 0)
|
||||
{
|
||||
if (createIfMissing)
|
||||
{
|
||||
LogError($"SpawnableCollectionId cannot be 0 when create missing is true.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SpawnablePrefabs;
|
||||
}
|
||||
}
|
||||
|
||||
PrefabObjects po;
|
||||
if (!_runtimeSpawnablePrefabs.TryGetValue(spawnableCollectionId, out po))
|
||||
{
|
||||
//Do not create missing, return null for not found.
|
||||
if (!createIfMissing)
|
||||
return null;
|
||||
|
||||
po = ScriptableObject.CreateInstance<T>();
|
||||
po.SetCollectionId(spawnableCollectionId);
|
||||
_runtimeSpawnablePrefabs[spawnableCollectionId] = po;
|
||||
}
|
||||
|
||||
return po;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the PrefabObjects collection from memory.
|
||||
/// This should only be called after you properly disposed of it's contents properly.
|
||||
/// </summary>
|
||||
/// <param name="spawnableCollectionId">CollectionId to remove.</param>
|
||||
/// <returns>True if collection was found and removed.</returns>
|
||||
public bool RemoveSpawnableCollection(ushort spawnableCollectionId)
|
||||
{
|
||||
return _runtimeSpawnablePrefabs.Remove(spawnableCollectionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index a prefab uses. Can be used in conjuction with GetPrefab.
|
||||
/// </summary>
|
||||
/// <param name="prefab"></param>
|
||||
/// <param name="asServer">True if to get from the server collection.</param>
|
||||
/// <returns>Returns index if found, and -1 if not found.</returns>
|
||||
public int GetPrefabIndex(GameObject prefab, bool asServer)
|
||||
{
|
||||
int count = SpawnablePrefabs.GetObjectCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GameObject go = SpawnablePrefabs.GetObject(asServer, i).gameObject;
|
||||
if (go == prefab)
|
||||
return i;
|
||||
}
|
||||
|
||||
//Fall through, not found.
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a prefab with prefabId.
|
||||
/// This method will bypass object pooling.
|
||||
/// </summary>
|
||||
/// <param name="prefabId">PrefabId to get.</param>
|
||||
/// <param name="asServer">True if getting the prefab asServer.</param>
|
||||
public NetworkObject GetPrefab(int prefabId, bool asServer)
|
||||
{
|
||||
return SpawnablePrefabs.GetObject(asServer, prefabId);
|
||||
}
|
||||
|
||||
|
||||
#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<UnityComponent> handler) where T : UnityComponent
|
||||
{
|
||||
T result = GetInstance<T>(false);
|
||||
//If not found yet make a pending invoke.
|
||||
if (result == default(T))
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
List<Action<UnityComponent>> handlers;
|
||||
if (!_pendingInvokes.TryGetValue(tName, out handlers))
|
||||
{
|
||||
handlers = new List<Action<UnityComponent>>();
|
||||
_pendingInvokes[tName] = handlers;
|
||||
}
|
||||
|
||||
handlers.Add(handler);
|
||||
}
|
||||
//Already exist, invoke right away.
|
||||
else
|
||||
{
|
||||
handler.Invoke(result);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes an action to be invokes 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<UnityComponent> handler) where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
List<Action<UnityComponent>> handlers;
|
||||
if (!_pendingInvokes.TryGetValue(tName, out handlers))
|
||||
return;
|
||||
|
||||
handlers.Remove(handler);
|
||||
//Do not remove pending to prevent garbage collection later on list recreation.
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns if an instance exists for type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool HasInstance<T>() where T : UnityComponent
|
||||
{
|
||||
return (GetInstance<T>(false) != null);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns class of type if found within CodegenBase classes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="warn">True to warn if component is not registered.</param>
|
||||
/// <returns></returns>
|
||||
public T GetInstance<T>(bool warn = true) where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
if (_registeredComponents.TryGetValue(tName, out UnityComponent result))
|
||||
return (T)result;
|
||||
else if (warn)
|
||||
LogWarning($"Component {tName} is not registered.");
|
||||
|
||||
return default(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 : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
if (_registeredComponents.ContainsKey(tName) && !replace)
|
||||
{
|
||||
LogWarning($"Component {tName} is already registered.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_registeredComponents[tName] = component;
|
||||
RemoveNullPendingDelegates();
|
||||
//If in pending invokes also send these out.
|
||||
if (_pendingInvokes.TryGetValue(tName, out List<Action<UnityComponent>> dels))
|
||||
{
|
||||
for (int i = 0; i < dels.Count; i++)
|
||||
dels[i].Invoke(component);
|
||||
/* Clear delegates but do not remove dictionary entry
|
||||
* to prevent list from being re-initialized. */
|
||||
dels.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Unregisters a component from this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to unregister.</typeparam>
|
||||
public void UnregisterInstance<T>() where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
_registeredComponents.Remove(tName);
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes delegates from pending invokes when may have gone missing.
|
||||
/// </summary>
|
||||
private void RemoveNullPendingDelegates()
|
||||
{
|
||||
foreach (List<Action<UnityComponent>> handlers in _pendingInvokes.Values)
|
||||
{
|
||||
for (int i = 0; i < handlers.Count; i++)
|
||||
{
|
||||
if (handlers[i] == null)
|
||||
{
|
||||
handlers.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the name to use for T.
|
||||
/// </summary>
|
||||
private string GetInstanceName<T>()
|
||||
{
|
||||
return typeof(T).FullName;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Managing/NetworkManager.QOL.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/NetworkManager.QOL.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5761633dda73e7447a3a41b87354d06e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
573
Assets/FishNet/Runtime/Managing/NetworkManager.cs
Normal file
573
Assets/FishNet/Runtime/Managing/NetworkManager.cs
Normal file
@ -0,0 +1,573 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Client;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using UnityEngine;
|
||||
using FishNet.Managing.Scened;
|
||||
using FishNet.Authenticating;
|
||||
using FishNet.Object;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using FishNet.Managing.Observing;
|
||||
using System.Linq;
|
||||
using FishNet.Managing.Debugging;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using FishNet.Managing.Statistic;
|
||||
using FishNet.Utility.Performance;
|
||||
using FishNet.Component.ColliderRollback;
|
||||
using FishNet.Managing.Predicting;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Editing.PrefabCollectionGenerator;
|
||||
#endif
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
/// <summary>
|
||||
/// Acts as a container for all things related to your networking session.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(short.MinValue)]
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/NetworkManager")]
|
||||
public sealed partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Which socket to iterate data on first when as host.
|
||||
/// </summary>
|
||||
public enum HostIterationOrder
|
||||
{
|
||||
ServerFirst,
|
||||
ClientFirst
|
||||
}
|
||||
/// <summary>
|
||||
/// How to persist with multiple NetworkManagers.
|
||||
/// </summary>
|
||||
public enum PersistenceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Destroy any new NetworkManagers.
|
||||
/// </summary>
|
||||
DestroyNewest,
|
||||
/// <summary>
|
||||
/// Destroy previous NetworkManager when a new NetworkManager occurs.
|
||||
/// </summary>
|
||||
DestroyOldest,
|
||||
/// <summary>
|
||||
/// Allow multiple NetworkManagers, do not destroy any automatically.
|
||||
/// </summary>
|
||||
AllowMultiple
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if this instance of the NetworkManager is initialized.
|
||||
/// </summary>
|
||||
public bool Initialized { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private static List<NetworkManager> _instances = new List<NetworkManager>();
|
||||
/// <summary>
|
||||
/// Currently initialized NetworkManagers.
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<NetworkManager> Instances
|
||||
{
|
||||
get
|
||||
{
|
||||
/* Remove null instances of NetworkManager.
|
||||
* This shouldn't happen because instances are removed
|
||||
* OnDestroy but none the less something is causing
|
||||
* it. */
|
||||
for (int i = 0; i < _instances.Count; i++)
|
||||
{
|
||||
if (_instances[i] == null)
|
||||
{
|
||||
_instances.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return _instances;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if server is active.
|
||||
/// </summary>
|
||||
public bool IsServer => ServerManager.Started;
|
||||
/// <summary>
|
||||
/// True if only the server is active.
|
||||
/// </summary>
|
||||
public bool IsServerOnly => (IsServer && !IsClient);
|
||||
/// <summary>
|
||||
/// True if the client is active and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClient => (ClientManager.Started && ClientManager.Connection.Authenticated);
|
||||
/// <summary>
|
||||
/// True if only the client is active and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnly => (!IsServer && IsClient);
|
||||
/// <summary>
|
||||
/// True if client and server are active.
|
||||
/// </summary>
|
||||
public bool IsHost => (IsServer && IsClient);
|
||||
/// <summary>
|
||||
/// True if client nor server are active.
|
||||
/// </summary>
|
||||
public bool IsOffline => (!IsServer && !IsClient);
|
||||
/// <summary>
|
||||
/// PredictionManager for this NetworkManager.
|
||||
/// </summary>
|
||||
internal PredictionManager PredictionManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ServerManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public ServerManager ServerManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ClientManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public ClientManager ClientManager { get; private set; }
|
||||
/// <summary>
|
||||
/// TransportManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public TransportManager TransportManager { get; private set; }
|
||||
/// <summary>
|
||||
/// TimeManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public TimeManager TimeManager { get; private set; }
|
||||
/// <summary>
|
||||
/// SceneManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public SceneManager SceneManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ObserverManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public ObserverManager ObserverManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Authenticator for this NetworkManager. May be null if no Authenticator is used.
|
||||
/// </summary>
|
||||
[Obsolete("Use ServerManager.GetAuthenticator or ServerManager.SetAuthenticator instead.")] //Remove on 2023/06/01
|
||||
public Authenticator Authenticator => ServerManager.Authenticator;
|
||||
/// <summary>
|
||||
/// DebugManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public DebugManager DebugManager { get; private set; }
|
||||
/// <summary>
|
||||
/// StatisticsManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public StatisticsManager StatisticsManager { get; private set; }
|
||||
/// <summary>
|
||||
/// An empty connection reference. Used when a connection cannot be found to prevent object creation.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public static NetworkConnection EmptyConnection { get; private set; } = new NetworkConnection();
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Starting index for RpcLinks.
|
||||
/// </summary>
|
||||
internal static ushort StartingRpcLinkIndex;
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to refresh the DefaultPrefabObjects collection whenever the editor enters play mode. This is an attempt to alleviate the DefaultPrefabObjects scriptable object not refreshing when using multiple editor applications such as ParrelSync.
|
||||
/// </summary>
|
||||
[Tooltip("True to refresh the DefaultPrefabObjects collection whenever the editor enters play mode. This is an attempt to alleviate the DefaultPrefabObjects scriptable object not refreshing when using multiple editor applications such as ParrelSync.")]
|
||||
[SerializeField]
|
||||
private bool _refreshDefaultPrefabs = false;
|
||||
/// <summary>
|
||||
/// True to have your application run while in the background.
|
||||
/// </summary>
|
||||
[Tooltip("True to have your application run while in the background.")]
|
||||
[SerializeField]
|
||||
private bool _runInBackground = true;
|
||||
/// <summary>
|
||||
/// True to make this instance DontDestroyOnLoad. This is typical if you only want one NetworkManager.
|
||||
/// </summary>
|
||||
[Tooltip("True to make this instance DontDestroyOnLoad. This is typical if you only want one NetworkManager.")]
|
||||
[SerializeField]
|
||||
private bool _dontDestroyOnLoad = true;
|
||||
/// <summary>
|
||||
/// Object pool to use for this NetworkManager. Value may be null.
|
||||
/// </summary>
|
||||
[Tooltip("Object pool to use for this NetworkManager. Value may be null.")]
|
||||
[SerializeField]
|
||||
private ObjectPool _objectPool;
|
||||
/// <summary>
|
||||
/// How to persist when other NetworkManagers are introduced.
|
||||
/// </summary>
|
||||
[Tooltip("How to persist when other NetworkManagers are introduced.")]
|
||||
[SerializeField]
|
||||
private PersistenceType _persistence = PersistenceType.DestroyNewest;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if this NetworkManager can persist after Awake checks.
|
||||
/// </summary>
|
||||
private bool _canPersist;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Maximum framerate allowed.
|
||||
/// </summary>
|
||||
internal const ushort MAXIMUM_FRAMERATE = 500;
|
||||
#endregion
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeLogging();
|
||||
if (!ValidateSpawnablePrefabs(true))
|
||||
return;
|
||||
|
||||
if (StartingRpcLinkIndex == 0)
|
||||
StartingRpcLinkIndex = (ushort)(EnumFN.GetHighestValue<PacketId>() + 1);
|
||||
|
||||
bool isDefaultPrefabs = (SpawnablePrefabs != null && SpawnablePrefabs is DefaultPrefabObjects);
|
||||
#if UNITY_EDITOR
|
||||
/* If first instance then force
|
||||
* default prefabs to repopulate.
|
||||
* This is only done in editor because
|
||||
* cloning tools sometimes don't synchronize
|
||||
* scriptable object changes, which is what
|
||||
* the default prefabs is. */
|
||||
if (_refreshDefaultPrefabs && _instances.Count == 0 && isDefaultPrefabs)
|
||||
{
|
||||
Generator.IgnorePostProcess = true;
|
||||
Debug.Log("DefaultPrefabCollection is being refreshed.");
|
||||
Generator.GenerateFull();
|
||||
Generator.IgnorePostProcess = false;
|
||||
}
|
||||
#endif
|
||||
//If default prefabs then also make a new instance and sort them.
|
||||
if (isDefaultPrefabs)
|
||||
{
|
||||
DefaultPrefabObjects originalDpo = (DefaultPrefabObjects)SpawnablePrefabs;
|
||||
//If not editor then a new instance must be made and sorted.
|
||||
DefaultPrefabObjects instancedDpo = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
|
||||
instancedDpo.AddObjects(originalDpo.Prefabs.ToList(), false);
|
||||
instancedDpo.Sort();
|
||||
SpawnablePrefabs = instancedDpo;
|
||||
}
|
||||
|
||||
_canPersist = CanInitialize();
|
||||
if (!_canPersist)
|
||||
return;
|
||||
|
||||
if (TryGetComponent<NetworkObject>(out _))
|
||||
LogError($"NetworkObject component found on the NetworkManager object {gameObject.name}. This is not allowed and will cause problems. Remove the NetworkObject component from this object.");
|
||||
|
||||
SpawnablePrefabs.InitializePrefabRange(0);
|
||||
SpawnablePrefabs.SetCollectionId(0);
|
||||
|
||||
SetDontDestroyOnLoad();
|
||||
SetRunInBackground();
|
||||
DebugManager = GetOrCreateComponent<DebugManager>();
|
||||
TransportManager = GetOrCreateComponent<TransportManager>();
|
||||
|
||||
ServerManager = GetOrCreateComponent<ServerManager>();
|
||||
ClientManager = GetOrCreateComponent<ClientManager>();
|
||||
TimeManager = GetOrCreateComponent<TimeManager>();
|
||||
SceneManager = GetOrCreateComponent<SceneManager>();
|
||||
ObserverManager = GetOrCreateComponent<ObserverManager>();
|
||||
RollbackManager = GetOrCreateComponent<RollbackManager>();
|
||||
PredictionManager = GetOrCreateComponent<PredictionManager>();
|
||||
StatisticsManager = GetOrCreateComponent<StatisticsManager>();
|
||||
if (_objectPool == null)
|
||||
_objectPool = GetOrCreateComponent<DefaultObjectPool>();
|
||||
|
||||
InitializeComponents();
|
||||
|
||||
_instances.Add(this);
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ServerManager.StartForHeadless();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_instances.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes components. To be called after all components are added.
|
||||
/// </summary>
|
||||
private void InitializeComponents()
|
||||
{
|
||||
TimeManager.InitializeOnce_Internal(this);
|
||||
TimeManager.OnLateUpdate += TimeManager_OnLateUpdate;
|
||||
SceneManager.InitializeOnce_Internal(this);
|
||||
TransportManager.InitializeOnce_Internal(this);
|
||||
ClientManager.InitializeOnce_Internal(this);
|
||||
ServerManager.InitializeOnce_Internal(this);
|
||||
ObserverManager.InitializeOnce_Internal(this);
|
||||
RollbackManager.InitializeOnce_Internal(this);
|
||||
PredictionManager.InitializeOnce_Internal(this);
|
||||
StatisticsManager.InitializeOnce_Internal(this);
|
||||
_objectPool.InitializeOnce(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the frame rate based on server and client status.
|
||||
/// </summary>
|
||||
internal void UpdateFramerate()
|
||||
{
|
||||
bool clientStarted = ClientManager.Started;
|
||||
bool serverStarted = ServerManager.Started;
|
||||
|
||||
int frameRate = 0;
|
||||
//If both client and server are started then use whichever framerate is higher.
|
||||
if (clientStarted && serverStarted)
|
||||
frameRate = Math.Max(ServerManager.FrameRate, ClientManager.FrameRate);
|
||||
else if (clientStarted)
|
||||
frameRate = ClientManager.FrameRate;
|
||||
else if (serverStarted)
|
||||
frameRate = ServerManager.FrameRate;
|
||||
|
||||
/* Make sure framerate isn't set to max on server.
|
||||
* If it is then default to tick rate. If framerate is
|
||||
* less than tickrate then also set to tickrate. */
|
||||
#if UNITY_SERVER
|
||||
ushort minimumServerFramerate = (ushort)(TimeManager.TickRate + 1);
|
||||
if (frameRate == MAXIMUM_FRAMERATE)
|
||||
frameRate = minimumServerFramerate;
|
||||
else if (frameRate < TimeManager.TickRate)
|
||||
frameRate = minimumServerFramerate;
|
||||
#endif
|
||||
//If there is a framerate to set.
|
||||
if (frameRate > 0)
|
||||
Application.targetFrameRate = frameRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when MonoBehaviours call LateUpdate.
|
||||
/// </summary>
|
||||
private void TimeManager_OnLateUpdate()
|
||||
{
|
||||
/* Some reason runinbackground becomes unset
|
||||
* or the setting goes ignored some times when it's set
|
||||
* in awake. Rather than try to fix or care why Unity
|
||||
* does this just set it in LateUpdate(or Update). */
|
||||
SetRunInBackground();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this NetworkManager can initialize.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool CanInitialize()
|
||||
{
|
||||
/* If allow multiple then any number of
|
||||
* NetworkManagers are allowed. Don't
|
||||
* automatically destroy any. */
|
||||
if (_persistence == PersistenceType.AllowMultiple)
|
||||
return true;
|
||||
|
||||
List<NetworkManager> instances = Instances.ToList();
|
||||
//This is the first instance, it may initialize.
|
||||
if (instances.Count == 0)
|
||||
return true;
|
||||
|
||||
//First instance of NM.
|
||||
NetworkManager firstInstance = instances[0];
|
||||
|
||||
//If to destroy the newest.
|
||||
if (_persistence == PersistenceType.DestroyNewest)
|
||||
{
|
||||
Log($"NetworkManager on object {gameObject.name} is being destroyed due to persistence type {_persistence}. A NetworkManager instance already exist on {firstInstance.name}.");
|
||||
Destroy(gameObject);
|
||||
//This one is being destroyed because its the newest.
|
||||
return false;
|
||||
}
|
||||
//If to destroy the oldest.
|
||||
else if (_persistence == PersistenceType.DestroyOldest)
|
||||
{
|
||||
Log($"NetworkManager on object {firstInstance.name} is being destroyed due to persistence type {_persistence}. A NetworkManager instance has been created on {gameObject.name}.");
|
||||
Destroy(firstInstance.gameObject);
|
||||
//This being the new one will persist, allow initialization.
|
||||
return true;
|
||||
}
|
||||
//Unhandled.
|
||||
else
|
||||
{
|
||||
Log($"Persistance type of {_persistence} is unhandled on {gameObject.name}. Initialization will not proceed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates SpawnablePrefabs field and returns if validated successfully.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool ValidateSpawnablePrefabs(bool print)
|
||||
{
|
||||
//If null and object is in a scene.
|
||||
if (SpawnablePrefabs == null && !string.IsNullOrEmpty(gameObject.scene.name))
|
||||
{
|
||||
//Always throw an error as this would cause failure.
|
||||
if (print)
|
||||
Debug.LogError($"SpawnablePrefabs is null on {gameObject.name}. Select the NetworkManager in scene {gameObject.scene.name} and choose a prefabs file. Choosing DefaultPrefabObjects will automatically populate prefabs for you.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets DontDestroyOnLoad if configured to.
|
||||
/// </summary>
|
||||
private void SetDontDestroyOnLoad()
|
||||
{
|
||||
if (_dontDestroyOnLoad)
|
||||
DontDestroyOnLoad(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Application.runInBackground to runInBackground.
|
||||
/// </summary>
|
||||
private void SetRunInBackground()
|
||||
{
|
||||
Application.runInBackground = _runInBackground;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a component, creating and adding it if it does not exist.
|
||||
/// </summary>
|
||||
/// <param name="presetValue">Value which may already be set. When not null this is returned instead.</param>
|
||||
private T GetOrCreateComponent<T>(T presetValue = null) where T : UnityEngine.Component
|
||||
{
|
||||
//If already set then return set value.
|
||||
if (presetValue != null)
|
||||
return presetValue;
|
||||
|
||||
if (gameObject.TryGetComponent<T>(out T result))
|
||||
return result;
|
||||
else
|
||||
return gameObject.AddComponent<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a client collection after disposing of the NetworkConnections.
|
||||
/// </summary>
|
||||
/// <param name="clients"></param>
|
||||
internal void ClearClientsCollection(Dictionary<int, NetworkConnection> clients)
|
||||
{
|
||||
foreach (NetworkConnection conn in clients.Values)
|
||||
conn.Dispose();
|
||||
|
||||
clients.Clear();
|
||||
}
|
||||
|
||||
#region Object pool.
|
||||
/// <summary>
|
||||
/// Returns an instantiated copy of prefab.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public NetworkObject GetPooledInstantiated(NetworkObject prefab, bool asServer)
|
||||
{
|
||||
return GetPooledInstantiated(prefab, 0, asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an instantiated copy of prefab.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public NetworkObject GetPooledInstantiated(NetworkObject prefab, ushort collectionId, bool asServer)
|
||||
{
|
||||
return GetPooledInstantiated(prefab.PrefabId, collectionId, asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an instantiated copy of prefab.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public NetworkObject GetPooledInstantiated(GameObject prefab, bool asServer)
|
||||
{
|
||||
return GetPooledInstantiated(prefab, 0, asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an instantiated copy of prefab.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(GameObject prefab, ushort collectionId, bool asServer)
|
||||
{
|
||||
NetworkObject nob;
|
||||
if (!prefab.TryGetComponent<NetworkObject>(out nob))
|
||||
{
|
||||
LogError($"NetworkObject was not found on {prefab}. An instantiated NetworkObject cannot be returned.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetPooledInstantiated(nob.PrefabId, collectionId, asServer);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an instantiated object that has prefabId.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public NetworkObject GetPooledInstantiated(int prefabId, bool asServer)
|
||||
{
|
||||
return GetPooledInstantiated(prefabId, 0, asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns an instantiated object that has prefabId.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(int prefabId, ushort collectionId, bool asServer)
|
||||
{
|
||||
return _objectPool.RetrieveObject(prefabId, collectionId, asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Stores an instantiated object.
|
||||
/// </summary>
|
||||
/// <param name="instantiated">Object which was instantiated.</param>
|
||||
/// <param name="prefabId"></param>
|
||||
/// <param name="asServer">True to store for the server.</param>
|
||||
[Obsolete("Use StorePooledInstantiated(NetworkObject, bool)")] //Remove on 2023/06/01.
|
||||
public void StorePooledInstantiated(NetworkObject instantiated, int prefabId, bool asServer)
|
||||
{
|
||||
StorePooledInstantiated(instantiated, asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Stores an instantied object.
|
||||
/// </summary>
|
||||
/// <param name="instantiated">Object which was instantiated.</param>
|
||||
/// <param name="asServer">True to store for the server.</param>
|
||||
public void StorePooledInstantiated(NetworkObject instantiated, bool asServer)
|
||||
{
|
||||
_objectPool.StoreObject(instantiated, asServer);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Editor.
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (SpawnablePrefabs == null)
|
||||
Reset();
|
||||
}
|
||||
private void Reset()
|
||||
{
|
||||
ValidateSpawnablePrefabs(true);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Managing/NetworkManager.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/NetworkManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c95dfde7d73b54dbbdc23155d35d36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Object.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Object.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba48f298f92e0684db6b68c8d3fc2672
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1
Assets/FishNet/Runtime/Managing/Object/DespawnWriter.cs
Normal file
1
Assets/FishNet/Runtime/Managing/Object/DespawnWriter.cs
Normal file
@ -0,0 +1 @@
|
||||
//Remove on 2023/06/01
|
11
Assets/FishNet/Runtime/Managing/Object/DespawnWriter.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/Object/DespawnWriter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b444a2a7364932340a4a3dede4a434a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
16
Assets/FishNet/Runtime/Managing/Object/DualPrefab.cs
Normal file
16
Assets/FishNet/Runtime/Managing/Object/DualPrefab.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using FishNet.Object;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// When using dual prefabs, defines which prefab to spawn for server, and which for clients.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct DualPrefab
|
||||
{
|
||||
public NetworkObject Server;
|
||||
public NetworkObject Client;
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Managing/Object/DualPrefab.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/Object/DualPrefab.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76840b2b810d8fc45aeccef03122763c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,380 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public abstract partial class ManagedObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads and outputs a transforms values.
|
||||
/// </summary>
|
||||
protected void ReadTransformProperties(Reader reader, out Vector3? localPosition, out Quaternion? localRotation, out Vector3? localScale)
|
||||
{
|
||||
//Read changed.
|
||||
ChangedTransformProperties ctp = (ChangedTransformProperties)reader.ReadByte();
|
||||
//Position.
|
||||
if (ChangedTransformPropertiesEnum.Contains(ctp, ChangedTransformProperties.LocalPosition))
|
||||
localPosition = reader.ReadVector3();
|
||||
else
|
||||
localPosition = null;
|
||||
//Rotation.
|
||||
if (ChangedTransformPropertiesEnum.Contains(ctp, ChangedTransformProperties.LocalRotation))
|
||||
localRotation = reader.ReadQuaternion(NetworkManager.ServerManager.SpawnPacking.Rotation);
|
||||
else
|
||||
localRotation = null;
|
||||
//Scale.
|
||||
if (ChangedTransformPropertiesEnum.Contains(ctp, ChangedTransformProperties.LocalScale))
|
||||
localScale = reader.ReadVector3();
|
||||
else
|
||||
localScale = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a spawn to clients.
|
||||
/// </summary>
|
||||
internal void WriteSpawn_Server(NetworkObject nob, NetworkConnection connection, Writer everyoneWriter, Writer ownerWriter)
|
||||
{
|
||||
/* Using a number of writers to prevent rebuilding the
|
||||
* packets excessively for values that are owner only
|
||||
* vs values that are everyone. To save performance the
|
||||
* owner writer is only written to if owner is valid.
|
||||
* This makes the code a little uglier but will scale
|
||||
* significantly better with more connections.
|
||||
*
|
||||
* EG:
|
||||
* with this technique networkBehaviours are iterated
|
||||
* twice if there is an owner; once for data to send to everyone
|
||||
* and again for data only going to owner.
|
||||
*
|
||||
* The alternative would be to iterate the networkbehaviours
|
||||
* for every connection it's going to and filling a single
|
||||
* writer with values based on if owner or not. This would
|
||||
* result in significantly more iterations. */
|
||||
PooledWriter headerWriter = WriterPool.GetWriter();
|
||||
headerWriter.WritePacketId(PacketId.ObjectSpawn);
|
||||
headerWriter.WriteNetworkObjectForSpawn(nob);
|
||||
if (NetworkManager.ServerManager.ShareIds || connection == nob.Owner)
|
||||
headerWriter.WriteNetworkConnection(nob.Owner);
|
||||
else
|
||||
headerWriter.WriteInt16(-1);
|
||||
|
||||
bool nested = (nob.IsNested && nob.ParentNetworkObject != null);
|
||||
bool sceneObject = nob.IsSceneObject;
|
||||
//Write type of spawn.
|
||||
SpawnType st = SpawnType.Unset;
|
||||
if (sceneObject)
|
||||
st |= SpawnType.Scene;
|
||||
else
|
||||
st |= (nob.IsGlobal) ? SpawnType.InstantiatedGlobal : SpawnType.Instantiated;
|
||||
//Add on nested if needed.
|
||||
if (nested)
|
||||
st |= SpawnType.Nested;
|
||||
|
||||
headerWriter.WriteByte((byte)st);
|
||||
//ComponentIndex for the nob. 0 is root but more appropriately there's a IsNested boolean as shown above.
|
||||
headerWriter.WriteByte(nob.ComponentIndex);
|
||||
//Properties on the transform which diff from serialized value.
|
||||
WriteChangedTransformProperties(nob, sceneObject, nested, headerWriter);
|
||||
|
||||
/* When nested the parent nob needs to be written. */
|
||||
if (nested)
|
||||
headerWriter.WriteNetworkObjectId(nob.ParentNetworkObject);
|
||||
|
||||
/* Writing a scene object. */
|
||||
if (sceneObject)
|
||||
{
|
||||
headerWriter.WriteUInt64(nob.SceneId, AutoPackType.Unpacked);
|
||||
}
|
||||
/* Writing a spawned object. */
|
||||
else
|
||||
{
|
||||
//Check to write parent behaviour or nob.
|
||||
NetworkBehaviour parentNb;
|
||||
Transform t = nob.transform.parent;
|
||||
if (t != null)
|
||||
{
|
||||
parentNb = t.GetComponent<NetworkBehaviour>();
|
||||
/* Check for a NetworkObject if there is no NetworkBehaviour.
|
||||
* There is a small chance the parent object will only contain
|
||||
* a NetworkObject. */
|
||||
if (parentNb == null)
|
||||
{
|
||||
//If null check if there is a nob.
|
||||
NetworkObject parentNob = t.GetComponent<NetworkObject>();
|
||||
//ParentNob is null or not spawned.
|
||||
if (!ParentIsSpawned(parentNob))
|
||||
{
|
||||
headerWriter.WriteByte((byte)SpawnParentType.Unset);
|
||||
}
|
||||
else
|
||||
{
|
||||
headerWriter.WriteByte((byte)SpawnParentType.NetworkObject);
|
||||
headerWriter.WriteNetworkObjectId(parentNob);
|
||||
}
|
||||
}
|
||||
//NetworkBehaviour found on parent.
|
||||
else
|
||||
{
|
||||
//ParentNb is null or not spawned.
|
||||
if (!ParentIsSpawned(parentNb.NetworkObject))
|
||||
{
|
||||
headerWriter.WriteByte((byte)SpawnParentType.Unset);
|
||||
}
|
||||
else
|
||||
{
|
||||
headerWriter.WriteByte((byte)SpawnParentType.NetworkBehaviour);
|
||||
headerWriter.WriteNetworkBehaviour(parentNb);
|
||||
}
|
||||
}
|
||||
|
||||
//True if pNob is not null, and is spawned.
|
||||
bool ParentIsSpawned(NetworkObject pNob)
|
||||
{
|
||||
bool isNull = (pNob == null);
|
||||
if (isNull || !pNob.IsSpawned)
|
||||
{
|
||||
/* Only log if pNob exist. Otherwise this would print if the user
|
||||
* was parenting any object, which may not be desirable as they could be
|
||||
* simply doing it for organization reasons. */
|
||||
if (!isNull)
|
||||
NetworkManager.LogWarning($"Parent {t.name} is not spawned. {nob.name} will not have it's parent sent in the spawn message.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
//No parent.
|
||||
else
|
||||
{
|
||||
headerWriter.WriteByte((byte)SpawnParentType.Unset);
|
||||
}
|
||||
|
||||
headerWriter.WriteNetworkObjectId(nob.PrefabId);
|
||||
}
|
||||
|
||||
//Write headers first.
|
||||
everyoneWriter.WriteBytes(headerWriter.GetBuffer(), 0, headerWriter.Length);
|
||||
if (nob.Owner.IsValid)
|
||||
ownerWriter.WriteBytes(headerWriter.GetBuffer(), 0, headerWriter.Length);
|
||||
|
||||
/* Used to write latest data which must be sent to
|
||||
* clients, such as SyncTypes and RpcLinks. */
|
||||
PooledWriter tempWriter = WriterPool.GetWriter();
|
||||
//Send RpcLinks first.
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteRpcLinks(tempWriter);
|
||||
//Add to everyone/owner.
|
||||
everyoneWriter.WriteBytesAndSize(tempWriter.GetBuffer(), 0, tempWriter.Length);
|
||||
if (nob.Owner.IsValid)
|
||||
ownerWriter.WriteBytesAndSize(tempWriter.GetBuffer(), 0, tempWriter.Length);
|
||||
|
||||
//Add most recent sync type values.
|
||||
/* SyncTypes have to be populated for owner and everyone.
|
||||
* The data may be unique for owner if synctypes are set
|
||||
* to only go to owner. */
|
||||
WriteSyncTypes(everyoneWriter, tempWriter, SyncTypeWriteType.Observers);
|
||||
//If owner is valid then populate owner writer as well.
|
||||
if (nob.Owner.IsValid)
|
||||
WriteSyncTypes(ownerWriter, tempWriter, SyncTypeWriteType.Owner);
|
||||
|
||||
void WriteSyncTypes(Writer finalWriter, PooledWriter tWriter, SyncTypeWriteType writeType)
|
||||
{
|
||||
tWriter.Reset();
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteSyncTypesForSpawn(tWriter, writeType);
|
||||
finalWriter.WriteBytesAndSize(tWriter.GetBuffer(), 0, tWriter.Length);
|
||||
}
|
||||
|
||||
//Dispose of writers created in this method.
|
||||
headerWriter.Dispose();
|
||||
tempWriter.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes changed transform proeprties to writer.
|
||||
/// </summary>
|
||||
protected void WriteChangedTransformProperties(NetworkObject nob, bool sceneObject, bool nested, Writer headerWriter)
|
||||
{
|
||||
/* Write changed transform properties. */
|
||||
ChangedTransformProperties ctp;
|
||||
//If a scene object then get it from scene properties.
|
||||
if (sceneObject || nested)
|
||||
{
|
||||
ctp = nob.GetTransformChanges(nob.SerializedTransformProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrefabObjects po = NetworkManager.GetPrefabObjects<PrefabObjects>(nob.SpawnableCollectionId, false);
|
||||
ctp = nob.GetTransformChanges(po.GetObject(true, nob.PrefabId).gameObject);
|
||||
}
|
||||
|
||||
headerWriter.WriteByte((byte)ctp);
|
||||
//If properties have changed.
|
||||
if (ctp != ChangedTransformProperties.Unset)
|
||||
{
|
||||
//Write any changed properties.
|
||||
if (ChangedTransformPropertiesEnum.Contains(ctp, ChangedTransformProperties.LocalPosition))
|
||||
headerWriter.WriteVector3(nob.transform.localPosition);
|
||||
if (ChangedTransformPropertiesEnum.Contains(ctp, ChangedTransformProperties.LocalRotation))
|
||||
headerWriter.WriteQuaternion(nob.transform.localRotation, NetworkManager.ServerManager.SpawnPacking.Rotation);
|
||||
if (ChangedTransformPropertiesEnum.Contains(ctp, ChangedTransformProperties.LocalScale))
|
||||
headerWriter.WriteVector3(nob.transform.localScale);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a despawn.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
protected void WriteDespawn(NetworkObject nob, DespawnType despawnType, Writer everyoneWriter)
|
||||
{
|
||||
everyoneWriter.WritePacketId(PacketId.ObjectDespawn);
|
||||
everyoneWriter.WriteNetworkObjectForDespawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets transform properties by applying passed in values if they are not null, otherwise using transforms defaults.
|
||||
/// </summary>
|
||||
internal void GetTransformProperties(Vector3? readPos, Quaternion? readRot, Vector3? readScale, Transform defaultTransform, out Vector3 pos, out Quaternion rot, out Vector3 scale)
|
||||
{
|
||||
pos = (readPos == null) ? defaultTransform.localPosition : readPos.Value;
|
||||
rot = (readRot == null) ? defaultTransform.localRotation : readRot.Value;
|
||||
scale = (readScale == null) ? defaultTransform.localScale : readScale.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a scene NetworkObject and sets transform values.
|
||||
/// </summary>
|
||||
internal NetworkObject GetSceneNetworkObject(ulong sceneId)
|
||||
{
|
||||
NetworkObject nob;
|
||||
SceneObjects.TryGetValueIL2CPP(sceneId, out nob);
|
||||
//If found in scene objects.
|
||||
if (nob == null)
|
||||
NetworkManager.LogError($"SceneId of {sceneId} not found in SceneObjects. This may occur if your scene differs between client and server, if client does not have the scene loaded, or if networked scene objects do not have a SceneCondition. See ObserverManager in the documentation for more on conditions.");
|
||||
|
||||
return nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject meets basic criteria for being predicted spawned.
|
||||
/// </summary>
|
||||
/// <param name="reader">If not null reader will be cleared on error.</param>
|
||||
/// <returns></returns>
|
||||
protected bool CanPredictedSpawn(NetworkObject nob, NetworkConnection spawner, NetworkConnection owner, bool asServer, Reader reader = null)
|
||||
{
|
||||
//Does not allow predicted spawning.
|
||||
if (!nob.AllowPredictedSpawning)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object {nob.name} which does not support predicted spawning.");
|
||||
else
|
||||
NetworkManager.LogError($"Object {nob.name} does not support predicted spawning. Modify the NetworkObject component settings to allow predicted spawning.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
//Parenting is not yet supported.
|
||||
if (nob.transform.parent != null)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object that is not root.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted spawning as a child is not supported.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
//Nested nobs not yet supported.
|
||||
if (nob.ChildNetworkObjects.Count > 0)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object {nob.name} which has nested NetworkObjects.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted spawning prefabs which contain nested NetworkObjects is not yet supported but will be in a later release.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
//Blocked by PredictedSpawn settings or user logic.
|
||||
if ((asServer && !nob.PredictedSpawn.OnTrySpawnServer(spawner, owner))
|
||||
|| (!asServer && !nob.PredictedSpawn.OnTrySpawnClient()))
|
||||
{
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject meets basic criteria for being predicted despawned.
|
||||
/// </summary>
|
||||
/// <param name="reader">If not null reader will be cleared on error.</param>
|
||||
/// <returns></returns>
|
||||
protected bool CanPredictedDespawn(NetworkObject nob, NetworkConnection despawner, bool asServer, Reader reader = null)
|
||||
{
|
||||
//Does not allow predicted spawning.
|
||||
if (!nob.AllowPredictedDespawning)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object {nob.name} which does not support predicted despawning.");
|
||||
else
|
||||
NetworkManager.LogError($"Object {nob.name} does not support predicted despawning. Modify the PredictedSpawn component settings to allow predicted despawning.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
//Parenting is not yet supported.
|
||||
if (nob.transform.parent != null)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object that is not root.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted despawning as a child is not supported.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
//Nested nobs not yet supported.
|
||||
if (nob.ChildNetworkObjects.Count > 0)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object {nob.name} which has nested NetworkObjects.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted despawning prefabs which contain nested NetworkObjects is not yet supported but will be in a later release.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
//Blocked by PredictedSpawn settings or user logic.
|
||||
if (
|
||||
(asServer && !nob.PredictedSpawn.OnTryDepawnServer(despawner))
|
||||
|| (!asServer && !nob.PredictedSpawn.OnTryDespawnClient())
|
||||
)
|
||||
{
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fcb759226359ad48926ff17cbf0ec6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
414
Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs
Normal file
414
Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs
Normal file
@ -0,0 +1,414 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public abstract partial class ManagedObjects
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// NetworkObjects which are currently active.
|
||||
/// </summary>
|
||||
public Dictionary<int, NetworkObject> Spawned = new Dictionary<int, NetworkObject>();
|
||||
/// <summary>
|
||||
/// NetworkObjects which are currently active on the local client.
|
||||
/// //TODO Move this to ClientObjects.
|
||||
/// </summary>
|
||||
internal List<NetworkObject> LocalClientSpawned = new List<NetworkObject>();
|
||||
#endregion
|
||||
|
||||
#region Protected.
|
||||
/// <summary>
|
||||
/// Returns the next ObjectId to use.
|
||||
/// </summary>
|
||||
protected internal virtual int GetNextNetworkObjectId(bool errorCheck = true) => NetworkObject.UNSET_OBJECTID_VALUE;
|
||||
/// <summary>
|
||||
/// NetworkManager handling this.
|
||||
/// </summary>
|
||||
protected NetworkManager NetworkManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
||||
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
||||
/// </summary>
|
||||
protected Dictionary<ulong, NetworkObject> SceneObjects = new Dictionary<ulong, NetworkObject>();
|
||||
#endregion
|
||||
|
||||
protected void Initialize(NetworkManager manager)
|
||||
{
|
||||
NetworkManager = manager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to SceneManager.SceneLoaded event.
|
||||
/// </summary>
|
||||
/// <param name="subscribe"></param>
|
||||
internal void SubscribeToSceneLoaded(bool subscribe)
|
||||
{
|
||||
if (subscribe)
|
||||
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
|
||||
else
|
||||
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a scene is loaded.
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="arg1"></param>
|
||||
protected internal virtual void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when a NetworkObject runs Deactivate.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
internal virtual void NetworkObjectUnexpectedlyDestroyed(NetworkObject nob, bool asServer)
|
||||
{
|
||||
if (nob == null)
|
||||
return;
|
||||
|
||||
RemoveFromSpawned(nob, true, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkedObject from spawned.
|
||||
/// </summary>
|
||||
private void RemoveFromSpawned(NetworkObject nob, bool unexpectedlyDestroyed, bool asServer)
|
||||
{
|
||||
Spawned.Remove(nob.ObjectId);
|
||||
if (!asServer)
|
||||
LocalClientSpawned.Remove(nob);
|
||||
//Do the same with SceneObjects.
|
||||
if (unexpectedlyDestroyed && nob.IsSceneObject)
|
||||
RemoveFromSceneObjects(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a NetworkObject.
|
||||
/// </summary>
|
||||
internal virtual void Despawn(NetworkObject nob, DespawnType despawnType, bool asServer)
|
||||
{
|
||||
if (nob == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot despawn a null NetworkObject.");
|
||||
return;
|
||||
}
|
||||
|
||||
//True if should be destroyed, false if deactivated.
|
||||
bool destroy = false;
|
||||
/* Only modify object state if asServer,
|
||||
* or !asServer and not host. This is so clients, when acting as
|
||||
* host, don't destroy objects they lost observation of. */
|
||||
|
||||
/* Nested prefabs can never be destroyed. Only check to
|
||||
* destroy if not nested. By nested prefab, this means the object
|
||||
* despawning is part of another prefab that is also a spawned
|
||||
* network object. */
|
||||
if (!nob.IsNested)
|
||||
{
|
||||
//If as server.
|
||||
if (asServer)
|
||||
{
|
||||
//Scene object.
|
||||
if (!nob.IsSceneObject)
|
||||
{
|
||||
/* If client-host has visibility
|
||||
* then disable and wait for client-host to get destroy
|
||||
* message. Otherwise destroy immediately. */
|
||||
if (nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
||||
NetworkManager.ServerManager.Objects.AddToPending(nob);
|
||||
else
|
||||
destroy = true;
|
||||
}
|
||||
}
|
||||
//Not as server.
|
||||
else
|
||||
{
|
||||
bool isServer = NetworkManager.IsServer;
|
||||
//Only check to destroy if not a scene object.
|
||||
if (!nob.IsSceneObject)
|
||||
{
|
||||
/* If was removed from pending then also destroy.
|
||||
* Pending objects are ones that exist on the server
|
||||
* side only to await destruction from client side.
|
||||
* Objects can also be destroyed if server is not
|
||||
* active. */
|
||||
destroy = (!isServer || NetworkManager.ServerManager.Objects.RemoveFromPending(nob.ObjectId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Deinitialize to invoke callbacks.
|
||||
nob.Deinitialize(asServer);
|
||||
//Remove from match condition only if server.
|
||||
if (asServer)
|
||||
MatchCondition.RemoveFromMatchWithoutRebuild(nob, NetworkManager);
|
||||
RemoveFromSpawned(nob, false, asServer);
|
||||
|
||||
//If to destroy.
|
||||
if (destroy)
|
||||
{
|
||||
if (despawnType == DespawnType.Destroy)
|
||||
MonoBehaviour.Destroy(nob.gameObject);
|
||||
else
|
||||
NetworkManager.StorePooledInstantiated(nob, asServer);
|
||||
}
|
||||
/* If to potentially disable instead of destroy.
|
||||
* This is such as something is despawning server side
|
||||
* but a clientHost is present, or if a scene object. */
|
||||
else
|
||||
{
|
||||
//If as server.
|
||||
if (asServer)
|
||||
{
|
||||
//If not clientHost then the object can be disabled.
|
||||
if (!NetworkManager.IsClient)
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
//Not as server.
|
||||
else
|
||||
{
|
||||
//If the server is not active then the object can be disabled.
|
||||
if (!NetworkManager.IsServer)
|
||||
{
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
//If also server then checks must be done.
|
||||
else
|
||||
{
|
||||
/* Object is still spawned on the server side. This means
|
||||
* the clientHost likely lost visibility. When this is the case
|
||||
* update clientHost renderers. */
|
||||
if (NetworkManager.ServerManager.Objects.Spawned.ContainsKey(nob.ObjectId))
|
||||
nob.SetRenderersVisible(false);
|
||||
/* No longer spawned on the server, can
|
||||
* deactivate on the client. */
|
||||
else
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Also despawn child objects.
|
||||
* This only must be done when not destroying
|
||||
* as destroying would result in the despawn being
|
||||
* forced.
|
||||
*
|
||||
* Only run if asServer as well. The server will send
|
||||
* individual despawns for each child. */
|
||||
if (asServer)
|
||||
{
|
||||
foreach (NetworkObject childNob in nob.ChildNetworkObjects)
|
||||
{
|
||||
if (childNob != null && !childNob.IsDeinitializing)
|
||||
Despawn(childNob, despawnType, asServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates NetworkBehaviours on nob.
|
||||
/// </summary>
|
||||
/// <param name="asServer"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void UpdateNetworkBehavioursForSceneObject(NetworkObject nob, bool asServer)
|
||||
{
|
||||
//Would have already been done on server side.
|
||||
if (!asServer && NetworkManager.IsServer)
|
||||
return;
|
||||
|
||||
InitializePrefab(nob, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a prefab, not to be mistaken for initializing a spawned object.
|
||||
/// </summary>
|
||||
/// <param name="prefab">Prefab to initialize.</param>
|
||||
/// <param name="index">Index within spawnable prefabs.</param>
|
||||
public static void InitializePrefab(NetworkObject prefab, int index, ushort? collectionId = null)
|
||||
{
|
||||
if (prefab == null)
|
||||
return;
|
||||
/* Only set the Id if not -1.
|
||||
* A value of -1 would indicate it's a scene
|
||||
* object. */
|
||||
if (index != -1)
|
||||
{
|
||||
//Use +1 because 0 indicates unset.
|
||||
prefab.PrefabId = (ushort)index;
|
||||
if (collectionId != null)
|
||||
prefab.SpawnableCollectionId = collectionId.Value;
|
||||
}
|
||||
|
||||
byte componentIndex = 0;
|
||||
prefab.UpdateNetworkBehaviours(null, ref componentIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns Spawned NetworkObjects. Scene objects will be disabled, others will be destroyed.
|
||||
/// </summary>
|
||||
internal virtual void DespawnWithoutSynchronization(bool asServer)
|
||||
{
|
||||
foreach (NetworkObject nob in Spawned.Values)
|
||||
DespawnWithoutSynchronization(nob, asServer, nob.GetDefaultDespawnType(), false);
|
||||
|
||||
Spawned.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a network object.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
internal virtual void DespawnWithoutSynchronization(NetworkObject nob, bool asServer, DespawnType despawnType, bool removeFromSpawned)
|
||||
{
|
||||
//Null can occur when running as host and server already despawns such as when stopping.
|
||||
if (nob == null)
|
||||
return;
|
||||
|
||||
nob.Deinitialize(asServer);
|
||||
/* Only run if asServer, or not
|
||||
* asServer and server isn't running. This
|
||||
* prevents objects from affecting the server
|
||||
* as host when being modified client side. */
|
||||
if (asServer || (!asServer && !NetworkManager.IsServer))
|
||||
{
|
||||
if (removeFromSpawned)
|
||||
RemoveFromSpawned(nob, false, asServer);
|
||||
if (nob.IsSceneObject)
|
||||
{
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (despawnType == DespawnType.Destroy)
|
||||
MonoBehaviour.Destroy(nob.gameObject);
|
||||
else
|
||||
NetworkManager.StorePooledInstantiated(nob, asServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkObject to Spawned.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
internal void AddToSpawned(NetworkObject nob, bool asServer)
|
||||
{
|
||||
Spawned[nob.ObjectId] = nob;
|
||||
if (!asServer)
|
||||
{
|
||||
LocalClientSpawned.Add(nob);
|
||||
//If being added as client and is also server.
|
||||
if (NetworkManager.IsServer)
|
||||
nob.SetRenderersVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkObject to SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
protected internal void AddToSceneObjects(NetworkObject nob)
|
||||
{
|
||||
SceneObjects[nob.SceneId] = nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkObject from SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
protected internal void RemoveFromSceneObjects(NetworkObject nob)
|
||||
{
|
||||
SceneObjects.Remove(nob.SceneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkObject from SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name="nob"></param>
|
||||
protected internal void RemoveFromSceneObjects(ulong sceneId)
|
||||
{
|
||||
SceneObjects.Remove(sceneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a NetworkObject within Spawned.
|
||||
/// </summary>
|
||||
/// <param name="objectId"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal NetworkObject GetSpawnedNetworkObject(int objectId)
|
||||
{
|
||||
NetworkObject r;
|
||||
if (!Spawned.TryGetValueIL2CPP(objectId, out r))
|
||||
NetworkManager.LogError($"Spawned NetworkObject not found for ObjectId {objectId}.");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to skip data length for a packet.
|
||||
/// </summary>
|
||||
/// <param name="packetId"></param>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="dataLength"></param>
|
||||
protected internal void SkipDataLength(ushort packetId, PooledReader reader, int dataLength, int rpcLinkObjectId = -1)
|
||||
{
|
||||
/* -1 means length wasn't set, which would suggest a reliable packet.
|
||||
* Object should never be missing for reliable packets since spawns
|
||||
* and despawns are reliable in order. */
|
||||
if (dataLength == (int)MissingObjectPacketLength.Reliable)
|
||||
{
|
||||
string msg;
|
||||
bool isRpcLink = (packetId >= NetworkManager.StartingRpcLinkIndex);
|
||||
if (isRpcLink)
|
||||
{
|
||||
msg = (rpcLinkObjectId == -1) ?
|
||||
$"RPCLink of Id {(PacketId)packetId} could not be found. Remaining data will be purged." :
|
||||
$"ObjectId {rpcLinkObjectId} for RPCLink {(PacketId)packetId} could not be found.";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"NetworkBehaviour could not be found for packetId {(PacketId)packetId}. Remaining data will be purged.";
|
||||
}
|
||||
|
||||
/* Default logging for server is errors only. Use error on client and warning
|
||||
* on servers to reduce chances of allocation attacks. */
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR || !UNITY_SERVER
|
||||
NetworkManager.LogError(msg);
|
||||
#else
|
||||
if (NetworkManager.CanLog(LoggingType.Warning))
|
||||
Debug.LogWarning(msg);
|
||||
#endif
|
||||
reader.Clear();
|
||||
}
|
||||
/* If length is known then is unreliable packet. It's possible
|
||||
* this packetId arrived before or after the object was spawned/destroyed.
|
||||
* Skip past the data for this packet and use rest in reader. With non-linked
|
||||
* RPCs length is sent before object information. */
|
||||
else if (dataLength >= 0)
|
||||
{
|
||||
reader.Skip(Math.Min(dataLength, reader.Remaining));
|
||||
}
|
||||
/* -2 indicates the length is very long. Don't even try saving
|
||||
* the packet, user shouldn't be sending this much data over unreliable. */
|
||||
else if (dataLength == (int)MissingObjectPacketLength.PurgeRemaiming)
|
||||
{
|
||||
reader.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1363007244792145846afddc31ac12c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
31
Assets/FishNet/Runtime/Managing/Object/ObjectSpawnType.cs
Normal file
31
Assets/FishNet/Runtime/Managing/Object/ObjectSpawnType.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
internal enum SpawnType : byte
|
||||
{
|
||||
Unset = 0,
|
||||
Nested = 1,
|
||||
Scene = 2,
|
||||
Instantiated = 4,
|
||||
InstantiatedGlobal = 8,
|
||||
}
|
||||
|
||||
[APIExclude]
|
||||
internal static partial class SpawnTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if whole contains part.
|
||||
/// </summary>
|
||||
/// <param name="whole"></param>
|
||||
/// <param name="part"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Contains(SpawnType whole, SpawnType part)
|
||||
{
|
||||
return (whole & part) == part;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed15edf5a1a100d45b05f6adace574cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 681483aaaf105014b93c3c89c7f43fda
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,104 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object.Helping;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Editing;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using FishNet.Object;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
|
||||
[APIExclude]
|
||||
//[CreateAssetMenu(fileName = "New DefaultPrefabObjects", menuName = "FishNet/Spawnable Prefabs/Default Prefab Objects")]
|
||||
public class DefaultPrefabObjects : SinglePrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets asset path hashes for prefabs starting at index, or if missing.
|
||||
/// </summary
|
||||
/// <return>Returns true if one or more NetworkObjects were updated.</return>
|
||||
internal bool SetAssetPathHashes(int index)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
bool dirtied = false;
|
||||
int count = base.GetObjectCount();
|
||||
|
||||
if (count == 0)
|
||||
return false;
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
Debug.LogError($"Index {index} is out of range when trying to set asset path hashes. Collection length is {count}. Defaulf prefabs may need to be rebuilt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkObject n = base.Prefabs[i];
|
||||
if (i < index)
|
||||
continue;
|
||||
|
||||
string pathAndName = $"{AssetDatabase.GetAssetPath(n.gameObject)}{n.gameObject.name}";
|
||||
ulong hashcode = Hashing.GetStableHash64(pathAndName);
|
||||
//Already set.
|
||||
if (n.AssetPathHash == hashcode)
|
||||
continue;
|
||||
|
||||
n.SetAssetPathHash(hashcode);
|
||||
EditorUtility.SetDirty(n);
|
||||
dirtied = true;
|
||||
}
|
||||
|
||||
return dirtied;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts prefabs by name and path hashcode.
|
||||
/// </summary>
|
||||
internal void Sort()
|
||||
{
|
||||
if (base.GetObjectCount() == 0)
|
||||
return;
|
||||
|
||||
Dictionary<ulong, NetworkObject> hashcodesAndNobs = new Dictionary<ulong, NetworkObject>();
|
||||
List<ulong> hashcodes = new List<ulong>();
|
||||
|
||||
bool error = false;
|
||||
foreach (NetworkObject n in base.Prefabs)
|
||||
{
|
||||
hashcodes.Add(n.AssetPathHash);
|
||||
//If hashcode is 0 something is wrong
|
||||
if (n.AssetPathHash == 0)
|
||||
{
|
||||
error = true;
|
||||
Debug.LogError($"AssetPathHash is not set for GameObject {n.name}.");
|
||||
|
||||
}
|
||||
hashcodesAndNobs.Add(n.AssetPathHash, n);
|
||||
}
|
||||
//An error occured, no reason to continue.
|
||||
if (error)
|
||||
{
|
||||
Debug.LogError($"One or more NetworkObject prefabs did not have their AssetPathHash set. This usually occurs when a prefab cannot be saved. Check the specified prefabs for missing scripts or serialization errors and correct them, then use Fish-Networking -> Refresh Default Prefabs.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Once all hashes have been made re-add them to prefabs sorted.
|
||||
hashcodes.Sort();
|
||||
//Build to a new list using sorted hashcodes.
|
||||
List<NetworkObject> sortedNobs = new List<NetworkObject>();
|
||||
foreach (ulong hc in hashcodes)
|
||||
sortedNobs.Add(hashcodesAndNobs[hc]);
|
||||
|
||||
base.Clear();
|
||||
base.AddObjects(sortedNobs, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ad70174b079c2f4ebc7931d3dd1af6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,136 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
|
||||
//document
|
||||
[APIExclude]
|
||||
[CreateAssetMenu(fileName = "New DualPrefabObjects", menuName = "FishNet/Spawnable Prefabs/Dual Prefab Objects")]
|
||||
public class DualPrefabObjects : PrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Prefabs which may be spawned.")]
|
||||
[SerializeField]
|
||||
private List<DualPrefab> _prefabs = new List<DualPrefab>();
|
||||
/// <summary>
|
||||
/// Prefabs which may be spawned.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<DualPrefab> Prefabs => _prefabs;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_prefabs.Clear();
|
||||
}
|
||||
public override int GetObjectCount()
|
||||
{
|
||||
return _prefabs.Count;
|
||||
}
|
||||
|
||||
public override NetworkObject GetObject(bool asServer, int id)
|
||||
{
|
||||
if (id < 0 || id >= _prefabs.Count)
|
||||
{
|
||||
NetworkManager.StaticLogError($"PrefabId {id} is out of range.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
DualPrefab dp = _prefabs[id];
|
||||
NetworkObject nob = (asServer) ? dp.Server : dp.Client;
|
||||
if (nob == null)
|
||||
{
|
||||
string lookupSide = (asServer) ? "server" : "client";
|
||||
NetworkManager.StaticLogError($"Prefab for {lookupSide} on id {id} is null ");
|
||||
}
|
||||
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveNull()
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i].Server == null || _prefabs[i].Client == null)
|
||||
{
|
||||
_prefabs.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false)
|
||||
{
|
||||
AddObjects(new DualPrefab[] { dualPrefab }, checkForDuplicates);
|
||||
}
|
||||
|
||||
public override void AddObjects(List<DualPrefab> dualPrefabs, bool checkForDuplicates = false)
|
||||
{
|
||||
AddObjects(dualPrefabs.ToArray(), checkForDuplicates);
|
||||
}
|
||||
|
||||
public override void AddObjects(DualPrefab[] dualPrefabs, bool checkForDuplicates = false)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
{
|
||||
_prefabs.AddRange(dualPrefabs);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (DualPrefab dp in dualPrefabs)
|
||||
AddUniqueNetworkObjects(dp);
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
private void AddUniqueNetworkObjects(DualPrefab dp)
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i].Server == dp.Server && _prefabs[i].Client == dp.Client)
|
||||
return;
|
||||
}
|
||||
|
||||
_prefabs.Add(dp);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void InitializePrefabRange(int startIndex)
|
||||
{
|
||||
for (int i = startIndex; i < _prefabs.Count; i++)
|
||||
{
|
||||
ManagedObjects.InitializePrefab(_prefabs[i].Server, i, CollectionId);
|
||||
ManagedObjects.InitializePrefab(_prefabs[i].Client, i, CollectionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Unused.
|
||||
public override void AddObject(NetworkObject networkObject, bool checkForDuplicates = false)
|
||||
{
|
||||
NetworkManager.StaticLogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false)
|
||||
{
|
||||
NetworkManager.StaticLogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false)
|
||||
{
|
||||
NetworkManager.StaticLogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4b890523e001c74a9a2bf0d6340e5f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,36 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
//document
|
||||
[APIExclude]
|
||||
public abstract class PrefabObjects : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// CollectionId for this PrefabObjects.
|
||||
/// </summary>
|
||||
public ushort CollectionId { get; private set; }
|
||||
/// <summary>
|
||||
/// Sets CollectionIdValue.
|
||||
/// </summary>
|
||||
internal void SetCollectionId(ushort id) => CollectionId = id;
|
||||
|
||||
public abstract void Clear();
|
||||
public abstract int GetObjectCount();
|
||||
public abstract NetworkObject GetObject(bool asServer, int id);
|
||||
public abstract void RemoveNull();
|
||||
public abstract void AddObject(NetworkObject networkObject, bool checkForDuplicates = false);
|
||||
public abstract void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false);
|
||||
public abstract void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false);
|
||||
public abstract void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false);
|
||||
public abstract void AddObjects(List<DualPrefab> dualPrefab, bool checkForDuplicates = false);
|
||||
public abstract void AddObjects(DualPrefab[] dualPrefab, bool checkForDuplicates = false);
|
||||
public abstract void InitializePrefabRange(int startIndex);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5a7beb0d6ee75a4fb1f058eb3e2640a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,128 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
//document
|
||||
[APIExclude]
|
||||
[CreateAssetMenu(fileName = "New SinglePrefabObjects", menuName = "FishNet/Spawnable Prefabs/Single Prefab Objects")]
|
||||
public class SinglePrefabObjects : PrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Prefabs which may be spawned.")]
|
||||
[SerializeField]
|
||||
private List<NetworkObject> _prefabs = new List<NetworkObject>();
|
||||
/// <summary>
|
||||
/// Prefabs which may be spawned.
|
||||
/// </summary>
|
||||
public IReadOnlyList<NetworkObject> Prefabs => _prefabs;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_prefabs.Clear();
|
||||
}
|
||||
public override int GetObjectCount()
|
||||
{
|
||||
return _prefabs.Count;
|
||||
}
|
||||
public override NetworkObject GetObject(bool asServer, int id)
|
||||
{
|
||||
if (id < 0 || id >= _prefabs.Count)
|
||||
{
|
||||
NetworkManager.StaticLogError($"PrefabId {id} is out of range.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkObject nob = _prefabs[id];
|
||||
if (nob == null)
|
||||
NetworkManager.StaticLogError($"Prefab on id {id} is null.");
|
||||
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveNull()
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i] == null)
|
||||
{
|
||||
_prefabs.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObject(NetworkObject networkObject, bool checkForDuplicates = false)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
_prefabs.Add(networkObject);
|
||||
else
|
||||
AddUniqueNetworkObject(networkObject);
|
||||
|
||||
if (Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
{
|
||||
_prefabs.AddRange(networkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject nob in networkObjects)
|
||||
AddUniqueNetworkObject(nob);
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
public override void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false)
|
||||
{
|
||||
AddObjects(networkObjects.ToList(), checkForDuplicates);
|
||||
}
|
||||
|
||||
private void AddUniqueNetworkObject(NetworkObject nob)
|
||||
{
|
||||
if (!_prefabs.Contains(nob))
|
||||
_prefabs.Add(nob);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void InitializePrefabRange(int startIndex)
|
||||
{
|
||||
for (int i = startIndex; i < _prefabs.Count; i++)
|
||||
ManagedObjects.InitializePrefab(_prefabs[i], i, CollectionId);
|
||||
}
|
||||
|
||||
|
||||
#region Unused.
|
||||
public override void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false)
|
||||
{
|
||||
NetworkManager.StaticLogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(List<DualPrefab> dualPrefab, bool checkForDuplicates = false)
|
||||
{
|
||||
NetworkManager.StaticLogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(DualPrefab[] dualPrefab, bool checkForDuplicates = false)
|
||||
{
|
||||
NetworkManager.StaticLogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4489d77032a81ef42b0067acf2737d4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
10
Assets/FishNet/Runtime/Managing/Object/SpawnParentType.cs
Normal file
10
Assets/FishNet/Runtime/Managing/Object/SpawnParentType.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public enum SpawnParentType : byte
|
||||
{
|
||||
Unset = 0,
|
||||
NetworkObject = 1,
|
||||
NetworkBehaviour = 2
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbace351ced9ff94eb294dbb2e1d6a75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1
Assets/FishNet/Runtime/Managing/Object/SpawnWriter.cs
Normal file
1
Assets/FishNet/Runtime/Managing/Object/SpawnWriter.cs
Normal file
@ -0,0 +1 @@
|
||||
//Remove on 2023/06/01
|
11
Assets/FishNet/Runtime/Managing/Object/SpawnWriter.cs.meta
Normal file
11
Assets/FishNet/Runtime/Managing/Object/SpawnWriter.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd305e51107fc3441a6f52636c27298f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Observing.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Observing.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c466e864bb3b114ab6e933d624f9934
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Observing/Editor.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Observing/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b070cdff750cb4542bf0770a57f9936a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,53 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Observing.Editing
|
||||
{
|
||||
|
||||
|
||||
[CustomEditor(typeof(ObserverManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class ObserverManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _useNetworkLod;
|
||||
private SerializedProperty _levelOfDetailDistances;
|
||||
private SerializedProperty _updateHostVisibility;
|
||||
private SerializedProperty _defaultConditions;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_useNetworkLod = serializedObject.FindProperty(nameof(_useNetworkLod));
|
||||
_levelOfDetailDistances = serializedObject.FindProperty(nameof(_levelOfDetailDistances));
|
||||
_updateHostVisibility = serializedObject.FindProperty(nameof(_updateHostVisibility));
|
||||
_defaultConditions = serializedObject.FindProperty(nameof(_defaultConditions));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ObserverManager)target), typeof(ObserverManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
|
||||
EditorGUILayout.PropertyField(_useNetworkLod);
|
||||
if (_useNetworkLod.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_levelOfDetailDistances);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_updateHostVisibility);
|
||||
EditorGUILayout.PropertyField(_defaultConditions);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86e21ffc228c07d4891b6e74080b8c90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
324
Assets/FishNet/Runtime/Managing/Observing/ObserverManager.cs
Normal file
324
Assets/FishNet/Runtime/Managing/Observing/ObserverManager.cs
Normal file
@ -0,0 +1,324 @@
|
||||
using FishNet.Connection; //remove on 2023/01/01 move to correct folder.
|
||||
using FishNet.Object;
|
||||
using FishNet.Observing;
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.DEMOS_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Managing.Observing
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional options for managing the observer system.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/ObserverManager")]
|
||||
public sealed class ObserverManager : MonoBehaviour
|
||||
{
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Current index to use for level of detail based on tick.
|
||||
/// </summary>
|
||||
internal byte LevelOfDetailIndex { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("True to use the NetworkLOD system.")]
|
||||
[SerializeField]
|
||||
private bool _useNetworkLod;
|
||||
/// <summary>
|
||||
/// True to use the NetworkLOD system.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool GetUseNetworkLod() => _useNetworkLod;
|
||||
/// <summary>
|
||||
/// Distance for each level of detal.
|
||||
/// </summary>
|
||||
internal List<float> GetLevelOfDetailDistances() => (_useNetworkLod) ? _levelOfDetailDistances : _singleLevelOfDetailDistances;
|
||||
[Tooltip("Distance for each level of detal.")]
|
||||
[SerializeField]
|
||||
private List<float> _levelOfDetailDistances = new List<float>();
|
||||
/// <summary>
|
||||
/// Returned when network LOD is off. Value contained is one level of detail with max distance.
|
||||
/// </summary>
|
||||
private List<float> _singleLevelOfDetailDistances = new List<float>() { float.MaxValue };
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("True to update visibility for clientHost based on if they are an observer or not.")]
|
||||
[FormerlySerializedAs("_setHostVisibility")]
|
||||
[SerializeField]
|
||||
private bool _updateHostVisibility = true;
|
||||
/// <summary>
|
||||
/// True to update visibility for clientHost based on if they are an observer or not.
|
||||
/// </summary>
|
||||
public bool UpdateHostVisibility
|
||||
{
|
||||
get => _updateHostVisibility;
|
||||
private set => _updateHostVisibility = value;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Default observer conditions for networked objects.")]
|
||||
[SerializeField]
|
||||
private List<ObserverCondition> _defaultConditions = new List<ObserverCondition>();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// NetworkManager on object.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// Intervals for each level of detail.
|
||||
/// </summary>
|
||||
private uint[] _levelOfDetailIntervals;
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_useNetworkLod && _levelOfDetailDistances.Count > 1)
|
||||
{
|
||||
Debug.LogWarning("Network Level of Detail has been disabled while bugs are resolved in relation to this feature. You do not need to make any changes to your project. This warning will be removed once all issues are resolved.");
|
||||
_useNetworkLod = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script for use.
|
||||
/// </summary>
|
||||
/// <param name="manager"></param>
|
||||
internal void InitializeOnce_Internal(NetworkManager manager)
|
||||
{
|
||||
_networkManager = manager;
|
||||
ValidateLevelOfDetails();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new value for UpdateHostVisibility.
|
||||
/// </summary>
|
||||
/// <param name="value">New value.</param>
|
||||
/// <param name="updateType">Which objects to update.</param>
|
||||
public void SetUpdateHostVisibility(bool value, HostVisibilityUpdateTypes updateType)
|
||||
{
|
||||
//Unchanged.
|
||||
if (value == UpdateHostVisibility)
|
||||
return;
|
||||
|
||||
/* Update even if server state is not known.
|
||||
* The setting should be updated so when the server
|
||||
* does start spawned objects have latest setting. */
|
||||
if (HostVisibilityUpdateContains(updateType, HostVisibilityUpdateTypes.Manager))
|
||||
UpdateHostVisibility = value;
|
||||
|
||||
/* If to update spawned as well then update all networkobservers
|
||||
* with the setting and also update renderers. */
|
||||
if (_networkManager.IsServer && HostVisibilityUpdateContains(updateType, HostVisibilityUpdateTypes.Spawned))
|
||||
{
|
||||
NetworkConnection clientConn = _networkManager.ClientManager.Connection;
|
||||
foreach (NetworkObject n in _networkManager.ServerManager.Objects.Spawned.Values)
|
||||
{
|
||||
n.NetworkObserver.SetUpdateHostVisibility(value);
|
||||
|
||||
//Only check to update renderers if clientHost. If not client then clientConn won't be active.
|
||||
if (clientConn.IsActive)
|
||||
n.SetRenderersVisible(n.Observers.Contains(clientConn), true);
|
||||
}
|
||||
}
|
||||
|
||||
bool HostVisibilityUpdateContains(HostVisibilityUpdateTypes whole, HostVisibilityUpdateTypes part)
|
||||
{
|
||||
return (whole & part) == part;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds default observer conditions to nob and returns the NetworkObserver used.
|
||||
/// </summary>
|
||||
internal NetworkObserver AddDefaultConditions(NetworkObject nob)
|
||||
{
|
||||
bool isGlobal = (nob.IsGlobal && !nob.IsSceneObject);
|
||||
bool obsAdded;
|
||||
|
||||
NetworkObserver result;
|
||||
if (!nob.TryGetComponent<NetworkObserver>(out result))
|
||||
{
|
||||
obsAdded = true;
|
||||
result = nob.gameObject.AddComponent<NetworkObserver>();
|
||||
}
|
||||
else
|
||||
{
|
||||
obsAdded = false;
|
||||
}
|
||||
|
||||
/* NetworkObserver is null and there are no
|
||||
* conditions to add. Nothing will change by adding
|
||||
* the NetworkObserver component so exit early. */
|
||||
if (!obsAdded && _defaultConditions.Count == 0)
|
||||
return result;
|
||||
|
||||
//If the NetworkObserver component was just added.
|
||||
if (obsAdded)
|
||||
{
|
||||
/* Global nobs do not need a NetworkObserver.
|
||||
* Ultimately, a global NetworkObject is one without
|
||||
* any conditions. */
|
||||
if (isGlobal)
|
||||
return result;
|
||||
//If there are no conditions then there's nothing to add.
|
||||
if (_defaultConditions.Count == 0)
|
||||
return result;
|
||||
/* If here then there not a global networkobject and there are conditions to use.
|
||||
* Since the NetworkObserver is being added fresh, set OverrideType to UseManager
|
||||
* so that the NetworkObserver is populated with the manager conditions. */
|
||||
result.OverrideType = NetworkObserver.ConditionOverrideType.UseManager;
|
||||
}
|
||||
//NetworkObject has a NetworkObserver already on it.
|
||||
else
|
||||
{
|
||||
//If global the NetworkObserver has to be cleared and set to ignore manager.
|
||||
if (isGlobal)
|
||||
{
|
||||
result.ObserverConditionsInternal.Clear();
|
||||
result.OverrideType = NetworkObserver.ConditionOverrideType.IgnoreManager;
|
||||
}
|
||||
}
|
||||
|
||||
//If ignoring manager then use whatever is already configured.
|
||||
if (result.OverrideType == NetworkObserver.ConditionOverrideType.IgnoreManager)
|
||||
{
|
||||
//Do nothing.
|
||||
}
|
||||
//If using manager then replace all with conditions.
|
||||
else if (result.OverrideType == NetworkObserver.ConditionOverrideType.UseManager)
|
||||
{
|
||||
result.ObserverConditionsInternal.Clear();
|
||||
AddMissing(result);
|
||||
}
|
||||
//Adding only new.
|
||||
else if (result.OverrideType == NetworkObserver.ConditionOverrideType.AddMissing)
|
||||
{
|
||||
AddMissing(result);
|
||||
}
|
||||
|
||||
void AddMissing(NetworkObserver networkObserver)
|
||||
{
|
||||
int count = _defaultConditions.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ObserverCondition oc = _defaultConditions[i];
|
||||
if (!networkObserver.ObserverConditionsInternal.Contains(oc))
|
||||
networkObserver.ObserverConditionsInternal.Add(oc);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tick interval to use for a lod level.
|
||||
/// </summary>
|
||||
/// <param name="lodLevel"></param>
|
||||
/// <returns></returns>
|
||||
public byte GetLevelOfDetailInterval(byte lodLevel)
|
||||
{
|
||||
if (LevelOfDetailIndex == 0)
|
||||
return 1;
|
||||
|
||||
return (byte)System.Math.Pow(2, lodLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates and sets the current level of detail index for the tick.
|
||||
/// </summary>
|
||||
internal void CalculateLevelOfDetail(uint tick)
|
||||
{
|
||||
int count = GetLevelOfDetailDistances().Count;
|
||||
for (int i = (count - 1); i > 0; i--)
|
||||
{
|
||||
uint interval = _levelOfDetailIntervals[i];
|
||||
if (tick % interval == 0)
|
||||
{
|
||||
LevelOfDetailIndex = (byte)i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//If here then index is 0 and interval is every tick.
|
||||
LevelOfDetailIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that level of detail intervals are proper.
|
||||
/// </summary>
|
||||
private void ValidateLevelOfDetails()
|
||||
{
|
||||
if (!_useNetworkLod)
|
||||
return;
|
||||
|
||||
//No distances specified.
|
||||
if (_levelOfDetailDistances == null || _levelOfDetailDistances.Count == 0)
|
||||
{
|
||||
if (_networkManager != null)
|
||||
{
|
||||
_networkManager.LogWarning("Level of detail distances contains no entries. NetworkLOD has been disabled.");
|
||||
_useNetworkLod = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Make sure every distance is larger than the last.
|
||||
float lastDistance = float.MinValue;
|
||||
foreach (float dist in _levelOfDetailDistances)
|
||||
{
|
||||
if (dist <= 0f || dist <= lastDistance)
|
||||
{
|
||||
if (_networkManager != null)
|
||||
{
|
||||
_networkManager.LogError($"Level of detail distances must be greater than 0f, and each distance larger than the previous. NetworkLOD has been disabled.");
|
||||
_useNetworkLod = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastDistance = dist;
|
||||
}
|
||||
|
||||
int maxEntries = 8;
|
||||
//Too many distances.
|
||||
if (_levelOfDetailDistances.Count > maxEntries)
|
||||
{
|
||||
_networkManager?.LogWarning("There can be a maximum of 8 level of detail distances. Entries beyond this quantity have been discarded.");
|
||||
while (_levelOfDetailDistances.Count > maxEntries)
|
||||
_levelOfDetailDistances.RemoveAt(_levelOfDetailDistances.Count - 1);
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
//Build intervals and sqr distances.
|
||||
int count = _levelOfDetailDistances.Count;
|
||||
_levelOfDetailIntervals = new uint[count];
|
||||
for (int i = (count - 1); i > 0; i--)
|
||||
{
|
||||
uint power = (uint)Mathf.Pow(2, i);
|
||||
_levelOfDetailIntervals[i] = power;
|
||||
|
||||
}
|
||||
//Sqr
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float dist = _levelOfDetailDistances[i];
|
||||
dist *= dist;
|
||||
_levelOfDetailDistances[i] = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d331f979d46e8e4a9fc90070c596d44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
12
Assets/FishNet/Runtime/Managing/Prediction.meta
Normal file
12
Assets/FishNet/Runtime/Managing/Prediction.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
<<<<<<<< HEAD:Assets/FishNet/Runtime/Managing/Prediction.meta
|
||||
guid: 549a94b4ad77ccc4b8511ccb476d767c
|
||||
========
|
||||
guid: 678ea4b95d0063048acf8abf6928acf4
|
||||
>>>>>>>> origin/3-pre-3.1:Assets/Test/InitializeOrder.meta
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Prediction/Editor.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Prediction/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f57d8859721e294489f2991b0458aea9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,74 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Predicting.Editing
|
||||
{
|
||||
|
||||
|
||||
[CustomEditor(typeof(PredictionManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class PredictionManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _dropExcessiveReplicates;
|
||||
private SerializedProperty _maximumServerReplicates;
|
||||
private SerializedProperty _maximumConsumeCount;
|
||||
private SerializedProperty _redundancyCount;
|
||||
private SerializedProperty _allowPredictedSpawning;
|
||||
private SerializedProperty _reservedObjectIds;
|
||||
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_dropExcessiveReplicates = serializedObject.FindProperty("_dropExcessiveReplicates");
|
||||
_maximumServerReplicates = serializedObject.FindProperty("_maximumServerReplicates");
|
||||
_maximumConsumeCount = serializedObject.FindProperty("_maximumConsumeCount");
|
||||
_redundancyCount = serializedObject.FindProperty("_redundancyCount");
|
||||
_allowPredictedSpawning = serializedObject.FindProperty(nameof(_allowPredictedSpawning));
|
||||
_reservedObjectIds = serializedObject.FindProperty(nameof(_reservedObjectIds));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((PredictionManager)target), typeof(PredictionManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.LabelField("Server", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_dropExcessiveReplicates);
|
||||
EditorGUI.indentLevel++;
|
||||
if (_dropExcessiveReplicates.boolValue == true)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_maximumServerReplicates);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.PropertyField(_maximumConsumeCount);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.LabelField("Client", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_redundancyCount);
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.PropertyField(_allowPredictedSpawning);
|
||||
if (_allowPredictedSpawning.boolValue == true)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_reservedObjectIds);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5382c1ab98f25c8439d23140d36651fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
323
Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs
Normal file
323
Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs
Normal file
@ -0,0 +1,323 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityScene = UnityEngine.SceneManagement.Scene;
|
||||
|
||||
|
||||
namespace FishNet.Managing.Predicting
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Additional options for managing the observer system.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/PredictionManager")]
|
||||
public sealed class PredictionManager : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called before performing a reconcile on NetworkBehaviour.
|
||||
/// </summary>
|
||||
public event Action<NetworkBehaviour> OnPreReconcile;
|
||||
/// <summary>
|
||||
/// Called after performing a reconcile on a NetworkBehaviour.
|
||||
/// </summary>
|
||||
public event Action<NetworkBehaviour> OnPostReconcile;
|
||||
/// <summary>
|
||||
/// Called before physics is simulated when replaying a replicate method.
|
||||
/// Contains the PhysicsScene and PhysicsScene2D which was simulated.
|
||||
/// </summary>
|
||||
public event Action<uint, PhysicsScene, PhysicsScene2D> OnPreReplicateReplay;
|
||||
/// <summary>
|
||||
/// Called after physics is simulated when replaying a replicate method.
|
||||
/// Contains the PhysicsScene and PhysicsScene2D which was simulated.
|
||||
/// </summary>
|
||||
public event Action<uint, PhysicsScene, PhysicsScene2D> OnPostReplicateReplay;
|
||||
/// <summary>
|
||||
/// Called before the server sends a reconcile.
|
||||
/// </summary>
|
||||
public event Action<NetworkBehaviour> OnPreServerReconcile;
|
||||
/// <summary>
|
||||
/// Called after the server sends a reconcile.
|
||||
/// </summary>
|
||||
public event Action<NetworkBehaviour> OnPostServerReconcile;
|
||||
/// <summary>
|
||||
/// Last tick any object reconciled.
|
||||
/// </summary>
|
||||
public uint LastReconcileTick { get; internal set; }
|
||||
/// <summary>
|
||||
/// Last tick any object replicated.
|
||||
/// </summary>
|
||||
public uint LastReplicateTick { get; internal set; }
|
||||
/// <summary>
|
||||
/// True if rigidbodies are being predicted.
|
||||
/// </summary>
|
||||
internal bool UsingRigidbodies => (_rigidbodies.Count > 0);
|
||||
/// <summary>
|
||||
/// Returns if any prediction is replaying.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsReplaying() => _isReplaying;
|
||||
private bool _isReplaying;
|
||||
/// <summary>
|
||||
/// Returns if scene is replaying.
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsReplaying(UnityScene scene) => _replayingScenes.Contains(scene);
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("True to drop replicates from clients which are being received excessively. This can help with attacks but may cause client to temporarily desynchronize during connectivity issues. When false the server will hold at most up to 3 seconds worth of replicates, consuming multiple per tick to clear out the buffer quicker. This is good to ensure all inputs are executed but potentially could allow speed hacking.")]
|
||||
[SerializeField]
|
||||
private bool _dropExcessiveReplicates = true;
|
||||
/// <summary>
|
||||
/// True to drop replicates from clients which are being received excessively. This can help with attacks but may cause client to temporarily desynchronize during connectivity issues.
|
||||
/// When false the server will hold at most up to 3 seconds worth of replicates, consuming multiple per tick to clear out the buffer quicker. This is good to ensure all inputs are executed but potentially could allow speed hacking.
|
||||
/// </summary>
|
||||
internal bool DropExcessiveReplicates => _dropExcessiveReplicates;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Maximum number of replicates a server can queue per object. Higher values will put more load on the server and add replicate latency for the client.")]
|
||||
[SerializeField]
|
||||
private ushort _maximumServerReplicates = 15;
|
||||
/// <summary>
|
||||
/// Maximum number of replicates a server can queue per object. Higher values will put more load on the server and add replicate latency for the client.
|
||||
/// </summary>
|
||||
public ushort GetMaximumServerReplicates() => _maximumServerReplicates;
|
||||
/// <summary>
|
||||
/// Sets the maximum number of replicates a server can queue per object.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetMaximumServerReplicates(ushort value)
|
||||
{
|
||||
_maximumServerReplicates = (ushort)Mathf.Clamp(value, MINIMUM_REPLICATE_QUEUE_SIZE, MAXIMUM_REPLICATE_QUEUE_SIZE);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Maximum number of excessive replicates which can be consumed per tick. Consumption count will scale up to this value automatically.")]
|
||||
[SerializeField]
|
||||
private byte _maximumConsumeCount = 4;
|
||||
/// <summary>
|
||||
/// Maximum number of excessive replicates which can be consumed per tick. Consumption count will scale up to this value automatically.
|
||||
/// </summary>
|
||||
internal byte GetMaximumConsumeCount() => _maximumConsumeCount;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Maximum number of past inputs which may send.")]
|
||||
[Range(MINIMUM_PAST_INPUTS, MAXIMUM_PAST_INPUTS)]
|
||||
[SerializeField]
|
||||
private byte _redundancyCount = 3;
|
||||
/// <summary>
|
||||
/// Maximum number of past inputs which may send and resend redundancy.
|
||||
/// </summary>
|
||||
#if UNITY_WEBGL
|
||||
//WebGL uses reliable so no reason to use redundancy.
|
||||
internal byte GetRedundancyCount() => 1;
|
||||
#else
|
||||
internal byte GetRedundancyCount() => _redundancyCount;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// True to allow clients to use predicted spawning. While true, each NetworkObject prefab you wish to predicted spawn must be marked as to allow this feature.
|
||||
/// </summary>
|
||||
internal bool GetAllowPredictedSpawning() => _allowPredictedSpawning;
|
||||
[Tooltip("True to allow clients to use predicted spawning and despawning. While true, each NetworkObject prefab you wish to predicted spawn must be marked as to allow this feature.")]
|
||||
[SerializeField]
|
||||
private bool _allowPredictedSpawning = false;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Tooltip("Maximum number of Ids to reserve on clients for predicted spawning. Higher values will allow clients to send more predicted spawns per second but may reduce availability of ObjectIds with high player counts.")]
|
||||
[Range(1, 100)]
|
||||
[SerializeField]
|
||||
private byte _reservedObjectIds = 15;
|
||||
/// <summary>
|
||||
/// Maximum number of Ids to reserve on clients for predicted spawning. Higher values will allow clients to send more predicted spawns per second but may reduce availability of ObjectIds with high player counts.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal byte GetReservedObjectIds() => _reservedObjectIds;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Number of active predicted rigidbodies.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
private HashSet<UnityEngine.Component> _rigidbodies = new HashSet<UnityEngine.Component>();
|
||||
/// <summary>
|
||||
/// Cache to remove null entries from _rigidbodies.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
private HashSet<UnityEngine.Component> _componentCache = new HashSet<UnityEngine.Component>();
|
||||
/// <summary>
|
||||
/// NetworkManager used with this.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// Scenes which are currently replaying prediction.
|
||||
/// </summary>
|
||||
private HashSet<UnityScene> _replayingScenes = new HashSet<UnityScene>(new SceneHandleEqualityComparer());
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Minimum number of past inputs which can be sent.
|
||||
/// </summary>
|
||||
private const byte MINIMUM_PAST_INPUTS = 2;
|
||||
/// <summary>
|
||||
/// Maximum number of past inputs which can be sent.
|
||||
/// </summary>
|
||||
internal const byte MAXIMUM_PAST_INPUTS = 15;
|
||||
/// <summary>
|
||||
/// Minimum amount of replicate queue size.
|
||||
/// </summary>
|
||||
private const ushort MINIMUM_REPLICATE_QUEUE_SIZE = 10;
|
||||
/// <summary>
|
||||
/// Maxmimum amount of replicate queue size.
|
||||
/// </summary>
|
||||
private const ushort MAXIMUM_REPLICATE_QUEUE_SIZE = 500;
|
||||
#endregion
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
|
||||
}
|
||||
|
||||
internal void InitializeOnce_Internal(NetworkManager manager)
|
||||
{
|
||||
_networkManager = manager;
|
||||
_networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the local client connection state changes.
|
||||
/// </summary>
|
||||
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
|
||||
{
|
||||
if (obj.ConnectionState != LocalConnectionState.Started)
|
||||
_replayingScenes.Clear();
|
||||
_isReplaying = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called before and after server sends a reconcile.
|
||||
/// </summary>
|
||||
/// <param name="before">True if before the reconcile is sent.</param>
|
||||
internal void InvokeServerReconcile(NetworkBehaviour caller, bool before)
|
||||
{
|
||||
if (before)
|
||||
OnPreServerReconcile?.Invoke(caller);
|
||||
else
|
||||
OnPostServerReconcile?.Invoke(caller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases Rigidbodies count by 1.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public void AddRigidbodyCount(UnityEngine.Component c)
|
||||
{
|
||||
_rigidbodies.Add(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dencreases Rigidbodies count by 1.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public void RemoveRigidbodyCount(UnityEngine.Component c)
|
||||
{
|
||||
bool removed = _rigidbodies.Remove(c);
|
||||
/* If remove failed the rigidbodies may need to be rebuild.
|
||||
* This might happen when an object is destroyed as
|
||||
* the referenced is passed. Could be any number of things
|
||||
* but it seems to occur frequently enough in Unity,
|
||||
* especially when testing in editor.
|
||||
*
|
||||
* This operation is not ideal in the hot path but
|
||||
* the odds of it happening are pretty slim and
|
||||
* it ensures stability against user error. */
|
||||
if (!removed)
|
||||
{
|
||||
//Cannt remove null entries from a hashset so have to rebuild.
|
||||
_componentCache.Clear();
|
||||
foreach (UnityEngine.Component item in _rigidbodies)
|
||||
{
|
||||
if (item != null)
|
||||
_componentCache.Add(item);
|
||||
}
|
||||
|
||||
//Apply to rigidbodies.
|
||||
_rigidbodies.Clear();
|
||||
foreach (UnityEngine.Component item in _componentCache)
|
||||
_rigidbodies.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnPre/PostReconcile events.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic] //To internal.
|
||||
public void InvokeOnReconcile_Internal(NetworkBehaviour nb, bool before)
|
||||
{
|
||||
nb.IsReconciling = before;
|
||||
if (before)
|
||||
OnPreReconcile?.Invoke(nb);
|
||||
else
|
||||
OnPostReconcile?.Invoke(nb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnReplicateReplay.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic] //To internal.
|
||||
public void InvokeOnReplicateReplay_Internal(UnityScene scene, uint tick, PhysicsScene ps, PhysicsScene2D ps2d, bool before)
|
||||
{
|
||||
_isReplaying = before;
|
||||
if (before)
|
||||
{
|
||||
_replayingScenes.Add(scene);
|
||||
OnPreReplicateReplay?.Invoke(tick, ps, ps2d);
|
||||
}
|
||||
else
|
||||
{
|
||||
_replayingScenes.Remove(scene);
|
||||
OnPostReplicateReplay?.Invoke(tick, ps, ps2d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when a scene unloads.
|
||||
/// </summary>
|
||||
/// <param name="arg0"></param>
|
||||
private void SceneManager_sceneUnloaded(UnityScene s)
|
||||
{
|
||||
_replayingScenes.Remove(s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e08bb003fce297d4086cf8cba5aa459a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Scened.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Scened.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 212ba5e93fab23e45a461067dc0bf611
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Scened/Broadcast.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Scened/Broadcast.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8eb071c832ae3664c9b1701b52872513
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,38 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Sent when there are starting scenes for the client to load.
|
||||
/// </summary>
|
||||
public struct EmptyStartScenesBroadcast : IBroadcast { }
|
||||
/// <summary>
|
||||
/// Sent to clients to load networked scenes.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct LoadScenesBroadcast : IBroadcast
|
||||
{
|
||||
public LoadQueueData QueueData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to clients to unload networked scenes.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct UnloadScenesBroadcast : IBroadcast
|
||||
{
|
||||
public UnloadQueueData QueueData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to server to indicate which scenes a client has loaded.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct ClientScenesLoadedBroadcast : IBroadcast
|
||||
{
|
||||
public SceneLookupData[] SceneLookupDatas;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 698a94b4f8664ac4ab108deae0ba3b7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
155
Assets/FishNet/Runtime/Managing/Scened/DefaultSceneProcessor.cs
Normal file
155
Assets/FishNet/Runtime/Managing/Scened/DefaultSceneProcessor.cs
Normal file
@ -0,0 +1,155 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnitySceneManager = UnityEngine.SceneManagement.SceneManager;
|
||||
using UnityScene = UnityEngine.SceneManagement.Scene;
|
||||
using System.Collections;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
|
||||
public class DefaultSceneProcessor : SceneProcessorBase
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Currently active loading AsyncOperations.
|
||||
/// </summary>
|
||||
protected List<AsyncOperation> LoadingAsyncOperations = new List<AsyncOperation>();
|
||||
/// <summary>
|
||||
/// A collection of scenes used both for loading and unloading.
|
||||
/// </summary>
|
||||
protected List<UnityScene> Scenes = new List<UnityScene>();
|
||||
/// <summary>
|
||||
/// Current AsyncOperation being processed.
|
||||
/// </summary>
|
||||
protected AsyncOperation CurrentAsyncOperation;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when scene loading has begun.
|
||||
/// </summary>
|
||||
public override void LoadStart(LoadQueueData queueData)
|
||||
{
|
||||
base.LoadStart(queueData);
|
||||
ResetValues();
|
||||
}
|
||||
|
||||
public override void LoadEnd(LoadQueueData queueData)
|
||||
{
|
||||
base.LoadEnd(queueData);
|
||||
ResetValues();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values for a fresh load or unload.
|
||||
/// </summary>
|
||||
private void ResetValues()
|
||||
{
|
||||
CurrentAsyncOperation = null;
|
||||
LoadingAsyncOperations.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when scene unloading has begun within an unload operation.
|
||||
/// </summary>
|
||||
/// <param name="queueData"></param>
|
||||
public override void UnloadStart(UnloadQueueData queueData)
|
||||
{
|
||||
base.UnloadStart(queueData);
|
||||
Scenes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin loading a scene using an async method.
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Scene name to load.</param>
|
||||
public override void BeginLoadAsync(string sceneName, UnityEngine.SceneManagement.LoadSceneParameters parameters)
|
||||
{
|
||||
AsyncOperation ao = UnitySceneManager.LoadSceneAsync(sceneName, parameters);
|
||||
LoadingAsyncOperations.Add(ao);
|
||||
|
||||
CurrentAsyncOperation = ao;
|
||||
CurrentAsyncOperation.allowSceneActivation = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin unloading a scene using an async method.
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Scene name to unload.</param>
|
||||
public override void BeginUnloadAsync(UnityScene scene)
|
||||
{
|
||||
CurrentAsyncOperation = UnitySceneManager.UnloadSceneAsync(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a scene load or unload percent is done.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool IsPercentComplete()
|
||||
{
|
||||
return (GetPercentComplete() >= 0.9f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the progress on the current scene load or unload.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override float GetPercentComplete()
|
||||
{
|
||||
return (CurrentAsyncOperation == null) ? 1f : CurrentAsyncOperation.progress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a loaded scene.
|
||||
/// </summary>
|
||||
/// <param name="scene">Scene loaded.</param>
|
||||
public override void AddLoadedScene(UnityScene scene)
|
||||
{
|
||||
base.AddLoadedScene(scene);
|
||||
Scenes.Add(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns scenes which were loaded during a load operation.
|
||||
/// </summary>
|
||||
public override List<UnityScene> GetLoadedScenes()
|
||||
{
|
||||
return Scenes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates scenes which were loaded.
|
||||
/// </summary>
|
||||
public override void ActivateLoadedScenes()
|
||||
{
|
||||
foreach (AsyncOperation ao in LoadingAsyncOperations)
|
||||
ao.allowSceneActivation = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if all asynchronized tasks are considered IsDone.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override IEnumerator AsyncsIsDone()
|
||||
{
|
||||
bool notDone;
|
||||
do
|
||||
{
|
||||
notDone = false;
|
||||
foreach (AsyncOperation ao in LoadingAsyncOperations)
|
||||
{
|
||||
|
||||
if (!ao.isDone)
|
||||
{
|
||||
notDone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (notDone);
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c6eacaa60569d947b383df03fff1ea3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Managing/Scened/Events.meta
Normal file
8
Assets/FishNet/Runtime/Managing/Scened/Events.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09823f70d9231d34f91d88d11b937758
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,34 @@
|
||||
using FishNet.Connection;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Data container about a scene presence change for a client.
|
||||
/// </summary>
|
||||
public struct ClientPresenceChangeEventArgs
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Scene on the server which the client's presence has changed.
|
||||
/// </summary>
|
||||
public Scene Scene;
|
||||
/// <summary>
|
||||
/// Connection to client.
|
||||
/// </summary>
|
||||
public NetworkConnection Connection;
|
||||
/// <summary>
|
||||
/// True if the client was added to the scene, false is removed.
|
||||
/// </summary>
|
||||
public bool Added;
|
||||
|
||||
internal ClientPresenceChangeEventArgs(Scene scene, NetworkConnection conn, bool added)
|
||||
{
|
||||
Scene = scene;
|
||||
Connection = conn;
|
||||
Added = added;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa91039d4ab1c6445af72881af122b0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,78 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Data container about a scene load start.
|
||||
/// </summary>
|
||||
public struct SceneLoadStartEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly LoadQueueData QueueData;
|
||||
|
||||
internal SceneLoadStartEventArgs(LoadQueueData lqd)
|
||||
{
|
||||
QueueData = lqd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene load percent change.
|
||||
/// </summary>
|
||||
public struct SceneLoadPercentEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly LoadQueueData QueueData;
|
||||
/// <summary>
|
||||
/// Percentage of change completion. 1f is equal to 100% complete.
|
||||
/// </summary>
|
||||
public readonly float Percent;
|
||||
|
||||
internal SceneLoadPercentEventArgs(LoadQueueData lqd, float percent)
|
||||
{
|
||||
QueueData = lqd;
|
||||
Percent = percent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene load end.
|
||||
/// </summary>
|
||||
public struct SceneLoadEndEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly LoadQueueData QueueData;
|
||||
/// <summary>
|
||||
/// Scenes which were loaded.
|
||||
/// </summary>
|
||||
public readonly Scene[] LoadedScenes;
|
||||
/// <summary>
|
||||
/// Scenes which were skipped because they were already loaded.
|
||||
/// </summary>
|
||||
public readonly string[] SkippedSceneNames;
|
||||
/// <summary>
|
||||
/// Scenes which were unloaded.
|
||||
/// </summary>
|
||||
public readonly string[] UnloadedSceneNames;
|
||||
|
||||
internal SceneLoadEndEventArgs(LoadQueueData lqd, string[] skipped, Scene[] loaded, string[] unloadedSceneNames)
|
||||
{
|
||||
QueueData = lqd;
|
||||
SkippedSceneNames = skipped;
|
||||
LoadedScenes = loaded;
|
||||
UnloadedSceneNames = unloadedSceneNames;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86278568f8087de49b0908f148501993
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene unload start.
|
||||
/// </summary>
|
||||
public struct SceneUnloadStartEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly UnloadQueueData QueueData;
|
||||
|
||||
internal SceneUnloadStartEventArgs(UnloadQueueData sqd)
|
||||
{
|
||||
QueueData = sqd;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene unload end.
|
||||
/// </summary>
|
||||
public struct SceneUnloadEndEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly UnloadQueueData QueueData;
|
||||
/// <summary>
|
||||
/// Handles of scenes which were successfully unloaded.
|
||||
/// </summary>
|
||||
[Obsolete("Use UnloadedScenesV2")]
|
||||
public int[] UnloadedSceneHandles;
|
||||
/// <summary>
|
||||
/// Names of scenes which were successfully unloaded.
|
||||
/// </summary>
|
||||
[Obsolete("Use UnloadedScenesV2")]
|
||||
public string[] UnloadedSceneNames;
|
||||
|
||||
/// <summary>
|
||||
/// Scenes which were successfully unloaded.
|
||||
/// This collection may be populated with empty scenes depending on engine version.
|
||||
/// </summary>
|
||||
public List<Scene> UnloadedScenes;
|
||||
/// <summary>
|
||||
/// Unloaded scenes with names and handles cached.
|
||||
/// This will be renamed as UnloadedScenes in Fish-Networking version 4.
|
||||
/// </summary>
|
||||
public List<UnloadedScene> UnloadedScenesV2;
|
||||
|
||||
internal SceneUnloadEndEventArgs(UnloadQueueData sqd, List<Scene> unloadedScenes, List<UnloadedScene> newUnloadedScenes)
|
||||
{
|
||||
QueueData = sqd;
|
||||
UnloadedScenes = unloadedScenes;
|
||||
UnloadedScenesV2 = newUnloadedScenes;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
UnloadedSceneNames = new string[newUnloadedScenes.Count];
|
||||
UnloadedSceneHandles = new int[newUnloadedScenes.Count];
|
||||
for (int i = 0; i < newUnloadedScenes.Count; i++)
|
||||
{
|
||||
UnloadedSceneNames[i] = newUnloadedScenes[i].Name;
|
||||
UnloadedSceneHandles[i] = newUnloadedScenes[i].Handle;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c24765fea85b564aa331b529f324f92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: beefc84827a4af141aa1b326fca9084f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,39 @@
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings to apply when loading a scene.
|
||||
/// </summary>
|
||||
public class LoadOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// True if to automatically unload the loaded scenes when they are no longer being used by clients. This field only applies to scenes loaded for connections, not globally loaded scenes.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public bool AutomaticallyUnload = true;
|
||||
/// <summary>
|
||||
/// False if to only load scenes which are not yet loaded. When true a scene may load multiple times; this is known as scene stacking. Only the server is able to stack scenes; clients will load a single instance. Global scenes cannot be stacked.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public bool AllowStacking;
|
||||
/// <summary>
|
||||
/// LocalPhysics mode to use when loading this scene. Generally this will only be used when applying scene stacking. Only used by the server.
|
||||
/// https://docs.unity3d.com/ScriptReference/SceneManagement.LocalPhysicsMode.html
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public LocalPhysicsMode LocalPhysics = LocalPhysicsMode.None;
|
||||
/// <summary>
|
||||
/// True to reload a scene if it's already loaded.
|
||||
/// This does not function yet.
|
||||
/// </summary>
|
||||
[System.Obsolete("This feature is not functional yet but will be at a later release.")]
|
||||
public bool ReloadScenes;
|
||||
/// <summary>
|
||||
/// True if scenes should be loaded using addressables. This field only exists for optional use so the user may know if their queue data is using addressables.
|
||||
/// </summary>
|
||||
public bool Addressables;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1614453d3786b2a4eb18b69297da7dc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,19 @@
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional user-crafted data which can be included in scene load callbacks.
|
||||
/// </summary>
|
||||
public class LoadParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Objects which are included in callbacks on the server when loading a scene. Can be useful for including unique information about the scene, such as match id. These are not sent to clients; use ClientParams for this.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public object[] ServerParams = new object[0];
|
||||
/// <summary>
|
||||
/// Bytes which are sent to clients during scene loads. Can contain any information.
|
||||
/// </summary>
|
||||
public byte[] ClientParams = new byte[0];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8b395f67f61b4e45830a70289a1901d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,51 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.GENERATED_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data generated when loading a scene.
|
||||
/// </summary>
|
||||
public class LoadQueueData
|
||||
{
|
||||
/// <summary>
|
||||
/// Clients which receive this SceneQueueData. If Networked, all clients do. If Connections, only the specified Connections do.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public SceneScopeType ScopeType;
|
||||
/// <summary>
|
||||
/// Connections to load scenes for. Only valid on the server and when ScopeType is Connections.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public NetworkConnection[] Connections = new NetworkConnection[0];
|
||||
/// <summary>
|
||||
/// SceneLoadData to use.
|
||||
/// </summary>
|
||||
public SceneLoadData SceneLoadData = null;
|
||||
/// <summary>
|
||||
/// Current global scenes.
|
||||
/// </summary>
|
||||
public string[] GlobalScenes = new string[0];
|
||||
/// <summary>
|
||||
/// True if to iterate this queue data as server.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public readonly bool AsServer;
|
||||
|
||||
public LoadQueueData() { }
|
||||
internal LoadQueueData(SceneScopeType scopeType, NetworkConnection[] conns, SceneLoadData sceneLoadData, string[] globalScenes, bool asServer)
|
||||
{
|
||||
ScopeType = scopeType;
|
||||
Connections = conns;
|
||||
SceneLoadData = sceneLoadData;
|
||||
GlobalScenes = globalScenes;
|
||||
AsServer = asServer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fb4183af628f754b800dfdbb1ba9bf0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,25 @@
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// How to replace scenes when loading.
|
||||
/// </summary>
|
||||
public enum ReplaceOption : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Replace all scenes, online and offline.
|
||||
/// </summary>
|
||||
All,
|
||||
/// <summary>
|
||||
/// Only replace scenes loaded using the SceneManager.
|
||||
/// </summary>
|
||||
OnlineOnly,
|
||||
/// <summary>
|
||||
/// Do not replace any scenes, additional scenes will be loaded as additive.
|
||||
/// </summary>
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb8e2c0fe3b9d3344a05810936861555
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,201 @@
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing.Helping;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Data about which scenes to load.
|
||||
/// </summary>
|
||||
public class SceneLoadData
|
||||
{
|
||||
/// <summary>
|
||||
/// When specified this scene will be set as the active scene after loading occurs.
|
||||
/// </summary>
|
||||
public SceneLookupData PreferredActiveScene = null;
|
||||
/// <summary>
|
||||
/// SceneLookupData for each scene to load.
|
||||
/// </summary>
|
||||
public SceneLookupData[] SceneLookupDatas = new SceneLookupData[0];
|
||||
/// <summary>
|
||||
/// NetworkObjects to move to the new scenes. Objects will be moved to the first scene.
|
||||
/// </summary>
|
||||
public NetworkObject[] MovedNetworkObjects = new NetworkObject[0];
|
||||
/// <summary>
|
||||
/// How to replace current scenes with new ones. When replacing scenes the first scene loaded will be set as the active scene, and the rest additive.
|
||||
/// </summary>
|
||||
public ReplaceOption ReplaceScenes = ReplaceOption.None;
|
||||
/// <summary>
|
||||
/// Parameters which may be set and will be included in load callbacks.
|
||||
/// </summary>
|
||||
public LoadParams Params = new LoadParams();
|
||||
/// <summary>
|
||||
/// Additional options to use for loaded scenes.
|
||||
/// </summary>
|
||||
public LoadOptions Options = new LoadOptions();
|
||||
|
||||
public SceneLoadData() { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="scene">Scene to load.</param>
|
||||
public SceneLoadData(Scene scene) : this(new Scene[] { scene }, null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Scene to load by name.</param>
|
||||
public SceneLoadData(string sceneName) : this(new string[] { sceneName }, null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneHandle">Scene to load by handle.</param>
|
||||
public SceneLoadData(int sceneHandle) : this(new int[] { sceneHandle }, null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneHandle">Scene to load by handle.</param>
|
||||
/// <param name="sceneName">Scene to load by name.</param>
|
||||
public SceneLoadData(int sceneHandle, string sceneName) : this(new SceneLookupData(sceneHandle, sceneName)) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneLookupData">Scene to load by SceneLookupData.</param>
|
||||
public SceneLoadData(SceneLookupData sceneLookupData) : this(new SceneLookupData[] { sceneLookupData }) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="scenes">Scenes to load.</param>
|
||||
public SceneLoadData(List<Scene> scenes) : this(scenes.ToArray(), null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneNames">Scenes to load by name.</param>
|
||||
public SceneLoadData(List<string> sceneNames) : this(sceneNames.ToArray(), null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneHandles">Scenes to load by handle.</param>
|
||||
public SceneLoadData(List<int> sceneHandles) : this(sceneHandles.ToArray(), null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="scenes">Scenes to load.</param>
|
||||
public SceneLoadData(Scene[] scenes) : this(scenes, null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneNames">Scenes to load by name.</param>
|
||||
public SceneLoadData(string[] sceneNames) : this(sceneNames, null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneHandles">Scenes to load by handle.</param>
|
||||
public SceneLoadData(int[] sceneHandles) : this(sceneHandles, null) { }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneLookupDatas">Scenes to load by SceneLookupDatas.</param>
|
||||
public SceneLoadData(SceneLookupData[] sceneLookupDatas) : this(sceneLookupDatas, null) { }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="scene">Scene to load.</param>
|
||||
/// <param name="movedNetworkObjects">NetworkObjects to move to the first specified scene.</param>
|
||||
public SceneLoadData(Scene scene, NetworkObject[] movedNetworkObjects)
|
||||
{
|
||||
SceneLookupData data = SceneLookupData.CreateData(scene);
|
||||
Construct(new SceneLookupData[] { data }, movedNetworkObjects);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="scenes">Scenes to load.</param>
|
||||
/// <param name="movedNetworkObjects">NetworkObjects to move to the first specified scene.</param>
|
||||
public SceneLoadData(Scene[] scenes, NetworkObject[] movedNetworkObjects)
|
||||
{
|
||||
SceneLookupData[] datas = SceneLookupData.CreateData(scenes);
|
||||
Construct(datas, movedNetworkObjects);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneNames">Scenes to load by Name.</param>
|
||||
/// <param name="movedNetworkObjects">NetworkObjects to move to the first specified scene.</param>
|
||||
public SceneLoadData(string[] sceneNames, NetworkObject[] movedNetworkObjects)
|
||||
{
|
||||
SceneLookupData[] datas = SceneLookupData.CreateData(sceneNames);
|
||||
Construct(datas, movedNetworkObjects);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneHandles">Scenes to load by handle.</param>
|
||||
/// <param name="movedNetworkObjects">NetworkObjects to move to the first specified scene.</param>
|
||||
public SceneLoadData(int[] sceneHandles, NetworkObject[] movedNetworkObjects)
|
||||
{
|
||||
SceneLookupData[] datas = SceneLookupData.CreateData(sceneHandles);
|
||||
Construct(datas, movedNetworkObjects);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sceneLookupDatas">Scenes to load by SceneLookupDatas.</param>
|
||||
/// <param name="movedNetworkObjects">NetworkObjects to move to the first specified scene.</param>
|
||||
public SceneLoadData(SceneLookupData[] sceneLookupDatas, NetworkObject[] movedNetworkObjects)
|
||||
{
|
||||
Construct(sceneLookupDatas, movedNetworkObjects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called at the end of every constructor.
|
||||
/// </summary>
|
||||
private void Construct(SceneLookupData[] datas, NetworkObject[] movedNetworkObjects)
|
||||
{
|
||||
SceneLookupDatas = datas;
|
||||
if (movedNetworkObjects == null)
|
||||
movedNetworkObjects = new NetworkObject[0];
|
||||
MovedNetworkObjects = movedNetworkObjects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first Scene in SceneLookupDatas.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Scene GetFirstLookupScene()
|
||||
{
|
||||
foreach (SceneLookupData sld in SceneLookupDatas)
|
||||
{
|
||||
Scene result = sld.GetScene(out _);
|
||||
if (!string.IsNullOrEmpty(result.name))
|
||||
return result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns if any data is invalid, such as null entries.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool DataInvalid()
|
||||
{
|
||||
//Null values.
|
||||
if (Params == null || MovedNetworkObjects == null || SceneLookupDatas == null ||
|
||||
Options == null)
|
||||
return true;
|
||||
//No lookups.
|
||||
if (SceneLookupDatas.Length == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecd4065158ab62047a074c594f245d90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user