198 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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();
 | |
|         }
 | |
| 
 | |
| 
 | |
|     }
 | |
| 
 | |
| 
 | |
| }
 |