SyncSet.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Mirror
  5. {
  6. public class SyncSet<T> : ISet<T>, SyncObject
  7. {
  8. public delegate void SyncSetChanged(Operation op, T item);
  9. protected readonly ISet<T> objects;
  10. public int Count => objects.Count;
  11. public bool IsReadOnly { get; private set; }
  12. public event SyncSetChanged Callback;
  13. public enum Operation : byte
  14. {
  15. OP_ADD,
  16. OP_CLEAR,
  17. OP_REMOVE
  18. }
  19. struct Change
  20. {
  21. internal Operation operation;
  22. internal T item;
  23. }
  24. readonly List<Change> changes = new List<Change>();
  25. // how many changes we need to ignore
  26. // this is needed because when we initialize the list,
  27. // we might later receive changes that have already been applied
  28. // so we need to skip them
  29. int changesAhead;
  30. public SyncSet(ISet<T> objects)
  31. {
  32. this.objects = objects;
  33. }
  34. public void Reset()
  35. {
  36. IsReadOnly = false;
  37. changes.Clear();
  38. changesAhead = 0;
  39. objects.Clear();
  40. }
  41. public bool IsDirty => changes.Count > 0;
  42. // throw away all the changes
  43. // this should be called after a successful sync
  44. public void Flush() => changes.Clear();
  45. void AddOperation(Operation op, T item)
  46. {
  47. if (IsReadOnly)
  48. {
  49. throw new InvalidOperationException("SyncSets can only be modified at the server");
  50. }
  51. Change change = new Change
  52. {
  53. operation = op,
  54. item = item
  55. };
  56. changes.Add(change);
  57. Callback?.Invoke(op, item);
  58. }
  59. void AddOperation(Operation op) => AddOperation(op, default);
  60. public void OnSerializeAll(NetworkWriter writer)
  61. {
  62. // if init, write the full list content
  63. writer.WriteUInt((uint)objects.Count);
  64. foreach (T obj in objects)
  65. {
  66. writer.Write(obj);
  67. }
  68. // all changes have been applied already
  69. // thus the client will need to skip all the pending changes
  70. // or they would be applied again.
  71. // So we write how many changes are pending
  72. writer.WriteUInt((uint)changes.Count);
  73. }
  74. public void OnSerializeDelta(NetworkWriter writer)
  75. {
  76. // write all the queued up changes
  77. writer.WriteUInt((uint)changes.Count);
  78. for (int i = 0; i < changes.Count; i++)
  79. {
  80. Change change = changes[i];
  81. writer.WriteByte((byte)change.operation);
  82. switch (change.operation)
  83. {
  84. case Operation.OP_ADD:
  85. writer.Write(change.item);
  86. break;
  87. case Operation.OP_CLEAR:
  88. break;
  89. case Operation.OP_REMOVE:
  90. writer.Write(change.item);
  91. break;
  92. }
  93. }
  94. }
  95. public void OnDeserializeAll(NetworkReader reader)
  96. {
  97. // This list can now only be modified by synchronization
  98. IsReadOnly = true;
  99. // if init, write the full list content
  100. int count = (int)reader.ReadUInt();
  101. objects.Clear();
  102. changes.Clear();
  103. for (int i = 0; i < count; i++)
  104. {
  105. T obj = reader.Read<T>();
  106. objects.Add(obj);
  107. }
  108. // We will need to skip all these changes
  109. // the next time the list is synchronized
  110. // because they have already been applied
  111. changesAhead = (int)reader.ReadUInt();
  112. }
  113. public void OnDeserializeDelta(NetworkReader reader)
  114. {
  115. // This list can now only be modified by synchronization
  116. IsReadOnly = true;
  117. int changesCount = (int)reader.ReadUInt();
  118. for (int i = 0; i < changesCount; i++)
  119. {
  120. Operation operation = (Operation)reader.ReadByte();
  121. // apply the operation only if it is a new change
  122. // that we have not applied yet
  123. bool apply = changesAhead == 0;
  124. T item = default;
  125. switch (operation)
  126. {
  127. case Operation.OP_ADD:
  128. item = reader.Read<T>();
  129. if (apply)
  130. {
  131. objects.Add(item);
  132. }
  133. break;
  134. case Operation.OP_CLEAR:
  135. if (apply)
  136. {
  137. objects.Clear();
  138. }
  139. break;
  140. case Operation.OP_REMOVE:
  141. item = reader.Read<T>();
  142. if (apply)
  143. {
  144. objects.Remove(item);
  145. }
  146. break;
  147. }
  148. if (apply)
  149. {
  150. Callback?.Invoke(operation, item);
  151. }
  152. // we just skipped this change
  153. else
  154. {
  155. changesAhead--;
  156. }
  157. }
  158. }
  159. public bool Add(T item)
  160. {
  161. if (objects.Add(item))
  162. {
  163. AddOperation(Operation.OP_ADD, item);
  164. return true;
  165. }
  166. return false;
  167. }
  168. void ICollection<T>.Add(T item)
  169. {
  170. if (objects.Add(item))
  171. {
  172. AddOperation(Operation.OP_ADD, item);
  173. }
  174. }
  175. public void Clear()
  176. {
  177. objects.Clear();
  178. AddOperation(Operation.OP_CLEAR);
  179. }
  180. public bool Contains(T item) => objects.Contains(item);
  181. public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);
  182. public bool Remove(T item)
  183. {
  184. if (objects.Remove(item))
  185. {
  186. AddOperation(Operation.OP_REMOVE, item);
  187. return true;
  188. }
  189. return false;
  190. }
  191. public IEnumerator<T> GetEnumerator() => objects.GetEnumerator();
  192. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  193. public void ExceptWith(IEnumerable<T> other)
  194. {
  195. if (other == this)
  196. {
  197. Clear();
  198. return;
  199. }
  200. // remove every element in other from this
  201. foreach (T element in other)
  202. {
  203. Remove(element);
  204. }
  205. }
  206. public void IntersectWith(IEnumerable<T> other)
  207. {
  208. if (other is ISet<T> otherSet)
  209. {
  210. IntersectWithSet(otherSet);
  211. }
  212. else
  213. {
  214. HashSet<T> otherAsSet = new HashSet<T>(other);
  215. IntersectWithSet(otherAsSet);
  216. }
  217. }
  218. void IntersectWithSet(ISet<T> otherSet)
  219. {
  220. List<T> elements = new List<T>(objects);
  221. foreach (T element in elements)
  222. {
  223. if (!otherSet.Contains(element))
  224. {
  225. Remove(element);
  226. }
  227. }
  228. }
  229. public bool IsProperSubsetOf(IEnumerable<T> other) => objects.IsProperSubsetOf(other);
  230. public bool IsProperSupersetOf(IEnumerable<T> other) => objects.IsProperSupersetOf(other);
  231. public bool IsSubsetOf(IEnumerable<T> other) => objects.IsSubsetOf(other);
  232. public bool IsSupersetOf(IEnumerable<T> other) => objects.IsSupersetOf(other);
  233. public bool Overlaps(IEnumerable<T> other) => objects.Overlaps(other);
  234. public bool SetEquals(IEnumerable<T> other) => objects.SetEquals(other);
  235. // custom implementation so we can do our own Clear/Add/Remove for delta
  236. public void SymmetricExceptWith(IEnumerable<T> other)
  237. {
  238. if (other == this)
  239. {
  240. Clear();
  241. }
  242. else
  243. {
  244. foreach (T element in other)
  245. {
  246. if (!Remove(element))
  247. {
  248. Add(element);
  249. }
  250. }
  251. }
  252. }
  253. // custom implementation so we can do our own Clear/Add/Remove for delta
  254. public void UnionWith(IEnumerable<T> other)
  255. {
  256. if (other != this)
  257. {
  258. foreach (T element in other)
  259. {
  260. Add(element);
  261. }
  262. }
  263. }
  264. }
  265. public class SyncHashSet<T> : SyncSet<T>
  266. {
  267. public SyncHashSet() : this(EqualityComparer<T>.Default) {}
  268. public SyncHashSet(IEqualityComparer<T> comparer) : base(new HashSet<T>(comparer ?? EqualityComparer<T>.Default)) {}
  269. // allocation free enumerator
  270. public new HashSet<T>.Enumerator GetEnumerator() => ((HashSet<T>)objects).GetEnumerator();
  271. }
  272. public class SyncSortedSet<T> : SyncSet<T>
  273. {
  274. public SyncSortedSet() : this(Comparer<T>.Default) {}
  275. public SyncSortedSet(IComparer<T> comparer) : base(new SortedSet<T>(comparer ?? Comparer<T>.Default)) {}
  276. // allocation free enumerator
  277. public new SortedSet<T>.Enumerator GetEnumerator() => ((SortedSet<T>)objects).GetEnumerator();
  278. }
  279. }