Mirror Networking
SyncDictionary.cs
1using System.Collections;
2using System.Collections.Generic;
3
4namespace Mirror
5{
6 public class SyncIDictionary<TKey, TValue> : SyncObject, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
7 {
8 public delegate void SyncDictionaryChanged(Operation op, TKey key, TValue item);
9
10 protected readonly IDictionary<TKey, TValue> objects;
11
12 public int Count => objects.Count;
13 public bool IsReadOnly { get; private set; }
14 public event SyncDictionaryChanged Callback;
15
16 public enum Operation : byte
17 {
18 OP_ADD,
19 OP_CLEAR,
20 OP_REMOVE,
21 OP_SET
22 }
23
24 struct Change
25 {
26 internal Operation operation;
27 internal TKey key;
28 internal TValue item;
29 }
30
31 // list of changes.
32 // -> insert/delete/clear is only ONE change
33 // -> changing the same slot 10x caues 10 changes.
34 // -> note that this grows until next sync(!)
35 // TODO Dictionary<key, change> to avoid ever growing changes / redundant changes!
36 readonly List<Change> changes = new List<Change>();
37
38 // how many changes we need to ignore
39 // this is needed because when we initialize the list,
40 // we might later receive changes that have already been applied
41 // so we need to skip them
42 int changesAhead;
43
44 public override void Reset()
45 {
46 IsReadOnly = false;
47 changes.Clear();
48 changesAhead = 0;
49 objects.Clear();
50 }
51
52 public ICollection<TKey> Keys => objects.Keys;
53
54 public ICollection<TValue> Values => objects.Values;
55
56 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => objects.Keys;
57
58 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => objects.Values;
59
60 // throw away all the changes
61 // this should be called after a successful sync
62 public override void ClearChanges() => changes.Clear();
63
64 public SyncIDictionary(IDictionary<TKey, TValue> objects)
65 {
66 this.objects = objects;
67 }
68
69 void AddOperation(Operation op, TKey key, TValue item)
70 {
71 if (IsReadOnly)
72 {
73 throw new System.InvalidOperationException("SyncDictionaries can only be modified by the server");
74 }
75
76 Change change = new Change
77 {
78 operation = op,
79 key = key,
80 item = item
81 };
82
83 if (IsRecording())
84 {
85 changes.Add(change);
86 OnDirty?.Invoke();
87 }
88
89 Callback?.Invoke(op, key, item);
90 }
91
92 public override void OnSerializeAll(NetworkWriter writer)
93 {
94 // if init, write the full list content
95 writer.WriteUInt((uint)objects.Count);
96
97 foreach (KeyValuePair<TKey, TValue> syncItem in objects)
98 {
99 writer.Write(syncItem.Key);
100 writer.Write(syncItem.Value);
101 }
102
103 // all changes have been applied already
104 // thus the client will need to skip all the pending changes
105 // or they would be applied again.
106 // So we write how many changes are pending
107 writer.WriteUInt((uint)changes.Count);
108 }
109
110 public override void OnSerializeDelta(NetworkWriter writer)
111 {
112 // write all the queued up changes
113 writer.WriteUInt((uint)changes.Count);
114
115 for (int i = 0; i < changes.Count; i++)
116 {
117 Change change = changes[i];
118 writer.WriteByte((byte)change.operation);
119
120 switch (change.operation)
121 {
122 case Operation.OP_ADD:
123 case Operation.OP_REMOVE:
124 case Operation.OP_SET:
125 writer.Write(change.key);
126 writer.Write(change.item);
127 break;
128 case Operation.OP_CLEAR:
129 break;
130 }
131 }
132 }
133
134 public override void OnDeserializeAll(NetworkReader reader)
135 {
136 // This list can now only be modified by synchronization
137 IsReadOnly = true;
138
139 // if init, write the full list content
140 int count = (int)reader.ReadUInt();
141
142 objects.Clear();
143 changes.Clear();
144
145 for (int i = 0; i < count; i++)
146 {
147 TKey key = reader.Read<TKey>();
148 TValue obj = reader.Read<TValue>();
149 objects.Add(key, obj);
150 }
151
152 // We will need to skip all these changes
153 // the next time the list is synchronized
154 // because they have already been applied
155 changesAhead = (int)reader.ReadUInt();
156 }
157
158 public override void OnDeserializeDelta(NetworkReader reader)
159 {
160 // This list can now only be modified by synchronization
161 IsReadOnly = true;
162
163 int changesCount = (int)reader.ReadUInt();
164
165 for (int i = 0; i < changesCount; i++)
166 {
167 Operation operation = (Operation)reader.ReadByte();
168
169 // apply the operation only if it is a new change
170 // that we have not applied yet
171 bool apply = changesAhead == 0;
172 TKey key = default;
173 TValue item = default;
174
175 switch (operation)
176 {
177 case Operation.OP_ADD:
178 case Operation.OP_SET:
179 key = reader.Read<TKey>();
180 item = reader.Read<TValue>();
181 if (apply)
182 {
183 objects[key] = item;
184 }
185 break;
186
187 case Operation.OP_CLEAR:
188 if (apply)
189 {
190 objects.Clear();
191 }
192 break;
193
194 case Operation.OP_REMOVE:
195 key = reader.Read<TKey>();
196 item = reader.Read<TValue>();
197 if (apply)
198 {
199 objects.Remove(key);
200 }
201 break;
202 }
203
204 if (apply)
205 {
206 Callback?.Invoke(operation, key, item);
207 }
208 // we just skipped this change
209 else
210 {
211 changesAhead--;
212 }
213 }
214 }
215
216 public void Clear()
217 {
218 objects.Clear();
219 AddOperation(Operation.OP_CLEAR, default, default);
220 }
221
222 public bool ContainsKey(TKey key) => objects.ContainsKey(key);
223
224 public bool Remove(TKey key)
225 {
226 if (objects.TryGetValue(key, out TValue item) && objects.Remove(key))
227 {
228 AddOperation(Operation.OP_REMOVE, key, item);
229 return true;
230 }
231 return false;
232 }
233
234 public TValue this[TKey i]
235 {
236 get => objects[i];
237 set
238 {
239 if (ContainsKey(i))
240 {
241 objects[i] = value;
242 AddOperation(Operation.OP_SET, i, value);
243 }
244 else
245 {
246 objects[i] = value;
247 AddOperation(Operation.OP_ADD, i, value);
248 }
249 }
250 }
251
252 public bool TryGetValue(TKey key, out TValue value) => objects.TryGetValue(key, out value);
253
254 public void Add(TKey key, TValue value)
255 {
256 objects.Add(key, value);
257 AddOperation(Operation.OP_ADD, key, value);
258 }
259
260 public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
261
262 public bool Contains(KeyValuePair<TKey, TValue> item)
263 {
264 return TryGetValue(item.Key, out TValue val) && EqualityComparer<TValue>.Default.Equals(val, item.Value);
265 }
266
267 public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
268 {
269 if (arrayIndex < 0 || arrayIndex > array.Length)
270 {
271 throw new System.ArgumentOutOfRangeException(nameof(arrayIndex), "Array Index Out of Range");
272 }
273 if (array.Length - arrayIndex < Count)
274 {
275 throw new System.ArgumentException("The number of items in the SyncDictionary is greater than the available space from arrayIndex to the end of the destination array");
276 }
277
278 int i = arrayIndex;
279 foreach (KeyValuePair<TKey, TValue> item in objects)
280 {
281 array[i] = item;
282 i++;
283 }
284 }
285
286 public bool Remove(KeyValuePair<TKey, TValue> item)
287 {
288 bool result = objects.Remove(item.Key);
289 if (result)
290 {
291 AddOperation(Operation.OP_REMOVE, item.Key, item.Value);
292 }
293 return result;
294 }
295
296 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => objects.GetEnumerator();
297
298 IEnumerator IEnumerable.GetEnumerator() => objects.GetEnumerator();
299 }
300
301 public class SyncDictionary<TKey, TValue> : SyncIDictionary<TKey, TValue>
302 {
303 public SyncDictionary() : base(new Dictionary<TKey, TValue>()) {}
304 public SyncDictionary(IEqualityComparer<TKey> eq) : base(new Dictionary<TKey, TValue>(eq)) {}
305 public SyncDictionary(IDictionary<TKey, TValue> d) : base(new Dictionary<TKey, TValue>(d)) {}
306 public new Dictionary<TKey, TValue>.ValueCollection Values => ((Dictionary<TKey, TValue>)objects).Values;
307 public new Dictionary<TKey, TValue>.KeyCollection Keys => ((Dictionary<TKey, TValue>)objects).Keys;
308 public new Dictionary<TKey, TValue>.Enumerator GetEnumerator() => ((Dictionary<TKey, TValue>)objects).GetEnumerator();
309 }
310}
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....
override void Reset()
Resets the SyncObject so that it can be re-used
override void OnSerializeDelta(NetworkWriter writer)
Write the changes made to the object since last sync
override void OnSerializeAll(NetworkWriter writer)
Write a full copy of the object
override void ClearChanges()
Discard all the queued changes
override void OnDeserializeDelta(NetworkReader reader)
Reads the changes made to the object since last sync
override void OnDeserializeAll(NetworkReader reader)
Reads a full copy of the object
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