mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
feat: Add SyncHashSet and SyncSortedSet (#685)
* feat: Add SyncHashSet and SyncSorted set
This commit is contained in:
parent
6ed7bded1e
commit
695979e914
@ -64,6 +64,7 @@ class Weaver
|
|||||||
|
|
||||||
public static TypeReference MessageBaseType;
|
public static TypeReference MessageBaseType;
|
||||||
public static TypeReference SyncListType;
|
public static TypeReference SyncListType;
|
||||||
|
public static TypeReference SyncSetType;
|
||||||
public static TypeReference SyncDictionaryType;
|
public static TypeReference SyncDictionaryType;
|
||||||
|
|
||||||
public static MethodReference NetworkBehaviourDirtyBitsReference;
|
public static MethodReference NetworkBehaviourDirtyBitsReference;
|
||||||
@ -1130,6 +1131,7 @@ static void SetupTargetTypes()
|
|||||||
|
|
||||||
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
|
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
|
||||||
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
|
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
|
||||||
|
SyncSetType = NetAssembly.MainModule.GetType("Mirror.SyncSet`1");
|
||||||
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
|
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
|
||||||
|
|
||||||
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
|
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
|
||||||
@ -1374,6 +1376,12 @@ static bool CheckSyncList(TypeDefinition td)
|
|||||||
didWork = true;
|
didWork = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else if (parent.FullName.StartsWith(SyncSetType.FullName))
|
||||||
|
{
|
||||||
|
SyncListProcessor.Process(td);
|
||||||
|
didWork = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
else if (parent.FullName.StartsWith(SyncDictionaryType.FullName))
|
else if (parent.FullName.StartsWith(SyncDictionaryType.FullName))
|
||||||
{
|
{
|
||||||
SyncDictionaryProcessor.Process(td);
|
SyncDictionaryProcessor.Process(td);
|
||||||
|
330
Assets/Mirror/Runtime/SyncSet.cs
Normal file
330
Assets/Mirror/Runtime/SyncSet.cs
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Mirror
|
||||||
|
{
|
||||||
|
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public abstract class SyncSet<T> : ISet<T>, SyncObject
|
||||||
|
{
|
||||||
|
public delegate void SyncSetChanged(Operation op, T item);
|
||||||
|
|
||||||
|
readonly ISet<T> objects;
|
||||||
|
|
||||||
|
public int Count => objects.Count;
|
||||||
|
public bool IsReadOnly { get; private set; }
|
||||||
|
public event SyncSetChanged Callback;
|
||||||
|
|
||||||
|
public enum Operation : byte
|
||||||
|
{
|
||||||
|
OP_ADD,
|
||||||
|
OP_CLEAR,
|
||||||
|
OP_REMOVE
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Change
|
||||||
|
{
|
||||||
|
internal Operation operation;
|
||||||
|
internal T item;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly List<Change> changes = new List<Change>();
|
||||||
|
// 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 = 0;
|
||||||
|
|
||||||
|
protected SyncSet(ISet<T> objects)
|
||||||
|
{
|
||||||
|
this.objects = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void SerializeItem(NetworkWriter writer, T item) {}
|
||||||
|
protected virtual T DeserializeItem(NetworkReader reader) => default;
|
||||||
|
|
||||||
|
public bool IsDirty => changes.Count > 0;
|
||||||
|
|
||||||
|
// throw away all the changes
|
||||||
|
// this should be called after a successfull sync
|
||||||
|
public void Flush() => changes.Clear();
|
||||||
|
|
||||||
|
void AddOperation(Operation op, T item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("SyncSets can only be modified at the server");
|
||||||
|
}
|
||||||
|
|
||||||
|
Change change = new Change
|
||||||
|
{
|
||||||
|
operation = op,
|
||||||
|
item = item
|
||||||
|
};
|
||||||
|
|
||||||
|
changes.Add(change);
|
||||||
|
|
||||||
|
Callback?.Invoke(op, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddOperation(Operation op) => AddOperation(op, default);
|
||||||
|
|
||||||
|
public void OnSerializeAll(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
// if init, write the full list content
|
||||||
|
writer.WritePackedUInt32((uint)objects.Count);
|
||||||
|
|
||||||
|
foreach (T obj in objects)
|
||||||
|
{
|
||||||
|
SerializeItem(writer, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.WritePackedUInt32((uint)changes.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSerializeDelta(NetworkWriter writer)
|
||||||
|
{
|
||||||
|
// write all the queued up changes
|
||||||
|
writer.WritePackedUInt32((uint)changes.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < changes.Count; i++)
|
||||||
|
{
|
||||||
|
Change change = changes[i];
|
||||||
|
writer.Write((byte)change.operation);
|
||||||
|
|
||||||
|
switch (change.operation)
|
||||||
|
{
|
||||||
|
case Operation.OP_ADD:
|
||||||
|
SerializeItem(writer, change.item);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operation.OP_CLEAR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operation.OP_REMOVE:
|
||||||
|
SerializeItem(writer, change.item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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.ReadPackedUInt32();
|
||||||
|
|
||||||
|
objects.Clear();
|
||||||
|
changes.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
T obj = DeserializeItem(reader);
|
||||||
|
objects.Add(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.ReadPackedUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDeserializeDelta(NetworkReader reader)
|
||||||
|
{
|
||||||
|
// This list can now only be modified by synchronization
|
||||||
|
IsReadOnly = true;
|
||||||
|
|
||||||
|
int changesCount = (int)reader.ReadPackedUInt32();
|
||||||
|
|
||||||
|
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;
|
||||||
|
T item = default;
|
||||||
|
|
||||||
|
switch (operation)
|
||||||
|
{
|
||||||
|
case Operation.OP_ADD:
|
||||||
|
item = DeserializeItem(reader);
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
objects.Add(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operation.OP_CLEAR:
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
objects.Clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operation.OP_REMOVE:
|
||||||
|
item = DeserializeItem(reader);
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
objects.Remove(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
Callback?.Invoke(operation, item);
|
||||||
|
}
|
||||||
|
// we just skipped this change
|
||||||
|
else
|
||||||
|
{
|
||||||
|
changesAhead--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Add(T item)
|
||||||
|
{
|
||||||
|
if (objects.Add(item))
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_ADD, item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollection<T>.Add(T item)
|
||||||
|
{
|
||||||
|
if (objects.Add(item))
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_ADD, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
objects.Clear();
|
||||||
|
AddOperation(Operation.OP_CLEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item) => objects.Contains(item);
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int index) => objects.CopyTo(array, index);
|
||||||
|
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
if (objects.Remove(item))
|
||||||
|
{
|
||||||
|
AddOperation(Operation.OP_REMOVE, item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => objects.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public void ExceptWith(IEnumerable<T> other)
|
||||||
|
{
|
||||||
|
if (other == this)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove every element in other from this
|
||||||
|
foreach (T element in other)
|
||||||
|
{
|
||||||
|
Remove(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void IntersectWith(IEnumerable<T> other)
|
||||||
|
{
|
||||||
|
if (other is ISet<T> otherSet)
|
||||||
|
{
|
||||||
|
IntersectWithSet(otherSet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HashSet<T> otherAsSet = new HashSet<T>(other);
|
||||||
|
IntersectWithSet(otherAsSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntersectWithSet(ISet<T> otherSet)
|
||||||
|
{
|
||||||
|
List<T> elements = new List<T>(objects);
|
||||||
|
|
||||||
|
foreach (T element in elements)
|
||||||
|
{
|
||||||
|
if (!otherSet.Contains(element))
|
||||||
|
{
|
||||||
|
Remove(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsProperSubsetOf(IEnumerable<T> other) => objects.IsProperSubsetOf(other);
|
||||||
|
|
||||||
|
public bool IsProperSupersetOf(IEnumerable<T> other) => objects.IsProperSupersetOf(other);
|
||||||
|
|
||||||
|
public bool IsSubsetOf(IEnumerable<T> other) => objects.IsSubsetOf(other);
|
||||||
|
|
||||||
|
public bool IsSupersetOf(IEnumerable<T> other) => objects.IsSupersetOf(other);
|
||||||
|
|
||||||
|
public bool Overlaps(IEnumerable<T> other) => objects.Overlaps(other);
|
||||||
|
|
||||||
|
public bool SetEquals(IEnumerable<T> other) => objects.SetEquals(other);
|
||||||
|
|
||||||
|
public void SymmetricExceptWith(IEnumerable<T> other)
|
||||||
|
{
|
||||||
|
if (other == this)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (T element in other)
|
||||||
|
{
|
||||||
|
if (!Remove(element))
|
||||||
|
{
|
||||||
|
Add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnionWith(IEnumerable<T> other)
|
||||||
|
{
|
||||||
|
if (other != this)
|
||||||
|
{
|
||||||
|
foreach (T element in other)
|
||||||
|
{
|
||||||
|
Add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class SyncHashSet<T> : SyncSet<T>
|
||||||
|
{
|
||||||
|
protected SyncHashSet() : base(new HashSet<T>()) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class SyncSortedSet<T> : SyncSet<T>
|
||||||
|
{
|
||||||
|
protected SyncSortedSet() : base(new SortedSet<T>()) {}
|
||||||
|
|
||||||
|
protected SyncSortedSet(IComparer<T> comparer) : base (new SortedSet<T>(comparer)) {}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Runtime/SyncSet.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncSet.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8a31599d9f9dd4ef9999f7b9707c832c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
274
Assets/Mirror/Tests/SyncSetTest.cs
Normal file
274
Assets/Mirror/Tests/SyncSetTest.cs
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Mirror.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class SyncSetTest
|
||||||
|
{
|
||||||
|
public class SyncSetString : SyncHashSet<string> {}
|
||||||
|
|
||||||
|
SyncSetString serverSyncSet;
|
||||||
|
SyncSetString clientSyncSet;
|
||||||
|
|
||||||
|
void SerializeAllTo<T>(T fromList, T toList) where T : SyncObject
|
||||||
|
{
|
||||||
|
NetworkWriter writer = new NetworkWriter();
|
||||||
|
fromList.OnSerializeAll(writer);
|
||||||
|
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||||
|
toList.OnDeserializeAll(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerializeDeltaTo<T>(T fromList, T toList) where T : SyncObject
|
||||||
|
{
|
||||||
|
NetworkWriter writer = new NetworkWriter();
|
||||||
|
fromList.OnSerializeDelta(writer);
|
||||||
|
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||||
|
toList.OnDeserializeDelta(reader);
|
||||||
|
fromList.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
serverSyncSet = new SyncSetString();
|
||||||
|
clientSyncSet = new SyncSetString();
|
||||||
|
|
||||||
|
// add some data to the list
|
||||||
|
serverSyncSet.Add("Hello");
|
||||||
|
serverSyncSet.Add("World");
|
||||||
|
serverSyncSet.Add("!");
|
||||||
|
SerializeAllTo(serverSyncSet, clientSyncSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInit()
|
||||||
|
{
|
||||||
|
Assert.That(serverSyncSet, Is.EquivalentTo(new[] {"Hello", "World", "!"}));
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] {"Hello", "World", "!"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAdd()
|
||||||
|
{
|
||||||
|
serverSyncSet.Add("yay");
|
||||||
|
Assert.That(serverSyncSet.IsDirty, Is.True);
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "World", "!", "yay" }));
|
||||||
|
Assert.That(serverSyncSet.IsDirty, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClear()
|
||||||
|
{
|
||||||
|
serverSyncSet.Clear();
|
||||||
|
Assert.That(serverSyncSet.IsDirty, Is.True);
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new string[] {}));
|
||||||
|
Assert.That(serverSyncSet.IsDirty, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemove()
|
||||||
|
{
|
||||||
|
serverSyncSet.Remove("World");
|
||||||
|
Assert.That(serverSyncSet.IsDirty, Is.True);
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "!" }));
|
||||||
|
Assert.That(serverSyncSet.IsDirty, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultSync()
|
||||||
|
{
|
||||||
|
serverSyncSet.Add("1");
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
// add some delta and see if it applies
|
||||||
|
serverSyncSet.Add("2");
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "World", "!", "1","2" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CallbackTest()
|
||||||
|
{
|
||||||
|
bool called = false;
|
||||||
|
|
||||||
|
clientSyncSet.Callback += (op, item) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
|
||||||
|
Assert.That(op, Is.EqualTo(SyncSetString.Operation.OP_ADD));
|
||||||
|
Assert.That(item, Is.EqualTo("yay"));
|
||||||
|
};
|
||||||
|
|
||||||
|
serverSyncSet.Add("yay");
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
|
||||||
|
Assert.That(called, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CallbackRemoveTest()
|
||||||
|
{
|
||||||
|
bool called = false;
|
||||||
|
|
||||||
|
clientSyncSet.Callback += (op, item) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
|
||||||
|
Assert.That(op, Is.EqualTo(SyncSetString.Operation.OP_REMOVE));
|
||||||
|
Assert.That(item, Is.EqualTo("World"));
|
||||||
|
};
|
||||||
|
serverSyncSet.Remove("World");
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
|
||||||
|
Assert.That(called, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CountTest()
|
||||||
|
{
|
||||||
|
Assert.That(serverSyncSet.Count, Is.EqualTo(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadOnlyTest()
|
||||||
|
{
|
||||||
|
Assert.That(serverSyncSet.IsReadOnly, Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadonlyTest()
|
||||||
|
{
|
||||||
|
SyncSetString serverList = new SyncSetString();
|
||||||
|
SyncSetString clientList = new SyncSetString();
|
||||||
|
|
||||||
|
// data has been flushed, should go back to clear
|
||||||
|
Assert.That(clientList.IsReadOnly, Is.False);
|
||||||
|
|
||||||
|
serverList.Add("1");
|
||||||
|
serverList.Add("2");
|
||||||
|
serverList.Add("3");
|
||||||
|
SerializeDeltaTo(serverList, clientList);
|
||||||
|
|
||||||
|
// client list should now lock itself, trying to modify it
|
||||||
|
// should produce an InvalidOperationException
|
||||||
|
Assert.That(clientList.IsReadOnly, Is.True);
|
||||||
|
Assert.Throws<InvalidOperationException>(() => { clientList.Add("5"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExceptWith()
|
||||||
|
{
|
||||||
|
serverSyncSet.ExceptWith(new[] { "World", "Hello" });
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "!" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExceptWithSelf()
|
||||||
|
{
|
||||||
|
serverSyncSet.ExceptWith(serverSyncSet);
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new String [] {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIntersectWith()
|
||||||
|
{
|
||||||
|
serverSyncSet.IntersectWith(new[] { "World", "Hello" });
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIntersectWithSet()
|
||||||
|
{
|
||||||
|
serverSyncSet.IntersectWith(new HashSet<string> { "World", "Hello" });
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIsProperSubsetOf()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.IsProperSubsetOf(new[] { "World", "Hello", "!", "pepe" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIsProperSubsetOfSet()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.IsProperSubsetOf(new HashSet<string> { "World", "Hello", "!", "pepe" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIsNotProperSubsetOf()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.IsProperSubsetOf(new[] { "World", "!", "pepe"}), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIsProperSuperSetOf()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.IsProperSupersetOf(new[] { "World", "Hello" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIsSubsetOf()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.IsSubsetOf(new[] { "World", "Hello", "!" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIsSupersetOf()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.IsSupersetOf(new[] { "World", "Hello" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlaps()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.Overlaps(new[] { "World", "my", "baby"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSetEquals()
|
||||||
|
{
|
||||||
|
Assert.That(clientSyncSet.SetEquals(new[] { "World","Hello", "!" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSymmetricExceptWith()
|
||||||
|
{
|
||||||
|
serverSyncSet.SymmetricExceptWith(new HashSet<string> { "Hello", "is" });
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "is", "!" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSymmetricExceptWithSelf()
|
||||||
|
{
|
||||||
|
serverSyncSet.SymmetricExceptWith(serverSyncSet);
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new String[] { }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnionWith()
|
||||||
|
{
|
||||||
|
serverSyncSet.UnionWith(new HashSet<string> { "Hello", "is" });
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello", "is", "!" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnionWithSelf()
|
||||||
|
{
|
||||||
|
serverSyncSet.UnionWith(serverSyncSet);
|
||||||
|
SerializeDeltaTo(serverSyncSet, clientSyncSet);
|
||||||
|
Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello", "!" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Assets/Mirror/Tests/SyncSetTest.cs.meta
Normal file
11
Assets/Mirror/Tests/SyncSetTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 36dbb64593fa546edb477df3d88b6e1a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -1,3 +1,71 @@
|
|||||||
# SyncHashSet
|
# SyncHashSet
|
||||||
|
|
||||||
Need description and code samples for SyncHashSet.
|
`SyncHashSet` are sets similar to C# [HashSet\<T\>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1) that synchronize their contents from the server to the clients.
|
||||||
|
|
||||||
|
A SyncHashSet can contain items of the following types:
|
||||||
|
|
||||||
|
- Basic type (byte, int, float, string, UInt64, etc)
|
||||||
|
- Built-in Unity math type (Vector3, Quaternion, etc)
|
||||||
|
- NetworkIdentity
|
||||||
|
- GameObject with a NetworkIdentity component attached.
|
||||||
|
- Structure with any of the above
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Create a class that derives from SyncHashSet<T> for your specific type. This is necesary because Mirror will add methods to that class with the weaver. Then add a SyncHashSet field to your NetworkBehaviour class. For example:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
class Player : NetworkBehaviour {
|
||||||
|
|
||||||
|
class SyncSkillSet : SyncHashSet<string> {}
|
||||||
|
|
||||||
|
SyncSkillSet skills = new SyncSkillSet();
|
||||||
|
|
||||||
|
int skillPoints = 10;
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
public void CmdLearnSkill(string skillName)
|
||||||
|
{
|
||||||
|
if (skillPoints > 1)
|
||||||
|
{
|
||||||
|
skillPoints--;
|
||||||
|
|
||||||
|
skills.Add(skillName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also detect when a SyncHashSet changes. This is useful for refreshing your character in the client or determining when you need to update your database. Subscribe to the Callback event typically during `Start`, `OnClientStart` or `OnServerStart` for that. Note that by the time you subscribe, the set will already be initialized, so you will not get a call for the initial data, only updates.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
class Player : NetworkBehaviour
|
||||||
|
{
|
||||||
|
class SyncSetBuffs : SyncHashSet<string> {};
|
||||||
|
|
||||||
|
SyncSetBuffs buffs = new SyncSetBuffs();
|
||||||
|
|
||||||
|
// this will add the delegate on the client.
|
||||||
|
// Use OnStartServer instead if you want it on the server
|
||||||
|
public override void OnStartClient()
|
||||||
|
{
|
||||||
|
buffs.Callback += OnBuffsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBuffsChanged(SyncSetBuffs.Operation op, string buff)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case SyncSetBuffs.Operation.OP_ADD:
|
||||||
|
// we added a buff, draw an icon on the character
|
||||||
|
break;
|
||||||
|
case SyncSetBuffs.Operation.OP_CLEAR:
|
||||||
|
// clear all buffs from the character
|
||||||
|
break;
|
||||||
|
case SyncSetBuffs.Operation.OP_REMOVE:
|
||||||
|
// We removed a buff from the character
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1,3 +1,74 @@
|
|||||||
# SyncSortedSet
|
# SyncSortedSet
|
||||||
|
|
||||||
Need description and code samples for SyncSortedSet
|
`SyncSortedSet` are sets similar to C# [SortedSet\<T\>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedset-1) that synchronize their contents from the server to the clients.
|
||||||
|
|
||||||
|
Unlike SyncHashSets, all elements in a SyncSortedSet are sorted when they are inserted. Please note this has some performance implications.
|
||||||
|
|
||||||
|
A SyncSortedSet can contain items of the following types:
|
||||||
|
|
||||||
|
- Basic type (byte, int, float, string, UInt64, etc)
|
||||||
|
- Built-in Unity math type (Vector3, Quaternion, etc)
|
||||||
|
- NetworkIdentity
|
||||||
|
- GameObject with a NetworkIdentity component attached.
|
||||||
|
- Structure with any of the above
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Create a class that derives from SyncSortedSet<T> for your specific type. This is necesary because Mirror will add methods to that class with the weaver. Then add a SyncSortedSet field to your NetworkBehaviour class. For example:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
class Player : NetworkBehaviour {
|
||||||
|
|
||||||
|
class SyncSkillSet : SyncSortedSet<string> {}
|
||||||
|
|
||||||
|
SyncSkillSet skills = new SyncSkillSet();
|
||||||
|
|
||||||
|
int skillPoints = 10;
|
||||||
|
|
||||||
|
[Command]
|
||||||
|
public void CmdLearnSkill(string skillName)
|
||||||
|
{
|
||||||
|
if (skillPoints > 1)
|
||||||
|
{
|
||||||
|
skillPoints--;
|
||||||
|
|
||||||
|
skills.Add(skillName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also detect when a SyncSortedSet changes. This is useful for refreshing your character in the client or determining when you need to update your database. Subscribe to the Callback event typically during `Start`, `OnClientStart` or `OnServerStart` for that. Note that by the time you subscribe, the set will already be initialized, so you will not get a call for the initial data, only updates.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
class Player : NetworkBehaviour
|
||||||
|
{
|
||||||
|
|
||||||
|
class SyncSetBuffs : SyncSortedSet<string> {};
|
||||||
|
|
||||||
|
SyncSetBuffs buffs = new SyncSetBuffs();
|
||||||
|
|
||||||
|
// this will add the delegate on the client.
|
||||||
|
// Use OnStartServer instead if you want it on the server
|
||||||
|
public override void OnStartClient()
|
||||||
|
{
|
||||||
|
buffs.Callback += OnBuffsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnBuffsChanged(SyncSetBuffs.Operation op, string buff)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case SyncSetBuffs.Operation.OP_ADD:
|
||||||
|
// we added a buff, draw an icon on the character
|
||||||
|
break;
|
||||||
|
case SyncSetBuffs.Operation.OP_CLEAR:
|
||||||
|
// clear all buffs from the character
|
||||||
|
break;
|
||||||
|
case SyncSetBuffs.Operation.OP_REMOVE:
|
||||||
|
// We removed a buff from the character
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -21,6 +21,6 @@ General description of Classes
|
|||||||
- [SyncDictionary](SyncDictionary)
|
- [SyncDictionary](SyncDictionary)
|
||||||
A SyncDictionary is an associative array containing an unordered list of key, value pairs.
|
A SyncDictionary is an associative array containing an unordered list of key, value pairs.
|
||||||
- [SyncHashSet](SyncHashSet)
|
- [SyncHashSet](SyncHashSet)
|
||||||
Description Needed
|
An unordered set of values that do not repeat.
|
||||||
- [SyncSortedSet](SyncSortedSet)
|
- [SyncSortedSet](SyncSortedSet)
|
||||||
Description Needed
|
A sorted set of values tha do not repeat.
|
||||||
|
@ -15,9 +15,9 @@ Data is not synchronized in the opposite direction - from remote clients to the
|
|||||||
- [SyncDictionary](../Classes/SyncDictionary)
|
- [SyncDictionary](../Classes/SyncDictionary)
|
||||||
A SyncDictionary is an associative array containing an unordered list of key, value pairs.
|
A SyncDictionary is an associative array containing an unordered list of key, value pairs.
|
||||||
- [SyncHashSet](../Classes/SyncHashSet)
|
- [SyncHashSet](../Classes/SyncHashSet)
|
||||||
Description Needed
|
An unordered set of values that do not repeat.
|
||||||
- [SyncSortedSet](../Classes/SyncSortedSet)
|
- [SyncSortedSet](../Classes/SyncSortedSet)
|
||||||
Description Needed
|
A sorted set of values tha do not repeat.
|
||||||
|
|
||||||
## Advanced State Synchronization
|
## Advanced State Synchronization
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user