Skip to content

Commit

Permalink
feat: synclists has individual meaningful events (#109)
Browse files Browse the repository at this point in the history
fixes #103

BREAKING CHANGE: Sync lists no longer have a Callback event with an operation enum
  • Loading branch information
paulpach committed Mar 23, 2020
1 parent 27d9bf6 commit e326064
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 74 deletions.
68 changes: 62 additions & 6 deletions Assets/Mirror/Runtime/SyncList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,43 @@ public class SyncListBool : SyncList<bool>
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class SyncList<T> : IList<T>, IReadOnlyList<T>, ISyncObject
{
public delegate void SyncListChanged(Operation op, int itemIndex, T oldItem, T newItem);

readonly IList<T> objects;
readonly IEqualityComparer<T> comparer;

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

public enum Operation : byte
/// <summary>
/// Raised when an element is added to the list.
/// Receives index and new item
/// </summary>
public event Action<int, T> OnInsert;

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

/// <summary>
/// Raised when an item is removed from the list
/// receives the index and the old item
/// </summary>
public event Action<int, T> OnRemove;

/// <summary>
/// Raised when an item is changed in a list
/// Receives index, old item and new item
/// </summary>
public event Action<int, T, T> OnSet;

/// <summary>
/// Raised after the list 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 @@ -107,7 +134,31 @@ void AddOperation(Operation op, int itemIndex, T oldItem, T newItem)

changes.Add(change);

Callback?.Invoke(op, itemIndex, oldItem, newItem);
RaiseEvents(op, itemIndex, oldItem, newItem);

OnChange?.Invoke();
}

private void RaiseEvents(Operation op, int itemIndex, T oldItem, T newItem)
{
switch (op)
{
case Operation.OP_ADD:
OnInsert?.Invoke(objects.Count - 1, newItem);
break;
case Operation.OP_CLEAR:
OnClear?.Invoke();
break;
case Operation.OP_INSERT:
OnInsert?.Invoke(itemIndex, newItem);
break;
case Operation.OP_REMOVEAT:
OnRemove?.Invoke(itemIndex, oldItem);
break;
case Operation.OP_SET:
OnSet?.Invoke(itemIndex, oldItem, newItem);
break;
}
}

public void OnSerializeAll(NetworkWriter writer)
Expand Down Expand Up @@ -187,6 +238,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 Down Expand Up @@ -250,14 +302,18 @@ public void OnDeserializeDelta(NetworkReader reader)

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

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

public void Add(T item)
Expand Down
34 changes: 4 additions & 30 deletions Assets/Mirror/Tests/Editor/SyncListStructTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using NSubstitute;
using NUnit.Framework;

namespace Mirror.Tests
Expand All @@ -7,8 +9,8 @@ public class SyncListStructTest
[Test]
public void ListIsDirtyWhenModifingAndSettingStruct()
{
SyncListTestPlayer serverList = new SyncListTestPlayer();
SyncListTestPlayer clientList = new SyncListTestPlayer();
var serverList = new SyncListTestPlayer();
var clientList = new SyncListTestPlayer();
SyncListTest.SerializeAllTo(serverList, clientList);
serverList.Add(new TestPlayer { item = new TestItem { price = 10 } });
SyncListTest.SerializeDeltaTo(serverList, clientList);
Expand All @@ -20,34 +22,6 @@ public void ListIsDirtyWhenModifingAndSettingStruct()

Assert.That(serverList.IsDirty, Is.True);
}


[Test]
public void OldValueShouldNotBeNewValue()
{
SyncListTestPlayer serverList = new SyncListTestPlayer();
SyncListTestPlayer clientList = new SyncListTestPlayer();
SyncListTest.SerializeAllTo(serverList, clientList);
serverList.Add(new TestPlayer { item = new TestItem { price = 10 } });
SyncListTest.SerializeDeltaTo(serverList, clientList);

TestPlayer player = serverList[0];
player.item.price = 15;
serverList[0] = player;

bool callbackCalled = false;
clientList.Callback += (SyncList<TestPlayer>.Operation op, int itemIndex, TestPlayer oldItem, TestPlayer newItem) =>
{
Assert.That(op == SyncList<TestPlayer>.Operation.OP_SET, Is.True);
Assert.That(itemIndex, Is.EqualTo(0));
Assert.That(oldItem.item.price, Is.EqualTo(10));
Assert.That(newItem.item.price, Is.EqualTo(15));
callbackCalled = true;
};

SyncListTest.SerializeDeltaTo(serverList, clientList);
Assert.IsTrue(callbackCalled);
}
}


Expand Down
135 changes: 97 additions & 38 deletions Assets/Mirror/Tests/Editor/SyncListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using NUnit.Framework;

using Mirror;
using NSubstitute;

namespace Mirror.Tests
{
Expand Down Expand Up @@ -183,64 +184,122 @@ public void SyncListFloatTest()
}

[Test]
public void CallbackTest()
public void AddClientCallbackTest()
{
bool called = false;

clientSyncList.Callback += (op, index, oldItem, newItem) =>
{
called = true;
Assert.That(op, Is.EqualTo(SyncList<string>.Operation.OP_ADD));
Assert.That(index, Is.EqualTo(3));
Assert.That(oldItem, Is.EqualTo(default(string)));
Assert.That(newItem, Is.EqualTo("yay"));
};

Action<int, string> callback = Substitute.For<Action<int, string>>();
clientSyncList.OnInsert += callback;
serverSyncList.Add("yay");
SerializeDeltaTo(serverSyncList, clientSyncList);
callback.Received().Invoke(3, "yay");
}

Assert.That(called, Is.True);
[Test]
public void InsertClientCallbackTest()
{
Action<int, string> callback = Substitute.For<Action<int, string>>();
clientSyncList.OnInsert += callback;
serverSyncList.Insert(1,"yay");
SerializeDeltaTo(serverSyncList, clientSyncList);
callback.Received().Invoke(1, "yay");
}

[Test]
public void CallbackRemoveTest()
public void RemoveClientCallbackTest()
{
bool called = false;
Action<int, string> callback = Substitute.For<Action<int, string>>();
clientSyncList.OnRemove += callback;
serverSyncList.Remove("World");
SerializeDeltaTo(serverSyncList, clientSyncList);
callback.Received().Invoke(1, "World");
}

clientSyncList.Callback += (op, index, oldItem, newItem) =>
{
called = true;
[Test]
public void ClearClientCallbackTest()
{
Action callback = Substitute.For<Action>();
clientSyncList.OnClear += callback;
serverSyncList.Clear();
SerializeDeltaTo(serverSyncList, clientSyncList);
callback.Received().Invoke();
}

Assert.That(op, Is.EqualTo(SyncList<string>.Operation.OP_REMOVEAT));
Assert.That(oldItem, Is.EqualTo("World"));
Assert.That(newItem, Is.EqualTo(default(string)));
};
serverSyncList.Remove("World");
[Test]
public void SetClientCallbackTest()
{
Action<int, string, string> callback = Substitute.For<Action<int, string, string>>();
clientSyncList.OnSet += callback;
serverSyncList[1] = "yo mama";
SerializeDeltaTo(serverSyncList, clientSyncList);
callback.Received().Invoke(1, "World", "yo mama");
}

Assert.That(called, Is.True);
[Test]
public void ChangeClientCallbackTest()
{
Action callback = Substitute.For<Action>();
clientSyncList.OnChange += callback;
serverSyncList.Add("1");
serverSyncList.Add("2");
SerializeDeltaTo(serverSyncList, clientSyncList);
callback.Received(1).Invoke();
}

[Test]
public void CallbackRemoveAtTest()
public void AddServerCallbackTest()
{
bool called = false;
Action<int, string> callback = Substitute.For<Action<int, string>>();
serverSyncList.OnInsert += callback;
serverSyncList.Add("yay");
callback.Received().Invoke(3, "yay");
}

clientSyncList.Callback += (op, index, oldItem, newItem) =>
{
called = true;
[Test]
public void InsertServerCallbackTest()
{
Action<int, string> callback = Substitute.For<Action<int, string>>();
serverSyncList.OnInsert += callback;
serverSyncList.Insert(1, "yay");
callback.Received().Invoke(1, "yay");
}

Assert.That(op, Is.EqualTo(SyncList<string>.Operation.OP_REMOVEAT));
Assert.That(index, Is.EqualTo(1));
Assert.That(oldItem, Is.EqualTo("World"));
Assert.That(newItem, Is.EqualTo(default(string)));
};
[Test]
public void RemoveServerCallbackTest()
{
Action<int, string> callback = Substitute.For<Action<int, string>>();
serverSyncList.OnRemove += callback;
serverSyncList.Remove("World");
callback.Received().Invoke(1, "World");
}

serverSyncList.RemoveAt(1);
SerializeDeltaTo(serverSyncList, clientSyncList);
[Test]
public void ClearServerCallbackTest()
{
Action callback = Substitute.For<Action>();
serverSyncList.OnClear += callback;
serverSyncList.Clear();
callback.Received().Invoke();
}

Assert.That(called, Is.True);
[Test]
public void SetServerCallbackTest()
{
Action<int, string, string> callback = Substitute.For<Action<int, string, string>>();
serverSyncList.OnSet += callback;
serverSyncList[1] = "yo mama";
callback.Received().Invoke(1, "World", "yo mama");
}

[Test]
public void ChangeServerCallbackTest()
{
Action callback = Substitute.For<Action>();
serverSyncList.OnChange += callback;
serverSyncList.Add("1");
serverSyncList.Add("2");
// note that on the server we would receive 2 calls
// because we are adding 2 operations separately
// there is no way to batch operations in the server
callback.Received().Invoke();
}

[Test]
Expand Down

0 comments on commit e326064

Please sign in to comment.