mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
feat: allow types that inherit from Networkbehaviour to be used in syncvar's (#2471)
* adding MakeGeneric method * adding tests for NB syncvar+hook * adding functions to NetworkBehaviour * getting references to new functions * fixing NB read so it always reads same number of bytes as write * adding backing field and serialize for Nb Syncvar * extra test * adding ignore to transform test * fixing test
This commit is contained in:
parent
df89f90437
commit
c3e2a26378
@ -90,7 +90,8 @@ public static bool IsMultidimensionalArray(this TypeReference tr)
|
||||
public static bool IsNetworkIdentityField(this TypeReference tr)
|
||||
{
|
||||
return tr.Is<UnityEngine.GameObject>()
|
||||
|| tr.Is<NetworkIdentity>();
|
||||
|| tr.Is<NetworkIdentity>()
|
||||
|| tr.IsDerivedFrom<NetworkBehaviour>();
|
||||
}
|
||||
|
||||
public static bool CanBeResolved(this TypeReference parent)
|
||||
@ -120,6 +121,20 @@ public static bool CanBeResolved(this TypeReference parent)
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes T => Variable and imports function
|
||||
/// </summary>
|
||||
/// <param name="generic"></param>
|
||||
/// <param name="variableReference"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodReference MakeGeneric(this MethodReference generic, TypeReference variableReference)
|
||||
{
|
||||
GenericInstanceMethod instance = new GenericInstanceMethod(generic);
|
||||
instance.GenericArguments.Add(variableReference);
|
||||
|
||||
MethodReference readFunc = Weaver.CurrentAssembly.MainModule.ImportReference(instance);
|
||||
return readFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a method of a generic class such as ArraySegment`T.get_Count,
|
||||
|
@ -500,7 +500,11 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor worker, MethodDefinit
|
||||
// check for Hook function
|
||||
MethodDefinition hookMethod = SyncVarProcessor.GetHookMethod(netBehaviourSubclass, syncVar);
|
||||
|
||||
if (syncVar.FieldType.IsNetworkIdentityField())
|
||||
if (syncVar.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
DeserializeNetworkBehaviourField(syncVar, worker, deserialize, hookMethod);
|
||||
}
|
||||
else if (syncVar.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
DeserializeNetworkIdentityField(syncVar, worker, deserialize, hookMethod);
|
||||
}
|
||||
@ -617,6 +621,115 @@ void DeserializeNetworkIdentityField(FieldDefinition syncVar, ILProcessor worker
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [SyncVar] NetworkBehaviour
|
||||
/// </summary>
|
||||
/// <param name="syncVar"></param>
|
||||
/// <param name="worker"></param>
|
||||
/// <param name="deserialize"></param>
|
||||
/// <param name="initialState"></param>
|
||||
/// <param name="hookResult"></param>
|
||||
void DeserializeNetworkBehaviourField(FieldDefinition syncVar, ILProcessor worker, MethodDefinition deserialize, MethodDefinition hookMethod)
|
||||
{
|
||||
/*
|
||||
Generates code like:
|
||||
uint oldNetId = ___qNetId.netId;
|
||||
byte oldCompIndex = ___qNetId.componentIndex;
|
||||
T oldSyncVar = syncvar.getter;
|
||||
___qNetId.netId = reader.ReadPackedUInt32();
|
||||
___qNetId.componentIndex = reader.ReadByte();
|
||||
if (!SyncVarEqual(oldNetId, ref ___goNetId))
|
||||
{
|
||||
// getter returns GetSyncVarGameObject(___qNetId)
|
||||
OnSetQ(oldSyncVar, syncvar.getter);
|
||||
}
|
||||
*/
|
||||
|
||||
// GameObject/NetworkIdentity SyncVar:
|
||||
// OnSerialize sends writer.Write(go);
|
||||
// OnDeserialize reads to __netId manually so we can use
|
||||
// lookups in the getter (so it still works if objects
|
||||
// move in and out of range repeatedly)
|
||||
FieldDefinition netIdField = syncVarNetIds[syncVar];
|
||||
|
||||
// uint oldNetId = ___qNetId;
|
||||
VariableDefinition oldNetId = new VariableDefinition(WeaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
|
||||
deserialize.Body.Variables.Add(oldNetId);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netIdField));
|
||||
worker.Append(worker.Create(OpCodes.Stloc, oldNetId));
|
||||
|
||||
// GameObject/NetworkIdentity oldSyncVar = syncvar.getter;
|
||||
VariableDefinition oldSyncVar = new VariableDefinition(syncVar.FieldType);
|
||||
deserialize.Body.Variables.Add(oldSyncVar);
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, syncVar));
|
||||
worker.Append(worker.Create(OpCodes.Stloc, oldSyncVar));
|
||||
|
||||
// read id and store in netId field BEFORE calling the hook
|
||||
// -> this makes way more sense. by definition, the hook is
|
||||
// supposed to be called after it was changed. not before.
|
||||
// -> setting it BEFORE calling the hook fixes the following bug:
|
||||
// https://github.com/vis2k/Mirror/issues/1151 in host mode
|
||||
// where the value during the Hook call would call Cmds on
|
||||
// the host server, and they would all happen and compare
|
||||
// values BEFORE the hook even returned and hence BEFORE the
|
||||
// actual value was even set.
|
||||
// put 'this.' onto stack for 'this.netId' below
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
// reader. for 'reader.Read()' below
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_1));
|
||||
// Read()
|
||||
worker.Append(worker.Create(OpCodes.Call, Readers.GetReadFunc(WeaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>())));
|
||||
// netId
|
||||
worker.Append(worker.Create(OpCodes.Stfld, netIdField));
|
||||
|
||||
if (hookMethod != null)
|
||||
{
|
||||
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
|
||||
// because we send/receive the netID, not the GameObject/NetworkIdentity
|
||||
// but only if SyncVar changed. otherwise a client would
|
||||
// get hook calls for all initial values, even if they
|
||||
// didn't change from the default values on the client.
|
||||
// see also: https://github.com/vis2k/Mirror/issues/1278
|
||||
|
||||
// IMPORTANT: for GameObjects/NetworkIdentities we usually
|
||||
// use SyncVarGameObjectEqual to compare equality.
|
||||
// in this case however, we can just use
|
||||
// SyncVarEqual with the two uint netIds.
|
||||
// => this is easier weaver code because we don't
|
||||
// have to get the GameObject/NetworkIdentity
|
||||
// from the uint netId
|
||||
// => this is faster because we void one
|
||||
// GetComponent call for GameObjects to get
|
||||
// their NetworkIdentity when comparing.
|
||||
|
||||
// Generates: if (!SyncVarEqual);
|
||||
Instruction syncVarEqualLabel = worker.Create(OpCodes.Nop);
|
||||
|
||||
// 'this.' for 'this.SyncVarEqual'
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
// 'oldNetId'
|
||||
worker.Append(worker.Create(OpCodes.Ldloc, oldNetId));
|
||||
// 'ref this.__netId'
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, netIdField));
|
||||
// call the function
|
||||
GenericInstanceMethod syncVarEqualGm = new GenericInstanceMethod(WeaverTypes.syncVarEqualReference);
|
||||
syncVarEqualGm.GenericArguments.Add(netIdField.FieldType);
|
||||
worker.Append(worker.Create(OpCodes.Call, syncVarEqualGm));
|
||||
worker.Append(worker.Create(OpCodes.Brtrue, syncVarEqualLabel));
|
||||
|
||||
// call the hook
|
||||
// Generates: OnValueChanged(oldValue, this.syncVar);
|
||||
SyncVarProcessor.WriteCallHookMethodUsingField(worker, hookMethod, oldSyncVar, syncVar);
|
||||
|
||||
// Generates: end if (!SyncVarEqual);
|
||||
worker.Append(syncVarEqualLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// [SyncVar] int/float/struct/etc.?
|
||||
/// </summary>
|
||||
|
@ -107,6 +107,19 @@ public static MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.getSyncVarNetworkIdentityReference));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
|
||||
// this.
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, fd));
|
||||
MethodReference getFunc = WeaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
|
||||
worker.Append(worker.Create(OpCodes.Call, getFunc));
|
||||
worker.Append(worker.Create(OpCodes.Ret));
|
||||
}
|
||||
// [SyncVar] int, string, etc.
|
||||
else
|
||||
{
|
||||
@ -157,6 +170,15 @@ public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDef
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.syncVarNetworkIdentityEqualReference));
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldfld, netFieldId));
|
||||
|
||||
MethodReference getFunc = WeaverTypes.syncVarNetworkBehaviourEqualReference.MakeGeneric(fd.FieldType);
|
||||
worker.Append(worker.Create(OpCodes.Call, getFunc));
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
@ -207,6 +229,15 @@ public static MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDef
|
||||
|
||||
worker.Append(worker.Create(OpCodes.Call, WeaverTypes.setSyncVarNetworkIdentityReference));
|
||||
}
|
||||
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
// reference to netId Field to set
|
||||
worker.Append(worker.Create(OpCodes.Ldarg_0));
|
||||
worker.Append(worker.Create(OpCodes.Ldflda, netFieldId));
|
||||
|
||||
MethodReference getFunc = WeaverTypes.setSyncVarNetworkBehaviourReference.MakeGeneric(fd.FieldType);
|
||||
worker.Append(worker.Create(OpCodes.Call, getFunc));
|
||||
}
|
||||
else
|
||||
{
|
||||
// make generic version of SetSyncVar with field type
|
||||
@ -266,7 +297,16 @@ public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Diction
|
||||
|
||||
// GameObject/NetworkIdentity SyncVars have a new field for netId
|
||||
FieldDefinition netIdField = null;
|
||||
if (fd.FieldType.IsNetworkIdentityField())
|
||||
// NetworkBehaviour has different field type than other NetworkIdentityFields
|
||||
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>())
|
||||
{
|
||||
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
|
||||
FieldAttributes.Private,
|
||||
WeaverTypes.Import<NetworkBehaviour.NetworkBehaviourSyncVar>());
|
||||
|
||||
syncVarNetIds[fd] = netIdField;
|
||||
}
|
||||
else if (fd.FieldType.IsNetworkIdentityField())
|
||||
{
|
||||
netIdField = new FieldDefinition("___" + fd.Name + "NetId",
|
||||
FieldAttributes.Private,
|
||||
|
@ -142,10 +142,7 @@ private static MethodReference GetNetworkBehaviourReader(TypeReference variableR
|
||||
// uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
|
||||
MethodReference generic = WeaverTypes.readNetworkBehaviourGeneric;
|
||||
|
||||
GenericInstanceMethod instance = new GenericInstanceMethod(generic);
|
||||
instance.GenericArguments.Add(variableReference);
|
||||
|
||||
MethodReference readFunc = Weaver.CurrentAssembly.MainModule.ImportReference(instance);
|
||||
MethodReference readFunc = generic.MakeGeneric(variableReference);
|
||||
|
||||
// register function so it is added to Reader<T>
|
||||
// use Register instead of RegisterWriteFunc because this is not a generated function
|
||||
|
@ -44,6 +44,9 @@ public static class WeaverTypes
|
||||
public static MethodReference getSyncVarGameObjectReference;
|
||||
public static MethodReference setSyncVarNetworkIdentityReference;
|
||||
public static MethodReference getSyncVarNetworkIdentityReference;
|
||||
public static MethodReference syncVarNetworkBehaviourEqualReference;
|
||||
public static MethodReference setSyncVarNetworkBehaviourReference;
|
||||
public static MethodReference getSyncVarNetworkBehaviourReference;
|
||||
public static MethodReference registerCommandDelegateReference;
|
||||
public static MethodReference registerRpcDelegateReference;
|
||||
public static MethodReference getTypeReference;
|
||||
@ -116,6 +119,10 @@ public static void SetupTargetTypes(AssemblyDefinition currentAssembly)
|
||||
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarGameObject");
|
||||
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarNetworkIdentity");
|
||||
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarNetworkIdentity");
|
||||
syncVarNetworkBehaviourEqualReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SyncVarNetworkBehaviourEqual");
|
||||
setSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "SetSyncVarNetworkBehaviour");
|
||||
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, currentAssembly, "GetSyncVarNetworkBehaviour");
|
||||
|
||||
registerCommandDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterCommandDelegate");
|
||||
registerRpcDelegateReference = Resolvers.ResolveMethod(RemoteCallHelperType, currentAssembly, "RegisterRpcDelegate");
|
||||
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
|
||||
|
@ -418,6 +418,107 @@ protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdent
|
||||
return identityField;
|
||||
}
|
||||
|
||||
protected bool SyncVarNetworkBehaviourEqual<T>(T newBehaviour, NetworkBehaviourSyncVar syncField) where T : NetworkBehaviour
|
||||
{
|
||||
uint newNetId = 0;
|
||||
int newComponentIndex = 0;
|
||||
if (newBehaviour != null)
|
||||
{
|
||||
newNetId = newBehaviour.netId;
|
||||
newComponentIndex = newBehaviour.ComponentIndex;
|
||||
if (newNetId == 0)
|
||||
{
|
||||
logger.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newBehaviour + " has a zero netId. Maybe it is not spawned yet?");
|
||||
}
|
||||
}
|
||||
|
||||
// netId changed?
|
||||
return syncField.Equals(newNetId, newComponentIndex);
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] NetworkIdentities.
|
||||
protected void SetSyncVarNetworkBehaviour<T>(T newBehaviour, ref T behaviourField, ulong dirtyBit, ref NetworkBehaviourSyncVar syncField) where T : NetworkBehaviour
|
||||
{
|
||||
if (getSyncVarHookGuard(dirtyBit))
|
||||
return;
|
||||
|
||||
uint newNetId = 0;
|
||||
int componentIndex = 0;
|
||||
if (newBehaviour != null)
|
||||
{
|
||||
newNetId = newBehaviour.netId;
|
||||
componentIndex = newBehaviour.ComponentIndex;
|
||||
if (newNetId == 0)
|
||||
{
|
||||
logger.LogWarning($"{nameof(SetSyncVarNetworkBehaviour)} NetworkIdentity " + newBehaviour + " has a zero netId. Maybe it is not spawned yet?");
|
||||
}
|
||||
}
|
||||
|
||||
// old field for log
|
||||
NetworkBehaviourSyncVar oldField = syncField;
|
||||
syncField = new NetworkBehaviourSyncVar(newNetId, componentIndex);
|
||||
|
||||
SetDirtyBit(dirtyBit);
|
||||
|
||||
// assign new one on the server, and in case we ever need it on client too
|
||||
behaviourField = newBehaviour;
|
||||
|
||||
if (logger.LogEnabled()) logger.Log($"SetSyncVarNetworkBehaviour NetworkIdentity {GetType().Name} bit [{dirtyBit}] netIdField:{oldField}->{syncField}");
|
||||
}
|
||||
|
||||
// helper function for [SyncVar] NetworkIdentities.
|
||||
// -> ref GameObject as second argument makes OnDeserialize processing easier
|
||||
protected T GetSyncVarNetworkBehaviour<T>(NetworkBehaviourSyncVar syncNetBehaviour, ref T behaviourField) where T : NetworkBehaviour
|
||||
{
|
||||
// server always uses the field
|
||||
if (isServer)
|
||||
{
|
||||
return behaviourField;
|
||||
}
|
||||
|
||||
// client always looks up based on netId because objects might get in and out of range
|
||||
// over and over again, which shouldn't null them forever
|
||||
|
||||
if (!NetworkIdentity.spawned.TryGetValue(syncNetBehaviour.netId, out NetworkIdentity identity))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
behaviourField = identity.NetworkBehaviours[syncNetBehaviour.componentIndex] as T;
|
||||
return behaviourField;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// backing field for sync NetworkBehaviour
|
||||
/// </summary>
|
||||
public struct NetworkBehaviourSyncVar : IEquatable<NetworkBehaviourSyncVar>
|
||||
{
|
||||
public uint netId;
|
||||
// limtied to 255 behaviours per identity
|
||||
public byte componentIndex;
|
||||
|
||||
public NetworkBehaviourSyncVar(uint netId, int componentIndex) : this()
|
||||
{
|
||||
this.netId = netId;
|
||||
this.componentIndex = (byte)componentIndex;
|
||||
}
|
||||
|
||||
public bool Equals(NetworkBehaviourSyncVar other)
|
||||
{
|
||||
return other.netId == netId && other.componentIndex == componentIndex;
|
||||
}
|
||||
|
||||
public bool Equals(uint netId, int componentIndex)
|
||||
{
|
||||
return this.netId == netId && this.componentIndex == componentIndex;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[netId:{netId} compIndex:{componentIndex}]";
|
||||
}
|
||||
}
|
||||
|
||||
protected bool SyncVarEqual<T>(T value, ref T fieldValue)
|
||||
{
|
||||
// newly initialized or changed value?
|
||||
|
@ -308,13 +308,19 @@ public static NetworkIdentity ReadNetworkIdentity(this NetworkReader reader)
|
||||
|
||||
public static NetworkBehaviour ReadNetworkBehaviour(this NetworkReader reader)
|
||||
{
|
||||
NetworkIdentity identity = reader.ReadNetworkIdentity();
|
||||
if (identity == null)
|
||||
uint netId = reader.ReadUInt32();
|
||||
if (netId == 0)
|
||||
return null;
|
||||
|
||||
// if netId is not 0, then index is also sent to read before returning
|
||||
byte componentIndex = reader.ReadByte();
|
||||
|
||||
if (!NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity))
|
||||
{
|
||||
if (logger.WarnEnabled()) logger.LogFormat(LogType.Warning, "ReadNetworkBehaviour netId:{0} not found in spawned", netId);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte componentIndex = reader.ReadByte();
|
||||
return identity.NetworkBehaviours[componentIndex];
|
||||
}
|
||||
|
||||
@ -323,6 +329,20 @@ public static T ReadNetworkBehaviour<T>(this NetworkReader reader) where T : Net
|
||||
return reader.ReadNetworkBehaviour() as T;
|
||||
}
|
||||
|
||||
public static NetworkBehaviour.NetworkBehaviourSyncVar ReadNetworkBehaviourSyncVar(this NetworkReader reader)
|
||||
{
|
||||
uint netId = reader.ReadUInt32();
|
||||
byte componentIndex = default;
|
||||
|
||||
// if netId is not 0, then index is also sent to read before returning
|
||||
if (netId != 0)
|
||||
{
|
||||
componentIndex = reader.ReadByte();
|
||||
}
|
||||
|
||||
return new NetworkBehaviour.NetworkBehaviourSyncVar(netId, componentIndex);
|
||||
}
|
||||
|
||||
public static List<T> ReadList<T>(this NetworkReader reader)
|
||||
{
|
||||
int length = reader.ReadInt32();
|
||||
|
@ -3,6 +3,7 @@
|
||||
using Mirror.Tests.RemoteAttrributeTest;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Mirror.Tests
|
||||
{
|
||||
@ -1008,6 +1009,37 @@ public void TestNullList()
|
||||
Assert.That(readList, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadNetworkIdentityGivesWarningWhenNotFound()
|
||||
{
|
||||
const uint netId = 423;
|
||||
NetworkWriter writer = new NetworkWriter();
|
||||
writer.WriteUInt32(netId);
|
||||
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||
|
||||
LogAssert.Expect(LogType.Warning, $"ReadNetworkIdentity netId:{netId} not found in spawned");
|
||||
NetworkIdentity actual = reader.ReadNetworkIdentity();
|
||||
Assert.That(actual, Is.Null);
|
||||
|
||||
Assert.That(reader.Position, Is.EqualTo(4), "should read 4 bytes");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReadNetworkBehaviourGivesWarningWhenNotFound()
|
||||
{
|
||||
const uint netId = 424;
|
||||
NetworkWriter writer = new NetworkWriter();
|
||||
writer.WriteUInt32(netId);
|
||||
writer.WriteByte(0);
|
||||
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||
|
||||
LogAssert.Expect(LogType.Warning, $"ReadNetworkBehaviour netId:{netId} not found in spawned");
|
||||
NetworkBehaviour actual = reader.ReadNetworkBehaviour();
|
||||
Assert.That(actual, Is.Null);
|
||||
|
||||
Assert.That(reader.Position, Is.EqualTo(5), "should read 5 bytes when netId is not 0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNetworkBehaviour()
|
||||
{
|
||||
@ -1056,7 +1088,9 @@ public void TestNetworkBehaviourNull()
|
||||
|
||||
NetworkReader reader = new NetworkReader(bytes);
|
||||
RpcNetworkIdentityBehaviour actual = reader.ReadNetworkBehaviour<RpcNetworkIdentityBehaviour>();
|
||||
Assert.That(actual, Is.EqualTo(null), "Read should find the same behaviour as written");
|
||||
Assert.That(actual, Is.Null, "should read null");
|
||||
|
||||
Assert.That(reader.Position, Is.EqualTo(4), "should read 4 bytes when netid is 0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Tests
|
||||
namespace Mirror.Tests.SyncVarTests
|
||||
{
|
||||
class HookBehaviour : NetworkBehaviour
|
||||
{
|
||||
@ -44,6 +43,20 @@ void OnValueChanged(NetworkIdentity oldValue, NetworkIdentity newValue)
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkBehaviourHookBehaviour : NetworkBehaviour
|
||||
{
|
||||
[SyncVar(hook = nameof(OnValueChanged))]
|
||||
public NetworkBehaviourHookBehaviour value = null;
|
||||
|
||||
public event Action<NetworkBehaviourHookBehaviour, NetworkBehaviourHookBehaviour> HookCalled;
|
||||
|
||||
void OnValueChanged(NetworkBehaviourHookBehaviour oldValue, NetworkBehaviourHookBehaviour newValue)
|
||||
{
|
||||
HookCalled.Invoke(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StaticHookBehaviour : NetworkBehaviour
|
||||
{
|
||||
[SyncVar(hook = nameof(OnValueChanged))]
|
||||
@ -99,70 +112,8 @@ protected override void OnValueChanged(int oldValue, int newValue)
|
||||
}
|
||||
|
||||
|
||||
public class SyncVarHookTest
|
||||
public class SyncVarHookTest : SyncVarTestBase
|
||||
{
|
||||
private List<GameObject> spawned = new List<GameObject>();
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
foreach (GameObject item in spawned)
|
||||
{
|
||||
GameObject.DestroyImmediate(item);
|
||||
}
|
||||
spawned.Clear();
|
||||
|
||||
NetworkIdentity.spawned.Clear();
|
||||
}
|
||||
|
||||
|
||||
T CreateObject<T>() where T : NetworkBehaviour
|
||||
{
|
||||
GameObject gameObject = new GameObject();
|
||||
spawned.Add(gameObject);
|
||||
|
||||
gameObject.AddComponent<NetworkIdentity>();
|
||||
|
||||
T behaviour = gameObject.AddComponent<T>();
|
||||
behaviour.syncInterval = 0f;
|
||||
|
||||
return behaviour;
|
||||
}
|
||||
|
||||
NetworkIdentity CreateNetworkIdentity(uint netId)
|
||||
{
|
||||
GameObject gameObject = new GameObject();
|
||||
spawned.Add(gameObject);
|
||||
|
||||
NetworkIdentity networkIdentity = gameObject.AddComponent<NetworkIdentity>();
|
||||
networkIdentity.netId = netId;
|
||||
NetworkIdentity.spawned[netId] = networkIdentity;
|
||||
return networkIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="serverObject"></param>
|
||||
/// <param name="clientObject"></param>
|
||||
/// <param name="initialState"></param>
|
||||
/// <returns>If data was written by OnSerialize</returns>
|
||||
public static bool SyncToClient<T>(T serverObject, T clientObject, bool initialState) where T : NetworkBehaviour
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter();
|
||||
bool written = serverObject.OnSerialize(writer, initialState);
|
||||
|
||||
NetworkReader reader = new NetworkReader(writer.ToArray());
|
||||
clientObject.OnDeserialize(reader, initialState);
|
||||
|
||||
int writeLength = writer.Length;
|
||||
int readLength = reader.Position;
|
||||
Assert.That(writeLength == readLength, $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}");
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
@ -296,6 +247,33 @@ public void NetworkIdentityHook_HookCalledWhenSyncingChangedValue(bool intialSta
|
||||
Assert.That(callCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void NetworkBehaviourHook_HookCalledWhenSyncingChangedValue(bool intialState)
|
||||
{
|
||||
NetworkBehaviourHookBehaviour serverObject = CreateObject<NetworkBehaviourHookBehaviour>();
|
||||
NetworkBehaviourHookBehaviour clientObject = CreateObject<NetworkBehaviourHookBehaviour>();
|
||||
|
||||
NetworkBehaviourHookBehaviour clientValue = null;
|
||||
NetworkBehaviourHookBehaviour serverValue = CreateNetworkIdentity(2033).gameObject.AddComponent<NetworkBehaviourHookBehaviour>();
|
||||
|
||||
serverObject.value = serverValue;
|
||||
clientObject.value = clientValue;
|
||||
|
||||
int callCount = 0;
|
||||
clientObject.HookCalled += (oldValue, newValue) =>
|
||||
{
|
||||
callCount++;
|
||||
Assert.That(oldValue, Is.EqualTo(clientValue));
|
||||
Assert.That(newValue, Is.EqualTo(serverValue));
|
||||
};
|
||||
|
||||
bool written = SyncToClient(serverObject, clientObject, intialState);
|
||||
Assert.IsTrue(written);
|
||||
Assert.That(callCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Tests
|
||||
namespace Mirror.Tests.SyncVarTests
|
||||
{
|
||||
|
||||
class MockPlayer : NetworkBehaviour
|
||||
{
|
||||
public struct Guild
|
||||
@ -16,9 +16,29 @@ public struct Guild
|
||||
|
||||
}
|
||||
|
||||
public class SyncVarTest
|
||||
class SyncVarGameObject : NetworkBehaviour
|
||||
{
|
||||
[SyncVar]
|
||||
public GameObject value;
|
||||
}
|
||||
class SyncVarNetworkIdentity : NetworkBehaviour
|
||||
{
|
||||
[SyncVar]
|
||||
public NetworkIdentity value;
|
||||
}
|
||||
class SyncVarTransform : NetworkBehaviour
|
||||
{
|
||||
[SyncVar]
|
||||
public Transform value;
|
||||
}
|
||||
class SyncVarNetworkBehaviour : NetworkBehaviour
|
||||
{
|
||||
[SyncVar]
|
||||
public SyncVarNetworkBehaviour value;
|
||||
}
|
||||
|
||||
public class SyncVarTest : SyncVarTestBase
|
||||
{
|
||||
[Test]
|
||||
public void TestSettingStruct()
|
||||
{
|
||||
@ -137,5 +157,206 @@ public void TestSynchronizingObjects()
|
||||
// check that the syncvars got updated
|
||||
Assert.That(player2.guild.name, Is.EqualTo("Back street boys"), "Data should be synchronized");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncsGameobject(bool initialState)
|
||||
{
|
||||
SyncVarGameObject serverObject = CreateObject<SyncVarGameObject>();
|
||||
SyncVarGameObject clientObject = CreateObject<SyncVarGameObject>();
|
||||
|
||||
GameObject serverValue = CreateNetworkIdentity(2044).gameObject;
|
||||
|
||||
serverObject.value = serverValue;
|
||||
clientObject.value = null;
|
||||
|
||||
bool written = SyncToClient(serverObject, clientObject, initialState);
|
||||
Assert.IsTrue(written);
|
||||
Assert.That(clientObject.value, Is.EqualTo(serverValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncIdentity(bool initialState)
|
||||
{
|
||||
SyncVarNetworkIdentity serverObject = CreateObject<SyncVarNetworkIdentity>();
|
||||
SyncVarNetworkIdentity clientObject = CreateObject<SyncVarNetworkIdentity>();
|
||||
|
||||
NetworkIdentity serverValue = CreateNetworkIdentity(2045);
|
||||
|
||||
serverObject.value = serverValue;
|
||||
clientObject.value = null;
|
||||
|
||||
bool written = SyncToClient(serverObject, clientObject, initialState);
|
||||
Assert.IsTrue(written);
|
||||
Assert.That(clientObject.value, Is.EqualTo(serverValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncTransform(bool initialState)
|
||||
{
|
||||
SyncVarTransform serverObject = CreateObject<SyncVarTransform>();
|
||||
SyncVarTransform clientObject = CreateObject<SyncVarTransform>();
|
||||
|
||||
Transform serverValue = CreateNetworkIdentity(2045).transform;
|
||||
|
||||
serverObject.value = serverValue;
|
||||
clientObject.value = null;
|
||||
|
||||
bool written = SyncToClient(serverObject, clientObject, initialState);
|
||||
Assert.IsTrue(written);
|
||||
Assert.That(clientObject.value, Is.EqualTo(serverValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncsBehaviour(bool initialState)
|
||||
{
|
||||
SyncVarNetworkBehaviour serverObject = CreateObject<SyncVarNetworkBehaviour>();
|
||||
SyncVarNetworkBehaviour clientObject = CreateObject<SyncVarNetworkBehaviour>();
|
||||
|
||||
SyncVarNetworkBehaviour serverValue = CreateNetworkIdentity(2046).gameObject.AddComponent<SyncVarNetworkBehaviour>();
|
||||
|
||||
serverObject.value = serverValue;
|
||||
clientObject.value = null;
|
||||
|
||||
bool written = SyncToClient(serverObject, clientObject, initialState);
|
||||
Assert.IsTrue(written);
|
||||
Assert.That(clientObject.value, Is.EqualTo(serverValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncsMultipleBehaviour(bool initialState)
|
||||
{
|
||||
SyncVarNetworkBehaviour serverObject = CreateObject<SyncVarNetworkBehaviour>();
|
||||
SyncVarNetworkBehaviour clientObject = CreateObject<SyncVarNetworkBehaviour>();
|
||||
|
||||
NetworkIdentity identity = CreateNetworkIdentity(2046);
|
||||
SyncVarNetworkBehaviour behaviour1 = identity.gameObject.AddComponent<SyncVarNetworkBehaviour>();
|
||||
SyncVarNetworkBehaviour behaviour2 = identity.gameObject.AddComponent<SyncVarNetworkBehaviour>();
|
||||
// create array/set indexs
|
||||
_ = identity.NetworkBehaviours;
|
||||
|
||||
int index1 = behaviour1.ComponentIndex;
|
||||
int index2 = behaviour2.ComponentIndex;
|
||||
|
||||
// check components of same type have different indexes
|
||||
Assert.That(index1, Is.Not.EqualTo(index2));
|
||||
|
||||
// check behaviour 1 can be synced
|
||||
serverObject.value = behaviour1;
|
||||
clientObject.value = null;
|
||||
|
||||
bool written1 = SyncToClient(serverObject, clientObject, initialState);
|
||||
Assert.IsTrue(written1);
|
||||
Assert.That(clientObject.value, Is.EqualTo(behaviour1));
|
||||
|
||||
// check that behaviour 2 can be synced
|
||||
serverObject.value = behaviour2;
|
||||
clientObject.value = null;
|
||||
|
||||
bool written2 = SyncToClient(serverObject, clientObject, initialState);
|
||||
Assert.IsTrue(written2);
|
||||
Assert.That(clientObject.value, Is.EqualTo(behaviour2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncVarCacheNetidForGameObject(bool initialState)
|
||||
{
|
||||
SyncVarCacheNetidForGeneric<SyncVarGameObject, GameObject>(
|
||||
(obj) => obj.value,
|
||||
(obj, value) => obj.value = value,
|
||||
(identity) => identity.gameObject,
|
||||
initialState
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncVarCacheNetidForIdentity(bool initialState)
|
||||
{
|
||||
SyncVarCacheNetidForGeneric<SyncVarNetworkIdentity, NetworkIdentity>(
|
||||
(obj) => obj.value,
|
||||
(obj, value) => obj.value = value,
|
||||
(identity) => identity,
|
||||
initialState
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
[Ignore("Transform backing field has not been implemented yet")]
|
||||
public void SyncVarCacheNetidForTransform(bool initialState)
|
||||
{
|
||||
SyncVarCacheNetidForGeneric<SyncVarTransform, Transform>(
|
||||
(obj) => obj.value,
|
||||
(obj, value) => obj.value = value,
|
||||
(identity) => identity.transform,
|
||||
initialState
|
||||
);
|
||||
}
|
||||
[Test]
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void SyncVarCacheNetidForBehaviour(bool initialState)
|
||||
{
|
||||
SyncVarCacheNetidForGeneric<SyncVarNetworkBehaviour, SyncVarNetworkBehaviour>(
|
||||
(obj) => obj.value,
|
||||
(obj, value) => obj.value = value,
|
||||
(identity) => identity.gameObject.AddComponent<SyncVarNetworkBehaviour>(),
|
||||
initialState
|
||||
);
|
||||
}
|
||||
|
||||
private void SyncVarCacheNetidForGeneric<TBehaviour, TValue>(
|
||||
Func<TBehaviour, TValue> getField,
|
||||
Action<TBehaviour, TValue> setField,
|
||||
Func<NetworkIdentity, TValue> getCreatedValue,
|
||||
bool initialState)
|
||||
where TValue : UnityEngine.Object
|
||||
where TBehaviour : NetworkBehaviour
|
||||
{
|
||||
TBehaviour serverObject = CreateObject<TBehaviour>();
|
||||
TBehaviour clientObject = CreateObject<TBehaviour>();
|
||||
|
||||
NetworkIdentity identity = CreateNetworkIdentity(2047);
|
||||
TValue serverValue = getCreatedValue(identity);
|
||||
|
||||
Assert.That(serverValue, Is.Not.Null, "getCreatedValue should not return null");
|
||||
|
||||
setField(serverObject, serverValue);
|
||||
setField(clientObject, null);
|
||||
|
||||
// write server data
|
||||
bool written = ServerWrite(serverObject, initialState, out ArraySegment<byte> data, out int writeLength);
|
||||
Assert.IsTrue(written, "did not write");
|
||||
|
||||
// remove identity from collection
|
||||
NetworkIdentity.spawned.Remove(identity.netId);
|
||||
|
||||
// read client data, this should be cached in field
|
||||
ClientRead(clientObject, initialState, data, writeLength);
|
||||
|
||||
// check field shows as null
|
||||
Assert.That(getField(clientObject), Is.EqualTo(null), "field should return null");
|
||||
|
||||
// add identity back to collection
|
||||
NetworkIdentity.spawned.Add(identity.netId, identity);
|
||||
|
||||
// check field finds value
|
||||
Assert.That(getField(clientObject), Is.EqualTo(serverValue), "fields should return serverValue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
81
Assets/Mirror/Tests/Editor/SyncVarTestBase.cs
Normal file
81
Assets/Mirror/Tests/Editor/SyncVarTestBase.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.Tests
|
||||
{
|
||||
public class SyncVarTestBase
|
||||
{
|
||||
readonly List<GameObject> spawned = new List<GameObject>();
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
foreach (GameObject item in spawned)
|
||||
{
|
||||
GameObject.DestroyImmediate(item);
|
||||
}
|
||||
spawned.Clear();
|
||||
|
||||
NetworkIdentity.spawned.Clear();
|
||||
}
|
||||
|
||||
|
||||
protected T CreateObject<T>() where T : NetworkBehaviour
|
||||
{
|
||||
GameObject gameObject = new GameObject();
|
||||
spawned.Add(gameObject);
|
||||
|
||||
gameObject.AddComponent<NetworkIdentity>();
|
||||
|
||||
T behaviour = gameObject.AddComponent<T>();
|
||||
behaviour.syncInterval = 0f;
|
||||
|
||||
return behaviour;
|
||||
}
|
||||
|
||||
protected NetworkIdentity CreateNetworkIdentity(uint netId)
|
||||
{
|
||||
GameObject gameObject = new GameObject();
|
||||
spawned.Add(gameObject);
|
||||
|
||||
NetworkIdentity networkIdentity = gameObject.AddComponent<NetworkIdentity>();
|
||||
networkIdentity.netId = netId;
|
||||
NetworkIdentity.spawned[netId] = networkIdentity;
|
||||
return networkIdentity;
|
||||
}
|
||||
|
||||
/// <returns>If data was written by OnSerialize</returns>
|
||||
public static bool SyncToClient<T>(T serverObject, T clientObject, bool initialState) where T : NetworkBehaviour
|
||||
{
|
||||
bool written = ServerWrite(serverObject, initialState, out ArraySegment<byte> data, out int writeLength);
|
||||
|
||||
ClientRead(clientObject, initialState, data, writeLength);
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
public static bool ServerWrite<T>(T serverObject, bool initialState, out ArraySegment<byte> data, out int writeLength) where T : NetworkBehaviour
|
||||
{
|
||||
NetworkWriter writer = new NetworkWriter();
|
||||
bool written = serverObject.OnSerialize(writer, initialState);
|
||||
writeLength = writer.Length;
|
||||
data = writer.ToArraySegment();
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
public static void ClientRead<T>(T clientObject, bool initialState, ArraySegment<byte> data, int writeLength) where T : NetworkBehaviour
|
||||
{
|
||||
NetworkReader reader = new NetworkReader(data);
|
||||
clientObject.OnDeserialize(reader, initialState);
|
||||
|
||||
int readLength = reader.Position;
|
||||
Assert.That(writeLength == readLength,
|
||||
$"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n" +
|
||||
$" writeLength={writeLength}\n" +
|
||||
$" readLength={readLength}");
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Mirror/Tests/Editor/SyncVarTestBase.cs.meta
Normal file
11
Assets/Mirror/Tests/Editor/SyncVarTestBase.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fbd8fff4a0795d49a0b122554ed6b13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user