[Add] FishNet
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A byte buffer that automatically resizes.
|
||||
/// </summary>
|
||||
internal class ByteBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// How many more bytes may fit into the buffer.
|
||||
/// </summary>
|
||||
internal int Remaining => Size - Length;
|
||||
/// <summary>
|
||||
/// Buffer data.
|
||||
/// </summary>
|
||||
internal byte[] Data { get; private set; }
|
||||
/// <summary>
|
||||
/// How many bytes currently into Data. This will include the reserve.
|
||||
/// </summary>
|
||||
internal int Length { get; private set; }
|
||||
/// <summary>
|
||||
/// Size of the buffer. Data.Length may exceed this value as it uses a pooled array.
|
||||
/// </summary>
|
||||
internal int Size { get; private set; }
|
||||
/// <summary>
|
||||
/// True if data has been written.
|
||||
/// </summary>
|
||||
internal bool HasData { get; private set; }
|
||||
/// <summary>
|
||||
/// Bytes to reserve when resetting.
|
||||
/// </summary>
|
||||
private int _reserve;
|
||||
|
||||
internal ByteBuffer(int size, int reserve = 0)
|
||||
{
|
||||
Data = ByteArrayPool.Retrieve(size);
|
||||
Size = size;
|
||||
_reserve = reserve;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Data != null)
|
||||
ByteArrayPool.Store(Data);
|
||||
Data = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets instance without clearing Data.
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
Length = _reserve;
|
||||
HasData = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies segments without error checking, including tick for the first time data is added.
|
||||
/// </summary>
|
||||
/// <param name = "segment"></param>
|
||||
internal void CopySegment(uint tick, ArraySegment<byte> segment)
|
||||
{
|
||||
/* If data has not been written to buffer yet
|
||||
* then write tick to the start. */
|
||||
if (!HasData)
|
||||
{
|
||||
int pos = 0;
|
||||
Writer.WriteUInt32Unpacked(Data, tick, ref pos);
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
|
||||
Length += segment.Count;
|
||||
HasData = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies segments without error checking.
|
||||
/// </summary>
|
||||
/// <param name = "segment"></param>
|
||||
internal void CopySegment(ArraySegment<byte> segment)
|
||||
{
|
||||
Buffer.BlockCopy(segment.Array, segment.Offset, Data, Length, segment.Count);
|
||||
Length += segment.Count;
|
||||
HasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class PacketBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// True if data has been written.
|
||||
/// </summary>
|
||||
internal bool HasData => _buffers[0].HasData || (!_isSendLastBundle && _sendLastBundle.HasData);
|
||||
/// <summary>
|
||||
/// All buffers written. Collection is not cleared when reset but rather the index in which to write is.
|
||||
/// </summary>
|
||||
private List<ByteBuffer> _buffers = new();
|
||||
/// <summary>
|
||||
/// Buffer which is being written to.
|
||||
/// </summary>
|
||||
private int _bufferIndex;
|
||||
/// <summary>
|
||||
/// Maximum size packet the transport can handle.
|
||||
/// </summary>
|
||||
private int _maximumTransportUnit;
|
||||
/// <summary>
|
||||
/// Number of buffers written to. Will return 0 if nothing has been written.
|
||||
/// </summary>
|
||||
public int WrittenBuffers => !HasData ? 0 : _bufferIndex + 1;
|
||||
/// <summary>
|
||||
/// Number of bytes to reserve at the beginning of each buffer.
|
||||
/// </summary>
|
||||
private int _reserve;
|
||||
/// <summary>
|
||||
/// NetworkManager this is for.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
/// <summary>
|
||||
/// Packet bundle to use for last enqueued data.
|
||||
/// </summary>
|
||||
private PacketBundle _sendLastBundle;
|
||||
/// <summary>
|
||||
/// True if being used as an sendLast bundle.
|
||||
/// </summary>
|
||||
private bool _isSendLastBundle;
|
||||
|
||||
internal PacketBundle(NetworkManager manager, int mtu, int reserve = 0, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
_isSendLastBundle = orderType == DataOrderType.Last;
|
||||
// If this is not the send last packetbundle then make a new one.
|
||||
if (!_isSendLastBundle)
|
||||
_sendLastBundle = new(manager, mtu, reserve, DataOrderType.Last);
|
||||
|
||||
_networkManager = manager;
|
||||
_maximumTransportUnit = mtu;
|
||||
/* Allow bytes for the tick.
|
||||
* Modify reserve after making sendLast bundle
|
||||
* so that the wrong reserve is not passed into
|
||||
* the sendLast bundle. */
|
||||
reserve += TransportManager.UNPACKED_TICK_LENGTH;
|
||||
_reserve = reserve;
|
||||
// Add buffer requires the right reserve so call after setting.
|
||||
AddBuffer();
|
||||
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _buffers.Count; i++)
|
||||
_buffers[i].Dispose();
|
||||
|
||||
_sendLastBundle?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a buffer using current settings.
|
||||
/// </summary>
|
||||
private ByteBuffer AddBuffer()
|
||||
{
|
||||
ByteBuffer ba = new(_maximumTransportUnit, _reserve);
|
||||
_buffers.Add(ba);
|
||||
return ba;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets using current settings.
|
||||
/// </summary>
|
||||
internal void Reset(bool resetSendLast)
|
||||
{
|
||||
_bufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < _buffers.Count; i++)
|
||||
_buffers[i].Reset();
|
||||
|
||||
if (resetSendLast)
|
||||
_sendLastBundle.Reset(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a segment to this packet bundle using the current WriteIndex.
|
||||
/// </summary>
|
||||
/// <param name = "forceNewBuffer">True to force data into a new buffer.</param>
|
||||
internal void Write(ArraySegment<byte> segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
/* If not the send last bundle and to send data last
|
||||
* then send using the send last bundle. */
|
||||
if (!_isSendLastBundle && orderType == DataOrderType.Last)
|
||||
{
|
||||
_sendLastBundle.Write(segment, forceNewBuffer, orderType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing to be written.
|
||||
if (segment.Count == 0)
|
||||
return;
|
||||
|
||||
/* If the segment count is larger than the mtu then
|
||||
* something went wrong. Nothing should call this method
|
||||
* directly except the TransportManager, which will automatically
|
||||
* split packets that exceed MTU into reliable ordered. */
|
||||
if (segment.Count > _maximumTransportUnit)
|
||||
{
|
||||
_networkManager.LogError($"Segment is length of {segment.Count} while MTU is {_maximumTransportUnit}. Packet was not split properly and will not be sent.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ByteBuffer ba = _buffers[_bufferIndex];
|
||||
/* Make a new buffer if...
|
||||
* forcing a new buffer and data has already been written to the current.
|
||||
* or---
|
||||
* segment.Count is more than what is remaining in the buffer. */
|
||||
bool useNewBuffer = (forceNewBuffer && ba.Length > _reserve) || segment.Count > ba.Remaining;
|
||||
if (useNewBuffer)
|
||||
{
|
||||
_bufferIndex++;
|
||||
// If need to make a new buffer then do so.
|
||||
if (_buffers.Count <= _bufferIndex)
|
||||
{
|
||||
ba = AddBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
ba = _buffers[_bufferIndex];
|
||||
ba.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
uint tick = _networkManager.TimeManager.LocalTick;
|
||||
ba.CopySegment(tick, segment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the packetBundle for send last.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal PacketBundle GetSendLastBundle() => _sendLastBundle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a buffer for the specified index. Returns true and outputs the buffer if it was successfully found.
|
||||
/// </summary>
|
||||
/// <param name = "index">Index of the buffer to retrieve.</param>
|
||||
/// <param name = "bb">Buffer retrieved from the list. Null if the specified buffer was not found.</param>
|
||||
internal bool GetBuffer(int index, out ByteBuffer bb)
|
||||
{
|
||||
bb = null;
|
||||
|
||||
if (index >= _buffers.Count || index < 0)
|
||||
{
|
||||
_networkManager.LogError($"Index of {index} is out of bounds. There are {_buffers.Count} available.");
|
||||
return false;
|
||||
}
|
||||
if (index > _bufferIndex)
|
||||
{
|
||||
_networkManager.LogError($"Index of {index} exceeds the number of written buffers. There are {WrittenBuffers} written buffers.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bb = _buffers[index];
|
||||
return bb.HasData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
|
||||
/// </summary>
|
||||
/// <param name = "channel"></param>
|
||||
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
|
||||
internal static bool GetPacketBundle(int channel, List<PacketBundle> bundles, out PacketBundle mtuBuffer)
|
||||
{
|
||||
// Out of bounds.
|
||||
if (channel >= bundles.Count)
|
||||
{
|
||||
mtuBuffer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
mtuBuffer = bundles[channel];
|
||||
return mtuBuffer.HasData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb2f3ce9b5ac27f40b7daa9364fb4d60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/Buffer.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,216 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Managing.Timing
|
||||
{
|
||||
public class EstimatedTick
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// How to handle old ticks, specifically related to EstimatedTick.
|
||||
/// </summary>
|
||||
public enum OldTickOption : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Completely ignore old ticks.
|
||||
/// </summary>
|
||||
Discard = 0,
|
||||
/// <summary>
|
||||
/// Set LastRemoteTick but do not update RemoteTick.
|
||||
/// </summary>
|
||||
SetLastRemoteTick = 1,
|
||||
/// <summary>
|
||||
/// Set LastRemoteTick and RemoteTick.
|
||||
/// </summary>
|
||||
SetRemoteTick = 2
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Local tick when this was last updated.
|
||||
/// </summary>
|
||||
public uint LocalTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Last remote tick this was updated with that was not out of order or a duplicate.
|
||||
/// </summary>
|
||||
public uint RemoteTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Last remote tick received regardless if it was out of order or a duplicate.
|
||||
/// </summary>
|
||||
public uint LastRemoteTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// True if LastRemoteTick is equal to RemoteTick.
|
||||
/// This would indicate that the LastRemoteTick did not arrive out of order.
|
||||
/// </summary>
|
||||
public bool IsLastRemoteTickOrdered => LastRemoteTick == RemoteTick;
|
||||
/// <summary>
|
||||
/// True if value is unset.
|
||||
/// </summary>
|
||||
// Only need to check one value for unset as they all would be if not set.
|
||||
public bool IsUnset => LocalTick == TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Last TimeManager specified during an Update call.
|
||||
/// </summary>
|
||||
private TimeManager _updateTimeManager;
|
||||
/// <summary>
|
||||
/// LocalTick when Value was last reset.
|
||||
/// </summary>
|
||||
private uint _valueLocalTick = TimeManager.UNSET_TICK;
|
||||
|
||||
/// <summary>
|
||||
/// Number of ticks LocalTick is being current LocalTick.
|
||||
/// </summary>
|
||||
public uint LocalTickDifference(TimeManager tm = null)
|
||||
{
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return TimeManager.UNSET_TICK;
|
||||
|
||||
long value = tm.LocalTick - LocalTick;
|
||||
// Shouldn't be possible to be less than 0.
|
||||
if (value < 0)
|
||||
return TimeManager.UNSET_TICK;
|
||||
else if (value > uint.MaxValue)
|
||||
value = uint.MaxValue;
|
||||
|
||||
return (uint)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if values were updated this tick.
|
||||
/// </summary>
|
||||
public bool IsCurrent(TimeManager tm = null)
|
||||
{
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return false;
|
||||
|
||||
return !IsUnset && LocalTick == tm.LocalTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current estimated value.
|
||||
/// </summary>
|
||||
/// <param name = "nm">NetworkManager to use. When null default value will be returned.</param>
|
||||
public uint Value(TimeManager tm = null)
|
||||
{
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return TimeManager.UNSET_TICK;
|
||||
|
||||
return Value(out _, tm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current estimated value. Outputs if value is current.
|
||||
/// </summary>
|
||||
/// <param name = "nm">NetworkManager to use. When null default value will be returned.</param>
|
||||
/// <param name = "isCurrent">True if the value was updated this local tick.</param>
|
||||
public uint Value(out bool isCurrent, TimeManager tm = null)
|
||||
{
|
||||
// Default value.
|
||||
isCurrent = false;
|
||||
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return TimeManager.UNSET_TICK;
|
||||
if (IsUnset)
|
||||
return TimeManager.UNSET_TICK;
|
||||
|
||||
isCurrent = IsCurrent(tm);
|
||||
|
||||
uint diff = tm.LocalTick - _valueLocalTick;
|
||||
return diff + RemoteTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this EstimatedTick with values.
|
||||
/// </summary>
|
||||
public void Initialize(TimeManager tm, uint remoteTick = 0, uint lastRemoteTick = 0, uint localTick = 0)
|
||||
{
|
||||
_updateTimeManager = tm;
|
||||
RemoteTick = remoteTick;
|
||||
LastRemoteTick = lastRemoteTick;
|
||||
LocalTick = localTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates values.
|
||||
/// </summary>
|
||||
/// <param name = "tm">TimeManager to use.</param>
|
||||
/// <param name = "remoteTick">Remote tick being updated.</param>
|
||||
/// <param name = "oldTickOption">How to handle remoteTick if it is old.</param>
|
||||
/// ///
|
||||
/// <param name = "resetValue">True to reset Value based on this information. False will allow Value to continue to to estimate tick based on the last reset.</param>
|
||||
/// <returns>True if was able to update values.</returns>
|
||||
public bool Update(TimeManager tm, uint remoteTick, OldTickOption oldTickOption = OldTickOption.Discard, bool resetValue = true)
|
||||
{
|
||||
_updateTimeManager = tm;
|
||||
// Always set LastRemoteTick even if out of order.
|
||||
LastRemoteTick = remoteTick;
|
||||
// If cannot update with old values return.
|
||||
if (oldTickOption != OldTickOption.SetRemoteTick && remoteTick <= RemoteTick)
|
||||
return false;
|
||||
|
||||
// nm is assumed set here.
|
||||
LocalTick = tm.LocalTick;
|
||||
if (resetValue)
|
||||
_valueLocalTick = LocalTick;
|
||||
RemoteTick = remoteTick;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates values.
|
||||
/// </summary>
|
||||
/// <param name = "remoteTick">Remote tick being updated.</param>
|
||||
/// <param name = "oldTickOption">How to handle remoteTick if it is old.</param>
|
||||
/// <param name = "resetValue">True to reset Value based on this information. False will allow Value to continue to to estimate tick based on the last reset.</param>
|
||||
/// <returns>True if was able to update values.</returns>
|
||||
public bool Update(uint remoteTick, OldTickOption oldTickOption = OldTickOption.Discard, bool resetValue = true)
|
||||
{
|
||||
TimeManager tm = null;
|
||||
if (!TryAssignTimeManager(ref tm))
|
||||
return false;
|
||||
|
||||
return Update(tm, remoteTick, oldTickOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates Value based on current ticks.
|
||||
/// This is typically used when you want to control when Value is reset through the Update methods.
|
||||
/// </summary>
|
||||
public void UpdateValue()
|
||||
{
|
||||
_valueLocalTick = LocalTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a TimeManager reference to UpdateTimeManager if was null.
|
||||
/// </summary>
|
||||
/// <returns>True if the reference has value or was assigned value. False if the reference remains null.</returns>
|
||||
private bool TryAssignTimeManager(ref TimeManager tm)
|
||||
{
|
||||
if (tm == null)
|
||||
tm = _updateTimeManager;
|
||||
|
||||
return tm != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values to unset and clears the NetworkManager.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
ResetTicks();
|
||||
_updateTimeManager = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets only tick values, leaving type references.
|
||||
/// </summary>
|
||||
public void ResetTicks()
|
||||
{
|
||||
LocalTick = TimeManager.UNSET_TICK;
|
||||
RemoteTick = TimeManager.UNSET_TICK;
|
||||
LastRemoteTick = TimeManager.UNSET_TICK;
|
||||
_valueLocalTick = TimeManager.UNSET_TICK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36df408e03d0cab4ab728897541c2bf2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/EstimatedTick.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,108 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// PacketBundles to send to this connection. An entry will be made for each channel.
|
||||
/// </summary>
|
||||
private List<PacketBundle> _toClientBundles = new();
|
||||
/// <summary>
|
||||
/// True if this object has been dirtied.
|
||||
/// </summary>
|
||||
private bool _serverDirtied;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script.
|
||||
/// </summary>
|
||||
private void InitializeBuffer()
|
||||
{
|
||||
for (byte i = 0; i < TransportManager.CHANNEL_COUNT; i++)
|
||||
{
|
||||
int mtu = NetworkManager.TransportManager.GetLowestMTU(i);
|
||||
_toClientBundles.Add(new(NetworkManager, mtu));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to this connection.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!IsActive)
|
||||
NetworkManager.LogError($"Connection is not valid, cannot send broadcast.");
|
||||
else
|
||||
NetworkManager.ServerManager.Broadcast(this, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data from the server to a client.
|
||||
/// </summary>
|
||||
/// <param name = "forceNewBuffer">True to force data into a new buffer.</param>
|
||||
internal void SendToClient(byte channel, ArraySegment<byte> segment, bool forceNewBuffer = false, DataOrderType orderType = DataOrderType.Default)
|
||||
{
|
||||
// Cannot send data when disconnecting.
|
||||
if (Disconnecting)
|
||||
return;
|
||||
|
||||
if (!IsActive)
|
||||
{
|
||||
NetworkManager.LogWarning($"Data cannot be sent to connection {ClientId} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If channel is out of bounds then default to the first channel.
|
||||
if (channel >= _toClientBundles.Count)
|
||||
channel = 0;
|
||||
|
||||
_toClientBundles[channel].Write(segment, forceNewBuffer, orderType);
|
||||
ServerDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a PacketBundle for a channel. ResetPackets must be called afterwards.
|
||||
/// </summary>
|
||||
/// <param name = "channel"></param>
|
||||
/// <returns>True if PacketBundle is valid on the index and contains data.</returns>
|
||||
internal bool GetPacketBundle(int channel, out PacketBundle packetBundle)
|
||||
{
|
||||
return PacketBundle.GetPacketBundle(channel, _toClientBundles, out packetBundle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the server has data to send to this connection.
|
||||
/// </summary>
|
||||
private void ServerDirty()
|
||||
{
|
||||
bool wasDirty = _serverDirtied;
|
||||
_serverDirtied = true;
|
||||
|
||||
// If not yet dirty then tell transport manager this is dirty.
|
||||
if (!wasDirty)
|
||||
NetworkManager.TransportManager.ServerDirty(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets that there is data to send.
|
||||
/// </summary>
|
||||
internal void ResetServerDirty()
|
||||
{
|
||||
_serverDirtied = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bc17ee5bac499347a6fbad9dd24b7b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/NetworkConnection.Buffer.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,92 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Managing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Current GridEntry this connection is in.
|
||||
/// </summary>
|
||||
internal GridEntry HashGridEntry = HashGrid.EmptyGridEntry;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// HashGrid for the NetworkManager on this connection.
|
||||
/// </summary>
|
||||
private HashGrid _hashGrid;
|
||||
/// <summary>
|
||||
/// Last unscaled time the HashGrid position was updated with this connections Objects.
|
||||
/// </summary>
|
||||
private float _nextHashGridUpdateTime;
|
||||
/// <summary>
|
||||
/// Current GridPosition this connection is in.
|
||||
/// </summary>
|
||||
private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when the FirstObject changes for this connection.
|
||||
/// </summary>
|
||||
private void Observers_FirstObjectChanged()
|
||||
{
|
||||
UpdateHashGridPositions(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this for use.
|
||||
/// </summary>
|
||||
private void Observers_Initialize(NetworkManager nm)
|
||||
{
|
||||
nm.TryGetInstance(out _hashGrid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the HashGridPosition value for FirstObject.
|
||||
/// </summary>
|
||||
internal void UpdateHashGridPositions(bool force)
|
||||
{
|
||||
if (_hashGrid == null)
|
||||
return;
|
||||
|
||||
float unscaledTime = Time.unscaledTime;
|
||||
// Not enough time has passed to update.
|
||||
if (!force && unscaledTime < _nextHashGridUpdateTime)
|
||||
return;
|
||||
|
||||
const float updateInterval = 1f;
|
||||
_nextHashGridUpdateTime = unscaledTime + updateInterval;
|
||||
|
||||
if (FirstObject == null)
|
||||
{
|
||||
HashGridEntry = HashGrid.EmptyGridEntry;
|
||||
_hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2Int newPosition = _hashGrid.GetHashGridPosition(FirstObject);
|
||||
if (newPosition != _hashGridPosition)
|
||||
{
|
||||
_hashGridPosition = newPosition;
|
||||
HashGridEntry = _hashGrid.GetGridEntry(newPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values.
|
||||
/// </summary>
|
||||
private void Observers_Reset()
|
||||
{
|
||||
_hashGrid = null;
|
||||
_hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
_nextHashGridUpdateTime = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1d1fe830d38ad043bd5e616cf6386ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/NetworkConnection.Observers.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,102 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#pragma warning disable CS0414
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Last tick this connection sent a ping.
|
||||
/// </summary>
|
||||
private uint _lastPingTick;
|
||||
///// <summary>
|
||||
///// Number of times client has excessively sent a ping.
|
||||
///// </summary>
|
||||
// private float _excessivePingCount;
|
||||
/// <summary>
|
||||
/// Ticks expected between each ping.
|
||||
/// </summary>
|
||||
private uint _requiredPingTicks;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Number of times a ping may occur excessively before server will punish connection.
|
||||
/// </summary>
|
||||
private const byte EXCESSIVE_PING_LIMIT = 10;
|
||||
#endregion
|
||||
|
||||
#pragma warning restore CS0414
|
||||
/// <summary>
|
||||
/// Initializes for ping.
|
||||
/// </summary>
|
||||
private void InitializePing()
|
||||
{
|
||||
// Give the client some room for error.
|
||||
float requiredInterval = NetworkManager.TimeManager.PingInterval * 0.85f;
|
||||
// Round down so required ticks is lower.
|
||||
_requiredPingTicks = NetworkManager.TimeManager.TimeToTicks(requiredInterval, TickRounding.RoundDown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets PingPong values.
|
||||
/// </summary>
|
||||
private void ResetPingPong()
|
||||
{
|
||||
// _excessivePingCount = 0;
|
||||
_lastPingTick = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a ping is received from this connection. Returns if can respond to ping.
|
||||
/// </summary>
|
||||
/// <returns>True to respond to ping, false to kick connection.</returns>
|
||||
internal bool CanPingPong()
|
||||
{
|
||||
/* Only check ping conditions in build. Editors are prone to pausing which can
|
||||
* improperly kick clients. */
|
||||
TimeManager tm = NetworkManager == null ? InstanceFinder.TimeManager : NetworkManager.TimeManager;
|
||||
/* Server FPS is running low, timing isn't reliable enough to kick clients.
|
||||
* Respond with clients ping and remove infractions just in case the
|
||||
* client received some from other server instabilities. */
|
||||
if (tm.HasMultipleTicksOccurred(timeSinceMultipleTicks: 1f))
|
||||
{
|
||||
// _excessivePingCount = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint currentTick = tm.Tick;
|
||||
uint difference = currentTick - _lastPingTick;
|
||||
_lastPingTick = currentTick;
|
||||
|
||||
// Ping sent too quickly.
|
||||
if (difference < _requiredPingTicks)
|
||||
{
|
||||
// _excessivePingCount += 1f;
|
||||
////Ping limit hit.
|
||||
// if (_excessivePingCount >= EXCESSIVE_PING_LIMIT)
|
||||
// {
|
||||
// NetworkManager.LogWarning($"Kicked connectionId {ClientId} for excessive pings.");
|
||||
// Disconnect(true);
|
||||
// }
|
||||
|
||||
// Return to not send pong back.
|
||||
return false;
|
||||
}
|
||||
// Ping isnt too fast.
|
||||
else
|
||||
{
|
||||
// _excessivePingCount = UnityEngine.Mathf.Max(0f, _excessivePingCount - 0.5f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20633bf6995f6534ba2b27e1eab3054d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/NetworkConnection.PingPong.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,114 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Approximate replicate tick on the server for this connection.
|
||||
/// This also contains the last set value for local and remote.
|
||||
/// </summary>
|
||||
public EstimatedTick ReplicateTick { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// Writers for states.
|
||||
/// </summary>
|
||||
internal List<PooledWriter> PredictionStateWriters = new();
|
||||
internal void Prediction_Initialize(NetworkManager manager, bool asServer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Writes a prediction state.
|
||||
/// </summary>
|
||||
/// <param name = "data"></param>
|
||||
internal void WriteState(PooledWriter data)
|
||||
{
|
||||
#if !DEVELOPMENT
|
||||
// Do not send states to clientHost.
|
||||
if (IsLocalClient)
|
||||
return;
|
||||
#endif
|
||||
|
||||
TimeManager timeManager = NetworkManager.TimeManager;
|
||||
TransportManager transportManager = NetworkManager.TransportManager;
|
||||
uint ticksBehind = IsLocalClient ? 0 : PacketTick.LocalTickDifference(timeManager);
|
||||
/* If it's been a really long while the client could just be setting up
|
||||
* or dropping. Only send if they've communicated within 5 seconds. */
|
||||
if (ticksBehind > timeManager.TickRate * 5)
|
||||
return;
|
||||
|
||||
int mtu = transportManager.GetLowestMTU((byte)Channel.Unreliable);
|
||||
PooledWriter stateWriter;
|
||||
int writerCount = PredictionStateWriters.Count;
|
||||
/* Conditions to create a new writer are:
|
||||
* - writer does not exist yet.
|
||||
* - data length + currentWriter length > mtu */
|
||||
Channel channel = Channel.Unreliable;
|
||||
if (writerCount > 0)
|
||||
transportManager.CheckSetReliableChannel(data.Length + PredictionStateWriters[writerCount - 1].Length, ref channel);
|
||||
/* If no writers or if channel would be forced reliable.
|
||||
*
|
||||
* By checking if channel would be reliable this is
|
||||
* essentially asking if (current written + new data) would
|
||||
* exceed mtu. When it would get a new writer to try
|
||||
* and favor unreliable. Emphasis on try, because if some
|
||||
* really unlikely chance the data was really large it would
|
||||
* still send on reliable down the line. */
|
||||
if (writerCount == 0 || channel == Channel.Reliable)
|
||||
{
|
||||
stateWriter = WriterPool.Retrieve(mtu);
|
||||
PredictionStateWriters.Add(stateWriter);
|
||||
stateWriter.Skip(PredictionManager.STATE_HEADER_RESERVE_LENGTH);
|
||||
/// 2 PacketId.
|
||||
/// 4 Last replicate tick run for connection.
|
||||
/// 4 Length unpacked.
|
||||
}
|
||||
else
|
||||
{
|
||||
stateWriter = PredictionStateWriters[writerCount - 1];
|
||||
}
|
||||
|
||||
stateWriter.WriteArraySegment(data.GetArraySegment());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores prediction writers to be re-used later.
|
||||
/// </summary>
|
||||
internal void StorePredictionStateWriters()
|
||||
{
|
||||
for (int i = 0; i < PredictionStateWriters.Count; i++)
|
||||
WriterPool.Store(PredictionStateWriters[i]);
|
||||
|
||||
PredictionStateWriters.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the last tick a NetworkBehaviour replicated with.
|
||||
/// </summary>
|
||||
/// <param name = "setUnordered">True to set unordered value, false to set ordered.</param>
|
||||
internal void SetReplicateTick(uint value, EstimatedTick.OldTickOption oldTickOption = EstimatedTick.OldTickOption.Discard)
|
||||
{
|
||||
ReplicateTick.Update(value, oldTickOption);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets NetworkConnection.
|
||||
/// </summary>
|
||||
private void Prediction_Reset()
|
||||
{
|
||||
StorePredictionStateWriters();
|
||||
ReplicateTick.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0068eb93a417a44b9151b61a08d8547
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/NetworkConnection.Prediction.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,81 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Serializing;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Returns true if this connection is a clientHost.
|
||||
/// </summary>
|
||||
public bool IsHost => NetworkManager == null ? false : NetworkManager.IsServerStarted && this == NetworkManager.ClientManager.Connection;
|
||||
/// <summary>
|
||||
/// Returns if this connection is for the local client.
|
||||
/// </summary>
|
||||
public bool IsLocalClient => NetworkManager == null ? false : NetworkManager.ClientManager.Connection == this;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns the address of this connection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAddress()
|
||||
{
|
||||
if (!IsValid)
|
||||
return string.Empty;
|
||||
if (NetworkManager == null)
|
||||
return string.Empty;
|
||||
|
||||
return NetworkManager.TransportManager.Transport.GetConnectionAddress(ClientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
if (CanKick())
|
||||
NetworkManager.ServerManager.Kick(this, kickReason, loggingType, log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "reader">Reader to clear before kicking.</param>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
if (CanKick())
|
||||
NetworkManager.ServerManager.Kick(this, reader, kickReason, loggingType, log);
|
||||
}
|
||||
|
||||
private bool CanKick()
|
||||
{
|
||||
// Connection isn't valid, calling kick on an empty connection.
|
||||
if (!IsValid)
|
||||
return false;
|
||||
|
||||
// Should never happen.
|
||||
if (NetworkManager == null)
|
||||
{
|
||||
NetworkManager = InstanceFinder.NetworkManager;
|
||||
NetworkManager.LogError($"NetworkManager was not set for connection {ToString()}. InstanceFinder has been used.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d45abc242399194b85e6c16bcb3676b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/NetworkConnection.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,487 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Transporting;
|
||||
using UnityEngine.SceneManagement;
|
||||
using static FishNet.Managing.Timing.EstimatedTick;
|
||||
|
||||
namespace FishNet.Connection
|
||||
{
|
||||
public static class NetworkConnectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
|
||||
/// Null references can be used with this method.
|
||||
/// </summary>
|
||||
public static bool IsValid(this NetworkConnection c)
|
||||
{
|
||||
if (c == null)
|
||||
return false;
|
||||
|
||||
return c.IsValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction of a NetworkConnection's broadcasting duties
|
||||
/// </summary>
|
||||
public interface INetworkConnectionBroadcaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a broadcast to this connection.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A container for a connected client used to perform actions on and gather information for the declared client.
|
||||
/// </summary>
|
||||
public partial class NetworkConnection : IResettable, IEquatable<NetworkConnection>, INetworkConnectionBroadcaster
|
||||
{
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Tick when Disconnecting was set.
|
||||
/// </summary>
|
||||
internal uint DisconnectingTick { get; private set; }
|
||||
/// <summary>
|
||||
/// ObjectIds to use for predicted spawning.
|
||||
/// </summary>
|
||||
internal Queue<int> PredictedObjectIds = new();
|
||||
/// <summary>
|
||||
/// True if the client has sent the same version that the server is on.
|
||||
/// </summary>
|
||||
internal bool HasSentVersion;
|
||||
/// <summary>
|
||||
/// LocalTick of the server when this connection was established. This value is not set for clients.
|
||||
/// </summary>
|
||||
internal uint ServerConnectionTick;
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server.
|
||||
/// </summary>
|
||||
public event Action<NetworkConnection, bool> OnLoadedStartScenes;
|
||||
/// <summary>
|
||||
/// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server.
|
||||
/// </summary>
|
||||
public event Action<NetworkObject> OnObjectAdded;
|
||||
/// <summary>
|
||||
/// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server.
|
||||
/// </summary>
|
||||
public event Action<NetworkObject> OnObjectRemoved;
|
||||
/// <summary>
|
||||
/// NetworkManager managing this class.
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if connection has loaded start scenes. Available to this connection and server.
|
||||
/// </summary>
|
||||
public bool LoadedStartScenes() => _loadedStartScenesAsServer || _loadedStartScenesAsClient;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public bool LoadedStartScenes(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
return _loadedStartScenesAsServer;
|
||||
else
|
||||
return _loadedStartScenesAsClient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TransportIndex this connection is on.
|
||||
/// For security reasons this value will be unset on clients if this is not their connection.
|
||||
/// This is not yet used.
|
||||
/// </summary>
|
||||
public int TransportIndex { get; private set; } = -1;
|
||||
/// <summary>
|
||||
/// True if this connection is authenticated. Only available to server.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated { get; private set; }
|
||||
[Obsolete("Use IsAuthenticated.")] // Remove in V5
|
||||
public bool Authenticated
|
||||
{
|
||||
get => IsAuthenticated;
|
||||
set => IsAuthenticated = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// True if this connection IsValid and not Disconnecting.
|
||||
/// </summary>
|
||||
public bool IsActive => ClientId >= 0 && !Disconnecting;
|
||||
/// <summary>
|
||||
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
|
||||
/// </summary>
|
||||
public bool IsValid => ClientId >= 0;
|
||||
/// <summary>
|
||||
/// Unique Id for this connection.
|
||||
/// </summary>
|
||||
public int ClientId = -1;
|
||||
/// <summary>
|
||||
/// Objects owned by this connection. Available to this connection and server.
|
||||
/// </summary>
|
||||
public HashSet<NetworkObject> Objects = new();
|
||||
/// <summary>
|
||||
/// The first object within Objects.
|
||||
/// </summary>
|
||||
public NetworkObject FirstObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom FirstObject. This connection must be owner of the specified object.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
public void SetFirstObject(NetworkObject nob)
|
||||
{
|
||||
// Invalid object.
|
||||
if (!Objects.Contains(nob))
|
||||
{
|
||||
string errMessage = $"FirstObject for {ClientId} cannot be set to {nob.name} as it's not within Objects for this connection.";
|
||||
NetworkManager.LogError(errMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
FirstObject = nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scenes this connection is in. Available to this connection and server.
|
||||
/// </summary>
|
||||
public HashSet<Scene> Scenes { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// True if this connection is being disconnected. Only available to server.
|
||||
/// </summary>
|
||||
public bool Disconnecting { get; private set; }
|
||||
/// <summary>
|
||||
/// Custom data associated with this connection which may be modified by the user.
|
||||
/// The value of this field are not synchronized over the network.
|
||||
/// </summary>
|
||||
public object CustomData = null;
|
||||
/// <summary>
|
||||
/// Tick of the last packet received from this connection which was not out of order.
|
||||
/// This value is only available on the server.
|
||||
/// </summary>
|
||||
public EstimatedTick PacketTick { get; private set; } = new();
|
||||
/// <summary>
|
||||
/// Approximate local tick as it is on this connection.
|
||||
/// This also contains the last set value for local and remote.
|
||||
/// </summary>
|
||||
public EstimatedTick LocalTick { get; private set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if loaded start scenes as server.
|
||||
/// </summary>
|
||||
private bool _loadedStartScenesAsServer;
|
||||
/// <summary>
|
||||
/// True if loaded start scenes as client.
|
||||
/// </summary>
|
||||
private bool _loadedStartScenesAsClient;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Value used when ClientId has not been set.
|
||||
/// </summary>
|
||||
public const int UNSET_CLIENTID_VALUE = -1;
|
||||
/// <summary>
|
||||
/// Maximum value a ClientId can be.
|
||||
/// </summary>
|
||||
public const int MAXIMUM_CLIENTID_VALUE = int.MaxValue;
|
||||
/// <summary>
|
||||
/// Maximum value a ClientId can be excluding simulated value.
|
||||
/// </summary>
|
||||
public const int MAXIMUM_CLIENTID_WITHOUT_SIMULATED_VALUE = int.MaxValue - 1;
|
||||
/// <summary>
|
||||
/// Value to use as a ClientId when simulating a local client without actually using a socket.
|
||||
/// </summary>
|
||||
public const int SIMULATED_CLIENTID_VALUE = int.MaxValue;
|
||||
/// <summary>
|
||||
/// Number of bytes to reserve for a connectionId if writing the value uncompressed.
|
||||
/// </summary>
|
||||
public const int CLIENTID_UNCOMPRESSED_RESERVE_LENGTH = 4;
|
||||
#endregion
|
||||
|
||||
#region Comparers.
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is NetworkConnection nc)
|
||||
return nc.ClientId == ClientId;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(NetworkConnection nc)
|
||||
{
|
||||
if (nc is null)
|
||||
return false;
|
||||
// If either is -1 Id.
|
||||
if (ClientId == UNSET_CLIENTID_VALUE || nc.ClientId == UNSET_CLIENTID_VALUE)
|
||||
return false;
|
||||
// Same object.
|
||||
if (ReferenceEquals(this, nc))
|
||||
return true;
|
||||
|
||||
return ClientId == nc.ClientId;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ClientId;
|
||||
}
|
||||
|
||||
public static bool operator ==(NetworkConnection a, NetworkConnection b)
|
||||
{
|
||||
if (a is null && b is null)
|
||||
return true;
|
||||
if (a is null && !(b is null))
|
||||
return false;
|
||||
|
||||
return b == null ? a.Equals(b) : b.Equals(a);
|
||||
}
|
||||
|
||||
public static bool operator !=(NetworkConnection a, NetworkConnection b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public NetworkConnection() { }
|
||||
|
||||
[APIExclude]
|
||||
public NetworkConnection(NetworkManager manager, int clientId, int transportIndex, bool asServer)
|
||||
{
|
||||
Initialize(manager, clientId, transportIndex, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs data about this connection as a string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
int clientId = ClientId;
|
||||
string ip = NetworkManager != null ? NetworkManager.TransportManager.Transport.GetConnectionAddress(clientId) : "Unset";
|
||||
return $"Id [{ClientId}] Address [{ip}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this for use.
|
||||
/// </summary>
|
||||
private void Initialize(NetworkManager nm, int clientId, int transportIndex, bool asServer)
|
||||
{
|
||||
NetworkManager = nm;
|
||||
LocalTick.Initialize(nm.TimeManager);
|
||||
PacketTick.Initialize(nm.TimeManager);
|
||||
if (asServer)
|
||||
ServerConnectionTick = nm.TimeManager.LocalTick;
|
||||
TransportIndex = transportIndex;
|
||||
ClientId = clientId;
|
||||
/* Set PacketTick to current values so
|
||||
* that timeouts and other things around
|
||||
* first packet do not occur due to an unset value. */
|
||||
PacketTick.Update(nm.TimeManager, 0, OldTickOption.SetLastRemoteTick);
|
||||
Observers_Initialize(nm);
|
||||
Prediction_Initialize(nm, asServer);
|
||||
// Only the server uses the ping and buffer.
|
||||
if (asServer)
|
||||
{
|
||||
InitializeBuffer();
|
||||
InitializePing();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Disconnecting boolean for this connection.
|
||||
/// </summary>
|
||||
internal void SetDisconnecting(bool value)
|
||||
{
|
||||
Disconnecting = value;
|
||||
if (Disconnecting)
|
||||
DisconnectingTick = NetworkManager.TimeManager.LocalTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects this connection.
|
||||
/// </summary>
|
||||
/// <param name = "immediately">True to disconnect immediately. False to send any pending data first.</param>
|
||||
public void Disconnect(bool immediately)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
NetworkManager.LogWarning($"Disconnect called on an invalid connection.");
|
||||
return;
|
||||
}
|
||||
if (Disconnecting)
|
||||
{
|
||||
NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting.");
|
||||
return;
|
||||
}
|
||||
|
||||
SetDisconnecting(true);
|
||||
// If immediately then force disconnect through transport.
|
||||
if (immediately)
|
||||
NetworkManager.TransportManager.Transport.StopConnection(ClientId, true);
|
||||
// Otherwise mark dirty so server will push out any pending information, and then disconnect.
|
||||
else
|
||||
ServerDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if just loaded start scenes and sets them as loaded if not.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool SetLoadedStartScenes(bool asServer)
|
||||
{
|
||||
bool loadedToCheck = asServer ? _loadedStartScenesAsServer : _loadedStartScenesAsClient;
|
||||
// Result becomes true if not yet loaded start scenes.
|
||||
bool result = !loadedToCheck;
|
||||
if (asServer)
|
||||
_loadedStartScenesAsServer = true;
|
||||
else
|
||||
_loadedStartScenesAsClient = true;
|
||||
|
||||
OnLoadedStartScenes?.Invoke(this, asServer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets connection as authenticated.
|
||||
/// </summary>
|
||||
internal void ConnectionAuthenticated()
|
||||
{
|
||||
IsAuthenticated = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds to Objects owned by this connection.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal void AddObject(NetworkObject nob)
|
||||
{
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
Objects.Add(nob);
|
||||
// If adding the first object then set new FirstObject.
|
||||
if (Objects.Count == 1)
|
||||
SetFirstObject();
|
||||
|
||||
OnObjectAdded?.Invoke(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes from Objects owned by this connection.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal void RemoveObject(NetworkObject nob)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
ClearObjects();
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.Remove(nob);
|
||||
// If removing the first object then set a new one.
|
||||
if (nob == FirstObject)
|
||||
SetFirstObject();
|
||||
|
||||
OnObjectRemoved?.Invoke(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all Objects.
|
||||
/// </summary>
|
||||
private void ClearObjects()
|
||||
{
|
||||
Objects.Clear();
|
||||
FirstObject = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets FirstObject using the first element in Objects.
|
||||
/// </summary>
|
||||
private void SetFirstObject()
|
||||
{
|
||||
if (Objects.Count == 0)
|
||||
{
|
||||
FirstObject = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject nob in Objects)
|
||||
{
|
||||
FirstObject = nob;
|
||||
Observers_FirstObjectChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a scene to this connections Scenes.
|
||||
/// </summary>
|
||||
/// <param name = "scene"></param>
|
||||
/// <returns></returns>
|
||||
internal bool AddToScene(Scene scene)
|
||||
{
|
||||
return Scenes.Add(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a scene to this connections Scenes.
|
||||
/// </summary>
|
||||
/// <param name = "scene"></param>
|
||||
/// <returns></returns>
|
||||
internal bool RemoveFromScene(Scene scene)
|
||||
{
|
||||
return Scenes.Remove(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all states for re-use.
|
||||
/// </summary>
|
||||
public void ResetState()
|
||||
{
|
||||
MatchCondition.RemoveFromMatchesWithoutRebuild(this, NetworkManager);
|
||||
|
||||
foreach (PacketBundle p in _toClientBundles)
|
||||
p.Dispose();
|
||||
_toClientBundles.Clear();
|
||||
|
||||
ServerConnectionTick = 0;
|
||||
PacketTick.Reset();
|
||||
LocalTick.Reset();
|
||||
TransportIndex = -1;
|
||||
ClientId = -1;
|
||||
ClearObjects();
|
||||
IsAuthenticated = false;
|
||||
HasSentVersion = false;
|
||||
NetworkManager = null;
|
||||
_loadedStartScenesAsClient = false;
|
||||
_loadedStartScenesAsServer = false;
|
||||
SetDisconnecting(false);
|
||||
Scenes.Clear();
|
||||
PredictedObjectIds.Clear();
|
||||
ResetPingPong();
|
||||
Observers_Reset();
|
||||
Prediction_Reset();
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01c7bfc71e29621408451fa2fa6b1a0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/NetworkConnection.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Managing.Timing { }
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 139cb2731a4787e47919ef19010d7953
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Connection/OldTickOption.cs
|
||||
uploadId: 866910
|
||||
Reference in New Issue
Block a user