using System.Collections; using System.Collections.Generic; namespace Mirror { public class SyncIDictionary : SyncObject, IDictionary, IReadOnlyDictionary { public delegate void SyncDictionaryChanged(Operation op, TKey key, TValue item); protected readonly IDictionary objects; public int Count => objects.Count; public bool IsReadOnly { get; private set; } public event SyncDictionaryChanged Callback; public enum Operation : byte { OP_ADD, OP_CLEAR, OP_REMOVE, OP_SET } struct Change { internal Operation operation; internal TKey key; internal TValue item; } // list of changes. // -> insert/delete/clear is only ONE change // -> changing the same slot 10x caues 10 changes. // -> note that this grows until next sync(!) // TODO Dictionary to avoid ever growing changes / redundant changes! readonly List changes = new List(); // how many changes we need to ignore // this is needed because when we initialize the list, // we might later receive changes that have already been applied // so we need to skip them int changesAhead; public override void Reset() { IsReadOnly = false; changes.Clear(); changesAhead = 0; objects.Clear(); } public ICollection Keys => objects.Keys; public ICollection Values => objects.Values; IEnumerable IReadOnlyDictionary.Keys => objects.Keys; IEnumerable IReadOnlyDictionary.Values => objects.Values; // throw away all the changes // this should be called after a successful sync public override void ClearChanges() => changes.Clear(); public SyncIDictionary(IDictionary objects) { this.objects = objects; } void AddOperation(Operation op, TKey key, TValue item) { if (IsReadOnly) { throw new System.InvalidOperationException("SyncDictionaries can only be modified by the server"); } Change change = new Change { operation = op, key = key, item = item }; if (IsRecording()) { changes.Add(change); OnDirty?.Invoke(); } Callback?.Invoke(op, key, item); } public override void OnSerializeAll(NetworkWriter writer) { // if init, write the full list content writer.WriteUInt((uint)objects.Count); foreach (KeyValuePair syncItem in objects) { writer.Write(syncItem.Key); writer.Write(syncItem.Value); } // all changes have been applied already // thus the client will need to skip all the pending changes // or they would be applied again. // So we write how many changes are pending writer.WriteUInt((uint)changes.Count); } public override void OnSerializeDelta(NetworkWriter writer) { // write all the queued up changes writer.WriteUInt((uint)changes.Count); for (int i = 0; i < changes.Count; i++) { Change change = changes[i]; writer.WriteByte((byte)change.operation); switch (change.operation) { case Operation.OP_ADD: case Operation.OP_REMOVE: case Operation.OP_SET: writer.Write(change.key); writer.Write(change.item); break; case Operation.OP_CLEAR: break; } } } public override void OnDeserializeAll(NetworkReader reader) { // This list can now only be modified by synchronization IsReadOnly = true; // if init, write the full list content int count = (int)reader.ReadUInt(); objects.Clear(); changes.Clear(); for (int i = 0; i < count; i++) { TKey key = reader.Read(); TValue obj = reader.Read(); objects.Add(key, obj); } // We will need to skip all these changes // the next time the list is synchronized // because they have already been applied changesAhead = (int)reader.ReadUInt(); } public override void OnDeserializeDelta(NetworkReader reader) { // This list can now only be modified by synchronization IsReadOnly = true; int changesCount = (int)reader.ReadUInt(); for (int i = 0; i < changesCount; i++) { Operation operation = (Operation)reader.ReadByte(); // apply the operation only if it is a new change // that we have not applied yet bool apply = changesAhead == 0; TKey key = default; TValue item = default; switch (operation) { case Operation.OP_ADD: case Operation.OP_SET: key = reader.Read(); item = reader.Read(); if (apply) { objects[key] = item; } break; case Operation.OP_CLEAR: if (apply) { objects.Clear(); } break; case Operation.OP_REMOVE: key = reader.Read(); item = reader.Read(); if (apply) { objects.Remove(key); } break; } if (apply) { Callback?.Invoke(operation, key, item); } // we just skipped this change else { changesAhead--; } } } public void Clear() { objects.Clear(); AddOperation(Operation.OP_CLEAR, default, default); } public bool ContainsKey(TKey key) => objects.ContainsKey(key); public bool Remove(TKey key) { if (objects.TryGetValue(key, out TValue item) && objects.Remove(key)) { AddOperation(Operation.OP_REMOVE, key, item); return true; } return false; } public TValue this[TKey i] { get => objects[i]; set { if (ContainsKey(i)) { objects[i] = value; AddOperation(Operation.OP_SET, i, value); } else { objects[i] = value; AddOperation(Operation.OP_ADD, i, value); } } } public bool TryGetValue(TKey key, out TValue value) => objects.TryGetValue(key, out value); public void Add(TKey key, TValue value) { objects.Add(key, value); AddOperation(Operation.OP_ADD, key, value); } public void Add(KeyValuePair item) => Add(item.Key, item.Value); public bool Contains(KeyValuePair item) { return TryGetValue(item.Key, out TValue val) && EqualityComparer.Default.Equals(val, item.Value); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { if (arrayIndex < 0 || arrayIndex > array.Length) { throw new System.ArgumentOutOfRangeException(nameof(arrayIndex), "Array Index Out of Range"); } if (array.Length - arrayIndex < Count) { 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"); } int i = arrayIndex; foreach (KeyValuePair item in objects) { array[i] = item; i++; } } public bool Remove(KeyValuePair item) { bool result = objects.Remove(item.Key); if (result) { AddOperation(Operation.OP_REMOVE, item.Key, item.Value); } return result; } public IEnumerator> GetEnumerator() => objects.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => objects.GetEnumerator(); } public class SyncDictionary : SyncIDictionary { public SyncDictionary() : base(new Dictionary()) {} public SyncDictionary(IEqualityComparer eq) : base(new Dictionary(eq)) {} public SyncDictionary(IDictionary d) : base(new Dictionary(d)) {} public new Dictionary.ValueCollection Values => ((Dictionary)objects).Values; public new Dictionary.KeyCollection Keys => ((Dictionary)objects).Keys; public new Dictionary.Enumerator GetEnumerator() => ((Dictionary)objects).GetEnumerator(); } }