mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
feat(syncvar): Add SyncDictionary (#602)
* Added basic SyncDictionary support, no support for structs yet * Fixed TryGetValue usage * Removed extraneous hardcoded SyncDictionary type * Added a couple basic tests, more coming * Added 4 more tests * Added two tests and SyncDictionary now bubbles item to Callback on Remove (both Remove cases) * Added the remainder of tests * Added basic documentation about SyncDictionaries on StateSync.md page * Simplify test syntax Co-Authored-By: Katori <znorth@gmail.com> * Simplify test syntax Co-Authored-By: Katori <znorth@gmail.com> * Simplify test syntax Co-Authored-By: Katori <znorth@gmail.com> * Simplify test syntax Co-Authored-By: Katori <znorth@gmail.com> * Remove null-check when setting value directly (and updated expected test behaviour) * fix: Provide default implementation for SyncDictionary serializers * feat: Add Weaver support for syncdictionary * Fix minor issue with Set code and made test use Weaved serialization instead of manual * Added a new test for bare set (non-overwrite) * Added another test for BareSetNull and cleaned up some tests * Updated SyncDictionary documentation on StateSync.md * Update docs with SyncDictionary info * Update SyncDictionary docs wording * docs: document the types and better example * Add two SyncDictionary constructors * Removed unnecessary initialization * Style fixes * - Merged many operation cases - Fixed Contains method - Added new test to test contains (and flag its earlier improper usage) - Use PackedUInt32 instead of int for Changes and Counts * - Simplify "default" syntax - Use Rodol's remove method (faster) - Don't use var * Removed unnecessary newline, renamed <B, T> to <K, V> per vis2k, corrected wording of InvalidOperationException on ReadOnly AddOp * Code simplification, style fixes, docs example style fixes, newly improved implementation for CopyTo that fails gracefully
This commit is contained in:
parent
fea46b801d
commit
7d21bded9a
@ -0,0 +1,19 @@
|
||||
// this class generates OnSerialize/OnDeserialize for SyncLists
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
||||
namespace Mirror.Weaver
|
||||
{
|
||||
static class SyncDictionaryProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates serialization methods for synclists
|
||||
/// </summary>
|
||||
/// <param name="td">The synclist class</param>
|
||||
public static void Process(TypeDefinition td)
|
||||
{
|
||||
SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
|
||||
SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29e4a45f69822462ab0b15adda962a29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -64,6 +64,7 @@ class Weaver
|
||||
|
||||
public static TypeReference MessageBaseType;
|
||||
public static TypeReference SyncListType;
|
||||
public static TypeReference SyncDictionaryType;
|
||||
|
||||
public static MethodReference NetworkBehaviourDirtyBitsReference;
|
||||
public static TypeReference NetworkClientType;
|
||||
@ -1125,6 +1126,7 @@ static void SetupTargetTypes()
|
||||
|
||||
MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
|
||||
SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
|
||||
SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
|
||||
|
||||
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
|
||||
|
||||
@ -1364,6 +1366,12 @@ static bool CheckSyncList(TypeDefinition td)
|
||||
didWork = true;
|
||||
break;
|
||||
}
|
||||
else if (parent.FullName.StartsWith(SyncDictionaryType.FullName))
|
||||
{
|
||||
SyncDictionaryProcessor.Process(td);
|
||||
didWork = true;
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
parent = parent.Resolve().BaseType;
|
||||
|
303
Assets/Mirror/Runtime/SyncDictionary.cs
Normal file
303
Assets/Mirror/Runtime/SyncDictionary.cs
Normal file
@ -0,0 +1,303 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class SyncDictionary<K, V> : IDictionary<K, V>, SyncObject
|
||||
{
|
||||
public delegate void SyncDictionaryChanged(Operation op, K key, V item);
|
||||
|
||||
readonly Dictionary<K, V> m_Objects;
|
||||
|
||||
public int Count => m_Objects.Count;
|
||||
public bool IsReadOnly { get; private set; }
|
||||
public event SyncDictionaryChanged Callback;
|
||||
|
||||
public enum Operation : byte
|
||||
{
|
||||
OP_ADD,
|
||||
OP_CLEAR,
|
||||
OP_REMOVE,
|
||||
OP_SET,
|
||||
OP_DIRTY
|
||||
}
|
||||
|
||||
struct Change
|
||||
{
|
||||
internal Operation operation;
|
||||
internal K key;
|
||||
internal V 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 virtual void SerializeKey(NetworkWriter writer, K item) {}
|
||||
protected virtual void SerializeItem(NetworkWriter writer, V item) {}
|
||||
protected virtual K DeserializeKey(NetworkReader reader) => default;
|
||||
protected virtual V DeserializeItem(NetworkReader reader) => default;
|
||||
|
||||
public bool IsDirty => Changes.Count > 0;
|
||||
|
||||
public ICollection<K> Keys => m_Objects.Keys;
|
||||
|
||||
public ICollection<V> Values => m_Objects.Values;
|
||||
|
||||
// throw away all the changes
|
||||
// this should be called after a successfull sync
|
||||
public void Flush() => Changes.Clear();
|
||||
|
||||
public SyncDictionary()
|
||||
{
|
||||
m_Objects = new Dictionary<K, V>();
|
||||
}
|
||||
|
||||
public SyncDictionary(IEqualityComparer<K> eq)
|
||||
{
|
||||
m_Objects = new Dictionary<K, V>(eq);
|
||||
}
|
||||
|
||||
void AddOperation(Operation op, K key, V 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
|
||||
};
|
||||
|
||||
Changes.Add(change);
|
||||
|
||||
Callback?.Invoke(op, key, item);
|
||||
}
|
||||
|
||||
public void OnSerializeAll(NetworkWriter writer)
|
||||
{
|
||||
// if init, write the full list content
|
||||
writer.WritePackedUInt32((uint)m_Objects.Count);
|
||||
|
||||
foreach (KeyValuePair<K, V> syncItem in m_Objects)
|
||||
{
|
||||
SerializeKey(writer, syncItem.Key);
|
||||
SerializeItem(writer, 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.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:
|
||||
case Operation.OP_REMOVE:
|
||||
case Operation.OP_SET:
|
||||
case Operation.OP_DIRTY:
|
||||
SerializeKey(writer, change.key);
|
||||
SerializeItem(writer, change.item);
|
||||
break;
|
||||
case Operation.OP_CLEAR:
|
||||
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();
|
||||
|
||||
m_Objects.Clear();
|
||||
Changes.Clear();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
K key = DeserializeKey(reader);
|
||||
V obj = DeserializeItem(reader);
|
||||
m_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.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;
|
||||
K key = default;
|
||||
V item = default;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case Operation.OP_ADD:
|
||||
case Operation.OP_SET:
|
||||
case Operation.OP_DIRTY:
|
||||
key = DeserializeKey(reader);
|
||||
item = DeserializeItem(reader);
|
||||
if (apply)
|
||||
{
|
||||
m_Objects[key] = item;
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_CLEAR:
|
||||
if (apply)
|
||||
{
|
||||
m_Objects.Clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case Operation.OP_REMOVE:
|
||||
key = DeserializeKey(reader);
|
||||
item = DeserializeItem(reader);
|
||||
if (apply)
|
||||
{
|
||||
m_Objects.Remove(key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (apply)
|
||||
{
|
||||
Callback?.Invoke(operation, key, item);
|
||||
}
|
||||
// we just skipped this change
|
||||
else
|
||||
{
|
||||
changesAhead--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_Objects.Clear();
|
||||
AddOperation(Operation.OP_CLEAR, default, default);
|
||||
}
|
||||
|
||||
public bool ContainsKey(K key) => m_Objects.ContainsKey(key);
|
||||
|
||||
public bool Remove(K key)
|
||||
{
|
||||
if (m_Objects.TryGetValue(key, out V item) && m_Objects.Remove(key))
|
||||
{
|
||||
AddOperation(Operation.OP_REMOVE, key, item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dirty(K index)
|
||||
{
|
||||
AddOperation(Operation.OP_DIRTY, index, m_Objects[index]);
|
||||
}
|
||||
|
||||
public V this[K i]
|
||||
{
|
||||
get => m_Objects[i];
|
||||
set
|
||||
{
|
||||
if (TryGetValue(i, out V val))
|
||||
{
|
||||
AddOperation(Operation.OP_SET, i, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOperation(Operation.OP_ADD, i, value);
|
||||
}
|
||||
m_Objects[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(K key, out V value) => m_Objects.TryGetValue(key, out value);
|
||||
|
||||
public void Add(K key, V value)
|
||||
{
|
||||
m_Objects.Add(key, value);
|
||||
AddOperation(Operation.OP_ADD, key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<K, V> item) => Add(item.Key, item.Value);
|
||||
|
||||
public bool Contains(KeyValuePair<K, V> item)
|
||||
{
|
||||
return TryGetValue(item.Key, out V val) && EqualityComparer<V>.Default.Equals(val, item.Value);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new System.ArgumentNullException("Array Is Null");
|
||||
}
|
||||
if (arrayIndex < 0 || arrayIndex > array.Length)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException("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<K,V> item in m_Objects)
|
||||
{
|
||||
array[i] = item;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<K, V> item)
|
||||
{
|
||||
bool result = m_Objects.Remove(item.Key);
|
||||
if (result)
|
||||
{
|
||||
AddOperation(Operation.OP_REMOVE, item.Key, item.Value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<K, V>> GetEnumerator() => ((IDictionary<K, V>)m_Objects).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IDictionary<K, V>)m_Objects).GetEnumerator();
|
||||
}
|
||||
}
|
11
Assets/Mirror/Runtime/SyncDictionary.cs.meta
Normal file
11
Assets/Mirror/Runtime/SyncDictionary.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b346c49cfdb668488a364c3023590e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
233
Assets/Mirror/Tests/SyncDictionaryTest.cs
Normal file
233
Assets/Mirror/Tests/SyncDictionaryTest.cs
Normal file
@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Mirror.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SyncDictionaryTest
|
||||
{
|
||||
public class SyncDictionaryIntString : SyncDictionary<int, string> {}
|
||||
|
||||
SyncDictionaryIntString serverSyncDictionary;
|
||||
SyncDictionaryIntString clientSyncDictionary;
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
private 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()
|
||||
{
|
||||
serverSyncDictionary = new SyncDictionaryIntString();
|
||||
clientSyncDictionary = new SyncDictionaryIntString();
|
||||
|
||||
// add some data to the list
|
||||
serverSyncDictionary.Add(0, "Hello");
|
||||
serverSyncDictionary.Add(1, "World");
|
||||
serverSyncDictionary.Add(2, "!");
|
||||
SerializeAllTo(serverSyncDictionary, clientSyncDictionary);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInit()
|
||||
{
|
||||
Dictionary<int, string> comparer = new Dictionary<int, string>
|
||||
{
|
||||
[0] = "Hello",
|
||||
[1] = "World",
|
||||
[2] = "!"
|
||||
};
|
||||
Assert.That(clientSyncDictionary[0], Is.EqualTo("Hello"));
|
||||
Assert.That(clientSyncDictionary, Is.EquivalentTo(comparer));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdd()
|
||||
{
|
||||
serverSyncDictionary.Add(4, "yay");
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(4), Is.EqualTo(true));
|
||||
Assert.That(clientSyncDictionary[4], Is.EqualTo("yay"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClear()
|
||||
{
|
||||
serverSyncDictionary.Clear();
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(serverSyncDictionary, Is.EquivalentTo(new SyncDictionaryIntString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSet()
|
||||
{
|
||||
serverSyncDictionary[1] = "yay";
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(1));
|
||||
Assert.That(clientSyncDictionary[1], Is.EqualTo("yay"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBareSet()
|
||||
{
|
||||
serverSyncDictionary[4] = "yay";
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(4));
|
||||
Assert.That(clientSyncDictionary[4], Is.EqualTo("yay"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBareSetNull()
|
||||
{
|
||||
serverSyncDictionary[4] = null;
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary[4], Is.Null);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConsecutiveSet()
|
||||
{
|
||||
serverSyncDictionary[1] = "yay";
|
||||
serverSyncDictionary[1] = "world";
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary[1], Is.EqualTo("world"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullSet()
|
||||
{
|
||||
serverSyncDictionary[1] = null;
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(1));
|
||||
Assert.That(clientSyncDictionary[1], Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRemove()
|
||||
{
|
||||
serverSyncDictionary.Remove(1);
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(1), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultSync()
|
||||
{
|
||||
serverSyncDictionary.Add(10, "1");
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
// add some delta and see if it applies
|
||||
serverSyncDictionary.Add(11, "2");
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.ContainsKey(10));
|
||||
Assert.That(clientSyncDictionary[10], Is.EqualTo("1"));
|
||||
Assert.That(clientSyncDictionary.ContainsKey(11));
|
||||
Assert.That(clientSyncDictionary[11], Is.EqualTo("2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContains()
|
||||
{
|
||||
Assert.That(!clientSyncDictionary.Contains(new KeyValuePair<int, string>(2, "Hello")));
|
||||
serverSyncDictionary[2] = "Hello";
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(clientSyncDictionary.Contains(new KeyValuePair<int, string>(2, "Hello")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CallbackTest()
|
||||
{
|
||||
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"));
|
||||
};
|
||||
serverSyncDictionary.Add(3, "yay");
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(called, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CallbackRemoveTest()
|
||||
{
|
||||
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);
|
||||
SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary);
|
||||
Assert.That(called, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountTest()
|
||||
{
|
||||
Assert.That(serverSyncDictionary.Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadOnlyTest()
|
||||
{
|
||||
Assert.That(serverSyncDictionary.IsReadOnly, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DirtyTest()
|
||||
{
|
||||
SyncDictionaryIntString serverList = new SyncDictionaryIntString();
|
||||
SyncDictionaryIntString clientList = new SyncDictionaryIntString();
|
||||
|
||||
// nothing to send
|
||||
Assert.That(serverList.IsDirty, Is.False);
|
||||
|
||||
// something has changed
|
||||
serverList.Add(15, "yay");
|
||||
Assert.That(serverList.IsDirty, Is.True);
|
||||
SerializeDeltaTo(serverList, clientList);
|
||||
|
||||
// data has been flushed, should go back to clear
|
||||
Assert.That(serverList.IsDirty, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadonlyTest()
|
||||
{
|
||||
SyncDictionaryIntString serverList = new SyncDictionaryIntString();
|
||||
SyncDictionaryIntString clientList = new SyncDictionaryIntString();
|
||||
|
||||
// data has been flushed, should go back to clear
|
||||
Assert.That(clientList.IsReadOnly, Is.False);
|
||||
|
||||
serverList.Add(20, "yay");
|
||||
serverList.Add(30, "hello");
|
||||
serverList.Add(35, "world");
|
||||
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(50, "fail"));
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Tests/SyncDictionaryTest.cs.meta
Normal file
11
Assets/Mirror/Tests/SyncDictionaryTest.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cadf48c3662efac4181b91f5c9c88774
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,3 +1,55 @@
|
||||
# SyncDictionary
|
||||
|
||||
Need description and code samples for SyncDictionary.
|
||||
A `SyncDictionary` is an associative array containing an unordered list of key, value pairs. Keys and values can be 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.
|
||||
- Struct with any of the above
|
||||
|
||||
SyncDictionaries work much like [SyncLists](SyncLists): when you make a change on the server the change is propagated to all clients and the Callback is called.
|
||||
|
||||
To use it, create a class that derives from `SyncDictionary` for your specific type. This is necesary because the Weaver will add methods to that class. Then add a field to your NetworkBehaviour class.
|
||||
|
||||
## Simple Example
|
||||
|
||||
```cs
|
||||
using UnityEngine;
|
||||
using Mirror;
|
||||
|
||||
public class ExamplePlayer : NetworkBehaviour
|
||||
{
|
||||
public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
|
||||
|
||||
public struct Item
|
||||
{
|
||||
public string name;
|
||||
public int hitPoints;
|
||||
public int durability;
|
||||
}
|
||||
|
||||
public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
|
||||
|
||||
public void OnStartServer()
|
||||
{
|
||||
Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
|
||||
Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
|
||||
Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
|
||||
Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
|
||||
}
|
||||
|
||||
private void OnStartClient()
|
||||
{
|
||||
// Equipment is already populated with anything the server set up
|
||||
// but we can subscribe to the callback in case it is updated later on
|
||||
Equipment.Callback += OnEquipmentChange;
|
||||
}
|
||||
|
||||
private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
|
||||
{
|
||||
// equipment changed, perhaps update the gameobject
|
||||
Debug.Log(op + " - " + key);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -71,3 +71,59 @@ public class MyScript : NetworkBehaviour
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SyncDictionaries
|
||||
|
||||
A `SyncDictionary` is an associative array containing an unordered list of key, value pairs. Keys and values can be 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.
|
||||
- Struct with any of the above
|
||||
|
||||
SyncDictionaries work much like [SyncLists](SyncLists): when you make a change on the server the change is propagated to all clients and the Callback is called.
|
||||
|
||||
To use it, create a class that derives from `SyncDictionary` for your specific type. This is necesary because the Weaver will add methods to that class. Then add a field to your NetworkBehaviour class.
|
||||
|
||||
### Simple Example
|
||||
|
||||
```cs
|
||||
using UnityEngine;
|
||||
using Mirror;
|
||||
|
||||
public class ExamplePlayer : NetworkBehaviour
|
||||
{
|
||||
public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
|
||||
|
||||
public struct Item
|
||||
{
|
||||
public string name;
|
||||
public int hitPoints;
|
||||
public int durability;
|
||||
}
|
||||
|
||||
public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
|
||||
|
||||
public void OnStartServer()
|
||||
{
|
||||
Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
|
||||
Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
|
||||
Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
|
||||
Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
|
||||
}
|
||||
|
||||
private void OnStartClient()
|
||||
{
|
||||
// Equipment is already populated with anything the server set up
|
||||
// but we can subscribe to the callback in case it is updated later on
|
||||
Equipment.Callback += OnEquipmentChange;
|
||||
}
|
||||
|
||||
private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
|
||||
{
|
||||
// equipment changed, perhaps update the gameobject
|
||||
Debug.Log(op + " - " + key);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user