Skip to content

Commit

Permalink
feat: individual events for SyncDictionary (#112)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: SyncDictionary callback has been replaced
  • Loading branch information
PauloHMattos committed Mar 23, 2020
1 parent 7f6741b commit b3c1b16
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 47 deletions.
92 changes: 79 additions & 13 deletions Assets/Mirror/Runtime/SyncDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,42 @@ namespace Mirror
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class SyncIDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ISyncObject
{
public delegate void SyncDictionaryChanged(Operation op, TKey key, TValue item);

protected readonly IDictionary<TKey, TValue> objects;

public int Count => objects.Count;
public bool IsReadOnly { get; private set; }
public event SyncDictionaryChanged Callback;

public enum Operation : byte
/// <summary>
/// Raised when an element is added to the dictionary.
/// Receives the key and value of the new item
/// </summary>
public event Action<TKey, TValue> OnInsert;

/// <summary>
/// Raised when the dictionary is cleared
/// </summary>
public event Action OnClear;

/// <summary>
/// Raised when an item is removed from the dictionary
/// receives the key and value of the old item
/// </summary>
public event Action<TKey, TValue> OnRemove;

/// <summary>
/// Raised when an item is changed in a dictionary
/// Receives key, the old value and the new value
/// </summary>
public event Action<TKey, TValue, TValue> OnSet;

/// <summary>
/// Raised after the dictionary has been updated
/// Note that if there are multiple changes
/// this event is only raised once.
/// </summary>
public event Action OnChange;

private enum Operation : byte
{
OP_ADD,
OP_CLEAR,
Expand Down Expand Up @@ -59,7 +86,7 @@ protected SyncIDictionary(IDictionary<TKey, TValue> objects)
this.objects = objects;
}

void AddOperation(Operation op, TKey key, TValue item)
void AddOperation(Operation op, TKey key, TValue item, TValue oldItem)
{
if (IsReadOnly)
{
Expand All @@ -75,7 +102,28 @@ void AddOperation(Operation op, TKey key, TValue item)

changes.Add(change);

Callback?.Invoke(op, key, item);
RaiseEvents(op, key, item, oldItem);

OnChange?.Invoke();
}

private void RaiseEvents(Operation op, TKey key, TValue value, TValue oldValue)
{
switch (op)
{
case Operation.OP_ADD:
OnInsert?.Invoke(key, value);
break;
case Operation.OP_CLEAR:
OnClear?.Invoke();
break;
case Operation.OP_REMOVE:
OnRemove?.Invoke(key, value);
break;
case Operation.OP_SET:
OnSet?.Invoke(key, oldValue, value);
break;
}
}

public void OnSerializeAll(NetworkWriter writer)
Expand Down Expand Up @@ -148,6 +196,7 @@ public void OnDeserializeDelta(NetworkReader reader)
{
// This list can now only be modified by synchronization
IsReadOnly = true;
bool raiseOnChange = false;

int changesCount = (int)reader.ReadPackedUInt32();

Expand All @@ -160,15 +209,25 @@ public void OnDeserializeDelta(NetworkReader reader)
bool apply = changesAhead == 0;
TKey key = default;
TValue item = default;
TValue oldItem = default;

switch (operation)
{
case Operation.OP_ADD:
key = DeserializeKey(reader);
item = DeserializeItem(reader);
if (apply)
{
objects[key] = item;
}
break;

case Operation.OP_SET:
key = DeserializeKey(reader);
item = DeserializeItem(reader);
if (apply)
{
oldItem = objects[key];
objects[key] = item;
}
break;
Expand All @@ -192,20 +251,26 @@ public void OnDeserializeDelta(NetworkReader reader)

if (apply)
{
Callback?.Invoke(operation, key, item);
RaiseEvents(operation, key, item, oldItem);
raiseOnChange = true;
}
// we just skipped this change
else
{
changesAhead--;
}
}

if (raiseOnChange)
{
OnChange?.Invoke();
}
}

public void Clear()
{
objects.Clear();
AddOperation(Operation.OP_CLEAR, default, default);
AddOperation(Operation.OP_CLEAR, default, default, default);
}

public bool ContainsKey(TKey key) => objects.ContainsKey(key);
Expand All @@ -214,7 +279,7 @@ public bool Remove(TKey key)
{
if (objects.TryGetValue(key, out TValue item) && objects.Remove(key))
{
AddOperation(Operation.OP_REMOVE, key, item);
AddOperation(Operation.OP_REMOVE, key, item, default);
return true;
}
return false;
Expand All @@ -227,13 +292,14 @@ public bool Remove(TKey key)
{
if (ContainsKey(i))
{
TValue oldItem = objects[i];
objects[i] = value;
AddOperation(Operation.OP_SET, i, value);
AddOperation(Operation.OP_SET, i, value, oldItem);
}
else
{
objects[i] = value;
AddOperation(Operation.OP_ADD, i, value);
AddOperation(Operation.OP_ADD, i, value, default);
}
}
}
Expand All @@ -243,7 +309,7 @@ public bool Remove(TKey key)
public void Add(TKey key, TValue value)
{
objects.Add(key, value);
AddOperation(Operation.OP_ADD, key, value);
AddOperation(Operation.OP_ADD, key, value, default);
}

public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
Expand Down Expand Up @@ -277,7 +343,7 @@ public bool Remove(KeyValuePair<TKey, TValue> item)
bool result = objects.Remove(item.Key);
if (result)
{
AddOperation(Operation.OP_REMOVE, item.Key, item.Value);
AddOperation(Operation.OP_REMOVE, item.Key, item.Value, default);
}
return result;
}
Expand Down
79 changes: 45 additions & 34 deletions Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using NSubstitute;
using NUnit.Framework;

namespace Mirror.Tests
Expand Down Expand Up @@ -149,54 +150,64 @@ public void TestContains()
}

[Test]
public void CallbackTest()
public void AddClientCallbackTest()
{
bool called = false;
clientSyncDictionary.Callback += (op, index, item) =>
{
called = true;
Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_ADD));
Assert.That(index, Is.EqualTo(3));
Assert.That(item, Is.EqualTo("yay"));
Assert.That(clientSyncDictionary[index], Is.EqualTo("yay"));
Action<int, string> callback = Substitute.For<Action<int, string>>();
clientSyncDictionary.OnInsert += callback;
serverSyncDictionary.Add(3, "yay");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
callback.Received().Invoke(3, "yay");
}

};
[Test]
public void AddServerCallbackTest()
{
Action<int, string> callback = Substitute.For<Action<int, string>>();
serverSyncDictionary.OnInsert += callback;
serverSyncDictionary.Add(3, "yay");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(called, Is.True);
callback.Received().Invoke(3, "yay");
}

[Test]
public void ServerCallbackTest()
public void RemoveClientCallbackTest()
{
bool called = false;
serverSyncDictionary.Callback += (op, index, item) =>
{
called = true;
Action<int, string> callback = Substitute.For<Action<int, string>>();
clientSyncDictionary.OnRemove += callback;
serverSyncDictionary.Remove(1);
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
callback.Received().Invoke(1, "World");
}

Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_ADD));
Assert.That(index, Is.EqualTo(3));
Assert.That(item, Is.EqualTo("yay"));
Assert.That(serverSyncDictionary[index], Is.EqualTo("yay"));
};
serverSyncDictionary[3] = "yay";
Assert.That(called, Is.True);
[Test]
public void ClearClientCallbackTest()
{
Action callback = Substitute.For<Action>();
clientSyncDictionary.OnClear += callback;
serverSyncDictionary.Clear();
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
callback.Received().Invoke();
}

[Test]
public void CallbackRemoveTest()
public void ChangeClientCallbackTest()
{
bool called = false;
clientSyncDictionary.Callback += (op, key, item) =>
{
called = true;
Assert.That(op, Is.EqualTo(SyncDictionaryIntString.Operation.OP_REMOVE));
Assert.That(item, Is.EqualTo("World"));
};
serverSyncDictionary.Remove(1);
Action callback = Substitute.For<Action>();
clientSyncDictionary.OnChange += callback;
serverSyncDictionary.Add(3, "1");
serverSyncDictionary.Add(4, "1");
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
callback.Received(1).Invoke();
}

[Test]
public void SetClientCallbackTest()
{
Action<int, string, string> callback = Substitute.For<Action<int, string, string>>();
clientSyncDictionary.OnSet += callback;
serverSyncDictionary[0] = "yay";
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
Assert.That(called, Is.True);
callback.Received().Invoke(0, "Hello", "yay");
}

[Test]
Expand Down

0 comments on commit b3c1b16

Please sign in to comment.