Mirror Networking
SyncSet.cs
1using System;
2using System.Collections;
3using System.Collections.Generic;
4
5namespace Mirror
6{
7 public class SyncSet<T> : SyncObject, ISet<T>
8 {
9 public delegate void SyncSetChanged(Operation op, T item);
10
11 protected readonly ISet<T> objects;
12
13 public int Count => objects.Count;
14 public bool IsReadOnly { get; private set; }
15 public event SyncSetChanged Callback;
16
17 public enum Operation : byte
18 {
19 OP_ADD,
20 OP_CLEAR,
21 OP_REMOVE
22 }
23
24 struct Change
25 {
26 internal Operation operation;
27 internal T item;
28 }
29
30 // list of changes.
31 // -> insert/delete/clear is only ONE change
32 // -> changing the same slot 10x caues 10 changes.
33 // -> note that this grows until next sync(!)
34 // TODO Dictionary<key, change> to avoid ever growing changes / redundant changes!
35 readonly List<Change> changes = new List<Change>();
36
37 // how many changes we need to ignore
38 // this is needed because when we initialize the list,
39 // we might later receive changes that have already been applied
40 // so we need to skip them
41 int changesAhead;
42
43 public SyncSet(ISet<T> objects)
44 {
45 this.objects = objects;
46 }
47
48 public override void Reset()
49 {
50 IsReadOnly = false;
51 changes.Clear();
52 changesAhead = 0;
53 objects.Clear();
54 }
55
56 // throw away all the changes
57 // this should be called after a successful sync
58 public override void ClearChanges() => changes.Clear();
59
60 void AddOperation(Operation op, T item)
61 {
62 if (IsReadOnly)
63 {
64 throw new InvalidOperationException("SyncSets can only be modified at the server");
65 }
66
67 Change change = new Change
68 {
69 operation = op,
70 item = item
71 };
72
73 if (IsRecording())
74 {
75 changes.Add(change);
76 OnDirty?.Invoke();
77 }
78
79 Callback?.Invoke(op, item);
80 }
81
82 void AddOperation(Operation op) => AddOperation(op, default);
83
84 public override void OnSerializeAll(NetworkWriter writer)
85 {
86 // if init, write the full list content
87 writer.WriteUInt((uint)objects.Count);
88
89 foreach (T obj in objects)
90 {
91 writer.Write(obj);
92 }
93
94 // all changes have been applied already
95 // thus the client will need to skip all the pending changes
96 // or they would be applied again.
97 // So we write how many changes are pending
98 writer.WriteUInt((uint)changes.Count);
99 }
100
101 public override void OnSerializeDelta(NetworkWriter writer)
102 {
103 // write all the queued up changes
104 writer.WriteUInt((uint)changes.Count);
105
106 for (int i = 0; i < changes.Count; i++)
107 {
108 Change change = changes[i];
109 writer.WriteByte((byte)change.operation);
110
111 switch (change.operation)
112 {
113 case Operation.OP_ADD:
114 writer.Write(change.item);
115 break;
116
117 case Operation.OP_CLEAR:
118 break;
119
120 case Operation.OP_REMOVE:
121 writer.Write(change.item);
122 break;
123 }
124 }
125 }
126
127 public override void OnDeserializeAll(NetworkReader reader)
128 {
129 // This list can now only be modified by synchronization
130 IsReadOnly = true;
131
132 // if init, write the full list content
133 int count = (int)reader.ReadUInt();
134
135 objects.Clear();
136 changes.Clear();
137
138 for (int i = 0; i < count; i++)
139 {
140 T obj = reader.Read<T>();
141 objects.Add(obj);
142 }
143
144 // We will need to skip all these changes
145 // the next time the list is synchronized
146 // because they have already been applied
147 changesAhead = (int)reader.ReadUInt();
148 }
149
150 public override void OnDeserializeDelta(NetworkReader reader)
151 {
152 // This list can now only be modified by synchronization
153 IsReadOnly = true;
154
155 int changesCount = (int)reader.ReadUInt();
156
157 for (int i = 0; i < changesCount; i++)
158 {
159 Operation operation = (Operation)reader.ReadByte();
160
161 // apply the operation only if it is a new change
162 // that we have not applied yet
163 bool apply = changesAhead == 0;
164 T item = default;
165
166 switch (operation)
167 {
168 case Operation.OP_ADD:
169 item = reader.Read<T>();
170 if (apply)
171 {
172 objects.Add(item);
173 }
174 break;
175
176 case Operation.OP_CLEAR:
177 if (apply)
178 {
179 objects.Clear();
180 }
181 break;
182
183 case Operation.OP_REMOVE:
184 item = reader.Read<T>();
185 if (apply)
186 {
187 objects.Remove(item);
188 }
189 break;
190 }
191
192 if (apply)
193 {
194 Callback?.Invoke(operation, item);
195 }
196 // we just skipped this change
197 else
198 {
199 changesAhead--;
200 }
201 }
202 }
203
204 public bool Add(T item)
205 {
206 if (objects.Add(item))
207 {
208 AddOperation(Operation.OP_ADD, item);
209 return true;
210 }
211 return false;
212 }
213
214 void ICollection<T>.Add(T item)
215 {
216 if (objects.Add(item))
217 {
218 AddOperation(Operation.OP_ADD, item);
219 }
220 }
221
222 public void Clear()
223 {
224 objects.Clear();
225 AddOperation(Operation.OP_CLEAR);
226 }
227
228 public bool Contains(T item) => objects.Contains(item);
229
230 public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);
231
232 public bool Remove(T item)
233 {
234 if (objects.Remove(item))
235 {
236 AddOperation(Operation.OP_REMOVE, item);
237 return true;
238 }
239 return false;
240 }
241
242 public IEnumerator<T> GetEnumerator() => objects.GetEnumerator();
243
244 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
245
246 public void ExceptWith(IEnumerable<T> other)
247 {
248 if (other == this)
249 {
250 Clear();
251 return;
252 }
253
254 // remove every element in other from this
255 foreach (T element in other)
256 {
257 Remove(element);
258 }
259 }
260
261 public void IntersectWith(IEnumerable<T> other)
262 {
263 if (other is ISet<T> otherSet)
264 {
265 IntersectWithSet(otherSet);
266 }
267 else
268 {
269 HashSet<T> otherAsSet = new HashSet<T>(other);
270 IntersectWithSet(otherAsSet);
271 }
272 }
273
274 void IntersectWithSet(ISet<T> otherSet)
275 {
276 List<T> elements = new List<T>(objects);
277
278 foreach (T element in elements)
279 {
280 if (!otherSet.Contains(element))
281 {
282 Remove(element);
283 }
284 }
285 }
286
287 public bool IsProperSubsetOf(IEnumerable<T> other) => objects.IsProperSubsetOf(other);
288
289 public bool IsProperSupersetOf(IEnumerable<T> other) => objects.IsProperSupersetOf(other);
290
291 public bool IsSubsetOf(IEnumerable<T> other) => objects.IsSubsetOf(other);
292
293 public bool IsSupersetOf(IEnumerable<T> other) => objects.IsSupersetOf(other);
294
295 public bool Overlaps(IEnumerable<T> other) => objects.Overlaps(other);
296
297 public bool SetEquals(IEnumerable<T> other) => objects.SetEquals(other);
298
299 // custom implementation so we can do our own Clear/Add/Remove for delta
300 public void SymmetricExceptWith(IEnumerable<T> other)
301 {
302 if (other == this)
303 {
304 Clear();
305 }
306 else
307 {
308 foreach (T element in other)
309 {
310 if (!Remove(element))
311 {
312 Add(element);
313 }
314 }
315 }
316 }
317
318 // custom implementation so we can do our own Clear/Add/Remove for delta
319 public void UnionWith(IEnumerable<T> other)
320 {
321 if (other != this)
322 {
323 foreach (T element in other)
324 {
325 Add(element);
326 }
327 }
328 }
329 }
330
331 public class SyncHashSet<T> : SyncSet<T>
332 {
333 public SyncHashSet() : this(EqualityComparer<T>.Default) {}
334 public SyncHashSet(IEqualityComparer<T> comparer) : base(new HashSet<T>(comparer ?? EqualityComparer<T>.Default)) {}
335
336 // allocation free enumerator
337 public new HashSet<T>.Enumerator GetEnumerator() => ((HashSet<T>)objects).GetEnumerator();
338 }
339
340 public class SyncSortedSet<T> : SyncSet<T>
341 {
342 public SyncSortedSet() : this(Comparer<T>.Default) {}
343 public SyncSortedSet(IComparer<T> comparer) : base(new SortedSet<T>(comparer ?? Comparer<T>.Default)) {}
344
345 // allocation free enumerator
346 public new SortedSet<T>.Enumerator GetEnumerator() => ((SortedSet<T>)objects).GetEnumerator();
347 }
348}
Network Reader for most simple types like floats, ints, buffers, structs, etc. Use NetworkReaderPool....
Network Writer for most simple types like floats, ints, buffers, structs, etc. Use NetworkWriterPool....
SyncObjects sync state between server and client. E.g. SyncLists.
Definition: SyncObject.cs:14
Action OnDirty
Used internally to set owner NetworkBehaviour's dirty mask bit when changed.
Definition: SyncObject.cs:16
Func< bool > IsRecording
Used internally to check if we are currently tracking changes.
Definition: SyncObject.cs:26
override void OnDeserializeDelta(NetworkReader reader)
Reads the changes made to the object since last sync
Definition: SyncSet.cs:150
override void OnSerializeDelta(NetworkWriter writer)
Write the changes made to the object since last sync
Definition: SyncSet.cs:101
override void OnSerializeAll(NetworkWriter writer)
Write a full copy of the object
Definition: SyncSet.cs:84
override void Reset()
Resets the SyncObject so that it can be re-used
Definition: SyncSet.cs:48
override void OnDeserializeAll(NetworkReader reader)
Reads a full copy of the object
Definition: SyncSet.cs:127
override void ClearChanges()
Discard all the queued changes