617 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			617 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Documenting;
 | |
| using FishNet.Managing.Logging;
 | |
| using FishNet.Object.Synchronizing.Internal;
 | |
| using FishNet.Serializing;
 | |
| using FishNet.Serializing.Helping;
 | |
| using FishNet.Utility.Performance;
 | |
| using System;
 | |
| using System.Collections;
 | |
| using System.Collections.Generic;
 | |
| using System.Runtime.CompilerServices;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Object.Synchronizing
 | |
| {
 | |
| 
 | |
|     public class SyncHashSet<T> : SyncBase, ISet<T>
 | |
|     {
 | |
|         #region Types.
 | |
|         /// <summary>
 | |
|         /// Information needed to invoke a callback.
 | |
|         /// </summary>
 | |
|         private struct CachedOnChange
 | |
|         {
 | |
|             internal readonly SyncHashSetOperation Operation;
 | |
|             internal readonly T Item;
 | |
| 
 | |
|             public CachedOnChange(SyncHashSetOperation operation, T item)
 | |
|             {
 | |
|                 Operation = operation;
 | |
|                 Item = item;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Information about how the collection has changed.
 | |
|         /// </summary>
 | |
|         private struct ChangeData
 | |
|         {
 | |
|             internal readonly SyncHashSetOperation Operation;
 | |
|             internal readonly T Item;
 | |
| 
 | |
|             public ChangeData(SyncHashSetOperation operation, T item)
 | |
|             {
 | |
|                 Operation = operation;
 | |
| 
 | |
|                 Item = item;
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Public.
 | |
|         /// <summary>
 | |
|         /// Implementation from List<T>. Not used.
 | |
|         /// </summary>
 | |
|         [APIExclude]
 | |
|         public bool IsReadOnly => false;
 | |
|         /// <summary>
 | |
|         /// Delegate signature for when SyncList changes.
 | |
|         /// </summary>
 | |
|         /// <param name="op">Type of change.</param>
 | |
|         /// <param name="item">Item which was modified.</param>
 | |
|         /// <param name="asServer">True if callback is occuring on the server.</param>
 | |
|         [APIExclude]
 | |
|         public delegate void SyncHashSetChanged(SyncHashSetOperation op, T item, bool asServer);
 | |
|         /// <summary>
 | |
|         /// Called when the SyncList changes.
 | |
|         /// </summary>
 | |
|         public event SyncHashSetChanged OnChange;
 | |
|         /// <summary>
 | |
|         /// Collection of objects.
 | |
|         /// </summary>
 | |
|         public readonly ISet<T> Collection;
 | |
|         /// <summary>
 | |
|         /// Copy of objects on client portion when acting as a host.
 | |
|         /// </summary>
 | |
|         public readonly ISet<T> ClientHostCollection = new HashSet<T>();
 | |
|         /// <summary>
 | |
|         /// Number of objects in the collection.
 | |
|         /// </summary>
 | |
|         public int Count => Collection.Count;
 | |
|         #endregion
 | |
| 
 | |
|         #region Private.        
 | |
|         /// <summary>
 | |
|         /// ListCache for comparing.
 | |
|         /// </summary>
 | |
|         private ListCache<T> _listCache;
 | |
|         /// <summary>
 | |
|         /// Values upon initialization.
 | |
|         /// </summary>
 | |
|         private ISet<T> _initialValues = new HashSet<T>();
 | |
|         /// <summary>
 | |
|         /// Comparer to see if entries change when calling public methods.
 | |
|         /// </summary>
 | |
|         private readonly IEqualityComparer<T> _comparer;
 | |
|         /// <summary>
 | |
|         /// Changed data which will be sent next tick.
 | |
|         /// </summary>
 | |
|         private readonly List<ChangeData> _changed = new List<ChangeData>();
 | |
|         /// <summary>
 | |
|         /// Server OnChange events waiting for start callbacks.
 | |
|         /// </summary>
 | |
|         private readonly List<CachedOnChange> _serverOnChanges = new List<CachedOnChange>();
 | |
|         /// <summary>
 | |
|         /// Client OnChange events waiting for start callbacks.
 | |
|         /// </summary>
 | |
|         private readonly List<CachedOnChange> _clientOnChanges = new List<CachedOnChange>();
 | |
|         /// <summary>
 | |
|         /// True if values have changed since initialization.
 | |
|         /// The only reasonable way to reset this during a Reset call is by duplicating the original list and setting all values to it on reset.
 | |
|         /// </summary>
 | |
|         private bool _valuesChanged;
 | |
|         /// <summary>
 | |
|         /// True to send all values in the next WriteDelta.
 | |
|         /// </summary>
 | |
|         private bool _sendAll;
 | |
|         #endregion
 | |
| 
 | |
|         [APIExclude]
 | |
|         public SyncHashSet() : this(new HashSet<T>(), EqualityComparer<T>.Default) { }
 | |
|         [APIExclude]
 | |
|         public SyncHashSet(IEqualityComparer<T> comparer) : this(new HashSet<T>(), (comparer == null) ? EqualityComparer<T>.Default : comparer) { }
 | |
|         [APIExclude]
 | |
|         public SyncHashSet(ISet<T> collection, IEqualityComparer<T> comparer = null)
 | |
|         {
 | |
|             this._comparer = (comparer == null) ? EqualityComparer<T>.Default : comparer;
 | |
|             this.Collection = collection;
 | |
|             //Add each in collection to clienthostcollection.
 | |
|             foreach (T item in collection)
 | |
|                 ClientHostCollection.Add(item);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when the SyncType has been registered, but not yet initialized over the network.
 | |
|         /// </summary>
 | |
|         protected override void Registered()
 | |
|         {
 | |
|             base.Registered();
 | |
|             foreach (T item in Collection)
 | |
|                 _initialValues.Add(item);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets the collection being used within this SyncList.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         public HashSet<T> GetCollection(bool asServer)
 | |
|         {
 | |
|             bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
 | |
|             ISet<T> collection = (asClientAndHost) ? ClientHostCollection : Collection;
 | |
|             return (collection as HashSet<T>);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds an operation and invokes locally.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void AddOperation(SyncHashSetOperation operation, T item)
 | |
|         {
 | |
|             if (!base.IsRegistered)
 | |
|                 return;
 | |
| 
 | |
|             bool asServerInvoke = (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServer);
 | |
| 
 | |
|             if (asServerInvoke)
 | |
|             {
 | |
|                 _valuesChanged = true;
 | |
|                 if (base.Dirty())
 | |
|                 {
 | |
|                     ChangeData change = new ChangeData(operation, item);
 | |
|                     _changed.Add(change);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             InvokeOnChange(operation, item, asServerInvoke);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called after OnStartXXXX has occurred.
 | |
|         /// </summary>
 | |
|         /// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
 | |
|         public override void OnStartCallback(bool asServer)
 | |
|         {
 | |
|             base.OnStartCallback(asServer);
 | |
|             List<CachedOnChange> collection = (asServer) ? _serverOnChanges : _clientOnChanges;
 | |
|             if (OnChange != null)
 | |
|             {
 | |
|                 foreach (CachedOnChange item in collection)
 | |
|                     OnChange.Invoke(item.Operation, item.Item, asServer);
 | |
|             }
 | |
| 
 | |
|             collection.Clear();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes all changed values.
 | |
|         /// </summary>
 | |
|         /// <param name="writer"></param>
 | |
|         ///<param name="resetSyncTick">True to set the next time data may sync.</param>
 | |
|         public override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
 | |
|         {
 | |
|             //If sending all then clear changed and write full.
 | |
|             if (_sendAll)
 | |
|             {
 | |
|                 _sendAll = false;
 | |
|                 _changed.Clear();
 | |
|                 WriteFull(writer);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 base.WriteDelta(writer, resetSyncTick);
 | |
|                 //False for not full write.
 | |
|                 writer.WriteBoolean(false);
 | |
|                 writer.WriteInt32(_changed.Count);
 | |
| 
 | |
|                 for (int i = 0; i < _changed.Count; i++)
 | |
|                 {
 | |
|                     ChangeData change = _changed[i];
 | |
|                     writer.WriteByte((byte)change.Operation);
 | |
| 
 | |
|                     //Clear does not need to write anymore data so it is not included in checks.
 | |
|                     if (change.Operation == SyncHashSetOperation.Add || change.Operation == SyncHashSetOperation.Remove || change.Operation == SyncHashSetOperation.Update)
 | |
|                     {
 | |
|                         writer.Write(change.Item);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 _changed.Clear();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Writes all values if not initial values.
 | |
|         /// </summary>
 | |
|         /// <param name="writer"></param>
 | |
|         public override void WriteFull(PooledWriter writer)
 | |
|         {
 | |
|             if (!_valuesChanged)
 | |
|                 return;
 | |
| 
 | |
|             base.WriteHeader(writer, false);
 | |
|             //True for full write.
 | |
|             writer.WriteBoolean(true);
 | |
|             int count = Collection.Count;
 | |
|             writer.WriteInt32(count);
 | |
|             foreach (T item in Collection)
 | |
|             {
 | |
|                 writer.WriteByte((byte)SyncHashSetOperation.Add);
 | |
|                 writer.Write(item);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Reads and sets the current values for server or client.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         [APIExclude]
 | |
|         public override void Read(PooledReader reader, bool asServer)
 | |
|         {
 | |
|             /* When !asServer don't make changes if server is running.
 | |
|             * This is because changes would have already been made on
 | |
|             * the server side and doing so again would result in duplicates
 | |
|             * and potentially overwrite data not yet sent. */
 | |
|             bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
 | |
|             ISet<T> collection = (asClientAndHost) ? ClientHostCollection : Collection;
 | |
| 
 | |
|             //Clear collection since it's a full write.
 | |
|             bool fullWrite = reader.ReadBoolean();
 | |
|             if (fullWrite)
 | |
|                 collection.Clear();
 | |
| 
 | |
|             int changes = reader.ReadInt32();
 | |
|             for (int i = 0; i < changes; i++)
 | |
|             {
 | |
|                 SyncHashSetOperation operation = (SyncHashSetOperation)reader.ReadByte();
 | |
|                 T next = default;
 | |
| 
 | |
|                 //Add.
 | |
|                 if (operation == SyncHashSetOperation.Add)
 | |
|                 {
 | |
|                     next = reader.Read<T>();
 | |
|                     collection.Add(next);
 | |
|                 }
 | |
|                 //Clear.
 | |
|                 else if (operation == SyncHashSetOperation.Clear)
 | |
|                 {
 | |
|                     collection.Clear();
 | |
|                 }
 | |
|                 //Remove.
 | |
|                 else if (operation == SyncHashSetOperation.Remove)
 | |
|                 {
 | |
|                     next = reader.Read<T>();
 | |
|                     collection.Remove(next);
 | |
|                 }
 | |
|                 //Updated.
 | |
|                 else if (operation == SyncHashSetOperation.Update)
 | |
|                 {
 | |
|                     next = reader.Read<T>();
 | |
|                     collection.Remove(next);
 | |
|                     collection.Add(next);
 | |
|                 }
 | |
| 
 | |
|                 InvokeOnChange(operation, next, false);
 | |
|             }
 | |
| 
 | |
|             //If changes were made invoke complete after all have been read.
 | |
|             if (changes > 0)
 | |
|                 InvokeOnChange(SyncHashSetOperation.Complete, default, false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Invokes OnChanged callback.
 | |
|         /// </summary>
 | |
|         private void InvokeOnChange(SyncHashSetOperation operation, T item, bool asServer)
 | |
|         {
 | |
|             if (asServer)
 | |
|             {
 | |
|                 if (base.NetworkBehaviour.OnStartServerCalled)
 | |
|                     OnChange?.Invoke(operation, item, asServer);
 | |
|                 else
 | |
|                     _serverOnChanges.Add(new CachedOnChange(operation, item));
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (base.NetworkBehaviour.OnStartClientCalled)
 | |
|                     OnChange?.Invoke(operation, item, asServer);
 | |
|                 else
 | |
|                     _clientOnChanges.Add(new CachedOnChange(operation, item));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Resets to initialized values.
 | |
|         /// </summary>
 | |
|         public override void Reset()
 | |
|         {
 | |
|             base.Reset();
 | |
|             _sendAll = false;
 | |
|             _changed.Clear();
 | |
|             Collection.Clear();
 | |
|             ClientHostCollection.Clear();
 | |
| 
 | |
|             foreach (T item in _initialValues)
 | |
|             {
 | |
|                 Collection.Add(item);
 | |
|                 ClientHostCollection.Add(item);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds value.
 | |
|         /// </summary>
 | |
|         /// <param name="item"></param>
 | |
|         public bool Add(T item)
 | |
|         {
 | |
|             return Add(item, true);
 | |
|         }
 | |
|         private bool Add(T item, bool asServer)
 | |
|         {
 | |
|             if (!base.CanNetworkSetValues(true))
 | |
|                 return false;
 | |
| 
 | |
|             bool result = Collection.Add(item);
 | |
|             //Only process if remove was successful.
 | |
|             if (result && asServer)
 | |
|             {
 | |
|                 if (base.NetworkManager == null)
 | |
|                     ClientHostCollection.Add(item);
 | |
|                 AddOperation(SyncHashSetOperation.Add, item);
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Adds a range of values.
 | |
|         /// </summary>
 | |
|         /// <param name="range"></param>
 | |
|         public void AddRange(IEnumerable<T> range)
 | |
|         {
 | |
|             foreach (T entry in range)
 | |
|                 Add(entry, true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Clears all values.
 | |
|         /// </summary>
 | |
|         public void Clear()
 | |
|         {
 | |
|             Clear(true);
 | |
|         }
 | |
|         private void Clear(bool asServer)
 | |
|         {
 | |
|             if (!base.CanNetworkSetValues(true))
 | |
|                 return;
 | |
| 
 | |
|             Collection.Clear();
 | |
|             if (asServer)
 | |
|             {
 | |
|                 if (base.NetworkManager == null)
 | |
|                     ClientHostCollection.Clear();
 | |
|                 AddOperation(SyncHashSetOperation.Clear, default);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if value exist.
 | |
|         /// </summary>
 | |
|         /// <param name="item"></param>
 | |
|         /// <returns></returns>
 | |
|         public bool Contains(T item)
 | |
|         {
 | |
|             return Collection.Contains(item);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Removes a value.
 | |
|         /// </summary>
 | |
|         /// <param name="item"></param>
 | |
|         /// <returns></returns>
 | |
|         public bool Remove(T item)
 | |
|         {
 | |
|             return Remove(item, true);
 | |
|         }
 | |
|         private bool Remove(T item, bool asServer)
 | |
|         {
 | |
|             if (!base.CanNetworkSetValues(true))
 | |
|                 return false;
 | |
| 
 | |
|             bool result = Collection.Remove(item);
 | |
|             //Only process if remove was successful.
 | |
|             if (result && asServer)
 | |
|             {
 | |
|                 if (base.NetworkManager == null)
 | |
|                     ClientHostCollection.Remove(item);
 | |
|                 AddOperation(SyncHashSetOperation.Remove, item);
 | |
|             }
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Dirties the entire collection forcing a full send.
 | |
|         /// </summary>
 | |
|         public void DirtyAll()
 | |
|         {
 | |
|             if (!base.IsRegistered)
 | |
|                 return;
 | |
|             if (!base.CanNetworkSetValues(true))
 | |
|                 return;
 | |
| 
 | |
|             if (base.Dirty())
 | |
|                 _sendAll = true;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Looks up obj in Collection and if found marks it's index as dirty.
 | |
|         /// This operation can be very expensive, will cause allocations, and may fail if your value cannot be compared.
 | |
|         /// </summary>
 | |
|         /// <param name="obj">Object to lookup.</param>
 | |
|         public void Dirty(T obj)
 | |
|         {
 | |
|             if (!base.IsRegistered)
 | |
|                 return;
 | |
|             if (!base.CanNetworkSetValues(true))
 | |
|                 return;
 | |
| 
 | |
|             foreach (T item in Collection)
 | |
|             {
 | |
|                 if (item.Equals(obj))
 | |
|                 {
 | |
|                     AddOperation(SyncHashSetOperation.Update, obj);
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             //Not found.
 | |
|             base.NetworkManager.LogError($"Could not find object within SyncHashSet, dirty will not be set.");
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns Enumerator for collection.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         public IEnumerator GetEnumerator() => Collection.GetEnumerator();
 | |
|         [APIExclude]
 | |
|         IEnumerator<T> IEnumerable<T>.GetEnumerator() => Collection.GetEnumerator();
 | |
|         [APIExclude]
 | |
|         IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
 | |
| 
 | |
|         public void ExceptWith(IEnumerable<T> other)
 | |
|         {
 | |
|             //Again, removing from self is a clear.
 | |
|             if (other == Collection)
 | |
|             {
 | |
|                 Clear();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 foreach (T item in other)
 | |
|                     Remove(item);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void IntersectWith(IEnumerable<T> other)
 | |
|         {
 | |
|             ISet<T> set;
 | |
|             if (other is ISet<T> setA)
 | |
|                 set = setA;
 | |
|             else
 | |
|                 set = new HashSet<T>(other);
 | |
| 
 | |
|             IntersectWith(set);
 | |
|         }
 | |
| 
 | |
|         private void IntersectWith(ISet<T> other)
 | |
|         {
 | |
|             Intersect(Collection);
 | |
|             if (base.NetworkManager == null)
 | |
|                 Intersect(ClientHostCollection);
 | |
| 
 | |
|             void Intersect(ISet<T> collection)
 | |
|             {
 | |
|                 if (_listCache == null)
 | |
|                     _listCache = new ListCache<T>();
 | |
|                 else
 | |
|                     _listCache.Reset();
 | |
| 
 | |
|                 _listCache.AddValues(collection);
 | |
| 
 | |
|                 int count = _listCache.Written;
 | |
|                 for (int i = 0; i < count; i++)
 | |
|                 {
 | |
|                     T entry = _listCache.Collection[i];
 | |
|                     if (!other.Contains(entry))
 | |
|                         Remove(entry);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         public bool IsProperSubsetOf(IEnumerable<T> other)
 | |
|         {
 | |
|             return Collection.IsProperSubsetOf(other);
 | |
|         }
 | |
| 
 | |
|         public bool IsProperSupersetOf(IEnumerable<T> other)
 | |
|         {
 | |
|             return Collection.IsProperSupersetOf(other);
 | |
|         }
 | |
| 
 | |
|         public bool IsSubsetOf(IEnumerable<T> other)
 | |
|         {
 | |
|             return Collection.IsSubsetOf(other);
 | |
|         }
 | |
| 
 | |
|         public bool IsSupersetOf(IEnumerable<T> other)
 | |
|         {
 | |
|             return Collection.IsSupersetOf(other);
 | |
|         }
 | |
| 
 | |
|         public bool Overlaps(IEnumerable<T> other)
 | |
|         {
 | |
|             bool result = Collection.Overlaps(other);
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         public bool SetEquals(IEnumerable<T> other)
 | |
|         {
 | |
|             return Collection.SetEquals(other);
 | |
|         }
 | |
| 
 | |
|         public void SymmetricExceptWith(IEnumerable<T> other)
 | |
|         {
 | |
|             //If calling except on self then that is the same as a clear.
 | |
|             if (other == Collection)
 | |
|             {
 | |
|                 Clear();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 foreach (T item in other)
 | |
|                     Remove(item);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void UnionWith(IEnumerable<T> other)
 | |
|         {
 | |
|             if (other == Collection)
 | |
|                 return;
 | |
| 
 | |
|             foreach (T item in other)
 | |
|                 Add(item);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds an item.
 | |
|         /// </summary>
 | |
|         /// <param name="item"></param>
 | |
|         void ICollection<T>.Add(T item)
 | |
|         {
 | |
|             Add(item, true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Copies values to an array.
 | |
|         /// </summary>
 | |
|         /// <param name="array"></param>
 | |
|         /// <param name="index"></param>
 | |
|         public void CopyTo(T[] array, int index)
 | |
|         {
 | |
|             Collection.CopyTo(array, index);
 | |
|             if (base.NetworkManager == null)
 | |
|                 ClientHostCollection.CopyTo(array, index);
 | |
|         }
 | |
|     }
 | |
| }
 |