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.
///
/// How many ticks between each LOD update.
///
public uint LevelOfDetailInterval => NetworkManager.TimeManager.TimeToTicks(0.5d, TickRounding.RoundUp);
#endregion
#region Private.
///
/// Positions of the player objects.
///
private List _objectsPositionsCache = new List();
///
/// Next index within Spawned to update the LOD on.
///
private int _nextLodNobIndex;
#endregion
///
/// Sends a level of update if conditions are met.
///
/// 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).
///
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 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 currentLods = Connection.LevelOfDetails;
List 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();
}
}
}