using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Mirror
{
public class NetworkConnectionToClient : NetworkConnection
{
public override string address =>
Transport.active.ServerGetClientAddress(connectionId);
/// NetworkIdentities that this connection can see
// TODO move to server's NetworkConnectionToClient?
public new readonly HashSet observing = new HashSet();
[Obsolete(".clientOwnedObjects was renamed to .owned :)")] // 2022-10-13
public new HashSet clientOwnedObjects => owned;
// unbatcher
public Unbatcher unbatcher = new Unbatcher();
// server runs a time snapshot interpolation for each client's local time.
// this is necessary for client auth movement to still be smooth on the
// server for host mode.
// TODO move them along server's timeline in the future.
// perhaps with an offset.
// for now, keep compatibility by manually constructing a timeline.
ExponentialMovingAverage driftEma;
ExponentialMovingAverage deliveryTimeEma; // average delivery time (standard deviation gives average jitter)
public double remoteTimeline;
public double remoteTimescale;
double bufferTimeMultiplier = 2;
double bufferTime => NetworkServer.sendInterval * bufferTimeMultiplier;
//
readonly SortedList snapshots = new SortedList();
// Snapshot Buffer size limit to avoid ever growing list memory consumption attacks from clients.
public int snapshotBufferSizeLimit = 64;
public NetworkConnectionToClient(int networkConnectionId)
: base(networkConnectionId)
{
// initialize EMA with 'emaDuration' seconds worth of history.
// 1 second holds 'sendRate' worth of values.
// multiplied by emaDuration gives n-seconds.
driftEma = new ExponentialMovingAverage(NetworkServer.sendRate * NetworkClient.driftEmaDuration);
deliveryTimeEma = new ExponentialMovingAverage(NetworkServer.sendRate * NetworkClient.deliveryTimeEmaDuration);
// buffer limit should be at least multiplier to have enough in there
snapshotBufferSizeLimit = Mathf.Max((int)NetworkClient.bufferTimeMultiplier, snapshotBufferSizeLimit);
}
public void OnTimeSnapshot(TimeSnapshot snapshot)
{
// protect against ever growing buffer size attacks
if (snapshots.Count >= snapshotBufferSizeLimit) return;
// (optional) dynamic adjustment
if (NetworkClient.dynamicAdjustment)
{
// set bufferTime on the fly.
// shows in inspector for easier debugging :)
bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment(
NetworkServer.sendInterval,
deliveryTimeEma.StandardDeviation,
NetworkClient.dynamicAdjustmentTolerance
);
// Debug.Log($"[Server]: {name} delivery std={serverDeliveryTimeEma.StandardDeviation} bufferTimeMult := {bufferTimeMultiplier} ");
}
// insert into the server buffer & initialize / adjust / catchup
SnapshotInterpolation.InsertAndAdjust(
snapshots,
snapshot,
ref remoteTimeline,
ref remoteTimescale,
NetworkServer.sendInterval,
bufferTime,
NetworkClient.catchupSpeed,
NetworkClient.slowdownSpeed,
ref driftEma,
NetworkClient.catchupNegativeThreshold,
NetworkClient.catchupPositiveThreshold,
ref deliveryTimeEma
);
}
public void UpdateTimeInterpolation()
{
// timeline starts when the first snapshot arrives.
if (snapshots.Count > 0)
{
// progress local timeline.
SnapshotInterpolation.StepTime(Time.unscaledDeltaTime, ref remoteTimeline, remoteTimescale);
// progress local interpolation.
// TimeSnapshot doesn't interpolate anything.
// this is merely to keep removing older snapshots.
SnapshotInterpolation.StepInterpolation(snapshots, remoteTimeline, out _, out _, out _);
// Debug.Log($"NetworkClient SnapshotInterpolation @ {localTimeline:F2} t={t:F2}");
}
}
// Send stage three: hand off to transport
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void SendToTransport(ArraySegment segment, int channelId = Channels.Reliable) =>
Transport.active.ServerSend(connectionId, segment, channelId);
/// Disconnects this connection.
public override void Disconnect()
{
// set not ready and handle clientscene disconnect in any case
// (might be client or host mode here)
isReady = false;
Transport.active.ServerDisconnect(connectionId);
// IMPORTANT: NetworkConnection.Disconnect() is NOT called for
// voluntary disconnects from the other end.
// -> so all 'on disconnect' cleanup code needs to be in
// OnTransportDisconnect, where it's called for both voluntary
// and involuntary disconnects!
}
internal void AddToObserving(NetworkIdentity netIdentity)
{
observing.Add(netIdentity);
// spawn identity for this conn
NetworkServer.ShowForConnection(netIdentity, this);
}
internal void RemoveFromObserving(NetworkIdentity netIdentity, bool isDestroyed)
{
observing.Remove(netIdentity);
if (!isDestroyed)
{
// hide identity for this conn
NetworkServer.HideForConnection(netIdentity, this);
}
}
internal void RemoveFromObservingsObservers()
{
foreach (NetworkIdentity netIdentity in observing)
{
netIdentity.RemoveObserver(this);
}
observing.Clear();
}
internal void AddOwnedObject(NetworkIdentity obj)
{
owned.Add(obj);
}
internal void RemoveOwnedObject(NetworkIdentity obj)
{
owned.Remove(obj);
}
internal void DestroyOwnedObjects()
{
// create a copy because the list might be modified when destroying
HashSet tmp = new HashSet(owned);
foreach (NetworkIdentity netIdentity in tmp)
{
if (netIdentity != null)
{
NetworkServer.Destroy(netIdentity.gameObject);
}
}
// clear the hashset because we destroyed them all
owned.Clear();
}
}
}