Skip to content

Commit

Permalink
feat: send NI, NB and GO in RPC (#528)
Browse files Browse the repository at this point in the history
It is now valid to send NetworkIdentity, NetworkBehaviors and GameObjects as RPC parameters.
NG will serialize the gameobject id and component index where appropriate and do the lookup in the other side.
  • Loading branch information
paulpach committed Jan 11, 2021
1 parent 1106fcb commit 428ca63
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 18 deletions.
27 changes: 27 additions & 0 deletions Assets/Mirror/Runtime/NetworkReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,32 @@ public static Uri ReadUri(this NetworkReader reader)
{
return new Uri(reader.ReadString());
}

public static NetworkBehaviour ReadNetworkBehaviour(this NetworkReader reader)
{
NetworkIdentity identity = reader.ReadNetworkIdentity();
if (identity == null)
{
return null;
}

byte componentIndex = reader.ReadByte();
return identity.NetworkBehaviours[componentIndex];
}

public static T ReadNetworkBehaviour<T>(this NetworkReader reader) where T : NetworkBehaviour
{
return reader.ReadNetworkBehaviour() as T;
}

public static GameObject ReadGameObject(this NetworkReader reader)
{
NetworkIdentity identity = reader.ReadNetworkIdentity();
if (identity == null)
{
return null;
}
return identity.gameObject;
}
}
}
24 changes: 24 additions & 0 deletions Assets/Mirror/Runtime/NetworkWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -516,5 +516,29 @@ public static void WriteArraySegment<T>(this NetworkWriter writer, ArraySegment<
writer.Write(segment.Array[segment.Offset + i]);
}
}

public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehaviour value)
{
if (value == null)
{
writer.WriteUInt32(0);
return;
}
writer.WriteUInt32(value.NetId);
writer.WriteByte((byte)value.ComponentIndex);
}

public static void WriteGameObject(this NetworkWriter writer, GameObject value)
{
if (value == null)
{
writer.WriteUInt32(0);
return;
}
NetworkIdentity identity = value.GetComponent<NetworkIdentity>();
if (identity == null)
throw new InvalidOperationException($"Cannot send GameObject without a NetworkIdentity {value.name}");
writer.WriteNetworkIdentity(identity);
}
}
}
16 changes: 11 additions & 5 deletions Assets/Mirror/Weaver/Readers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ public MethodReference GetReadFunc(TypeReference typeReference)
{
return GenerateEnumReadFunc(typeReference);
}
if (variableDefinition.IsDerivedFrom<NetworkBehaviour>())
{
return GetNetworkBehaviourReader(typeReference);
}
if (variableDefinition.IsDerivedFrom<Component>())
{
logger.Error($"Cannot generate reader for component type {typeReference.Name}. Use a supported type or provide a custom reader", typeReference);
Expand All @@ -87,11 +91,6 @@ public MethodReference GetReadFunc(TypeReference typeReference)
logger.Error($"Cannot generate reader for {typeReference.Name}. Use a supported type or provide a custom reader", typeReference);
return null;
}
if (typeReference.Is<GameObject>())
{
logger.Error($"Cannot generate reader for {typeReference.Name}. Use a supported type or provide a custom reader", typeReference);
return null;
}
if (typeReference.IsByReference)
{
// error??
Expand All @@ -117,6 +116,13 @@ public MethodReference GetReadFunc(TypeReference typeReference)
return GenerateClassOrStructReadFunction(typeReference);
}

private MethodReference GetNetworkBehaviourReader(TypeReference typeReference)
{
MethodReference readFunc = module.ImportReference<NetworkReader>((reader) => reader.ReadNetworkBehaviour());
Register(typeReference, readFunc);
return readFunc;
}

void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
{
readFuncs[typeReference] = newReaderFunc;
Expand Down
17 changes: 12 additions & 5 deletions Assets/Mirror/Weaver/Writers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public MethodReference GetWriteFunc(TypeReference typeReference)
}

/// <exception cref="GenerateWriterException">Throws when writer could not be generated for type</exception>
MethodDefinition GenerateWriter(TypeReference typeReference)
MethodReference GenerateWriter(TypeReference typeReference)
{
if (typeReference.IsByReference)
{
Expand Down Expand Up @@ -112,6 +112,10 @@ MethodDefinition GenerateWriter(TypeReference typeReference)
{
throw new GenerateWriterException($"{typeReference.Name} is not a supported type. Use a supported type or provide a custom writer", typeReference);
}
if (typeDefinition.IsDerivedFrom<NetworkBehaviour>())
{
return GetNetworkBehaviourWriter(typeReference);
}
if (typeDefinition.IsDerivedFrom<UnityEngine.Component>())
{
throw new GenerateWriterException($"Cannot generate writer for component type {typeReference.Name}. Use a supported type or provide a custom writer", typeReference);
Expand All @@ -124,10 +128,6 @@ MethodDefinition GenerateWriter(TypeReference typeReference)
{
throw new GenerateWriterException($"Cannot generate writer for {typeReference.Name}. Use a supported type or provide a custom writer", typeReference);
}
if (typeReference.Is<UnityEngine.GameObject>())
{
throw new GenerateWriterException($"Cannot generate writer for {typeReference.Name}. Use a supported type or provide a custom writer", typeReference);
}
if (typeDefinition.HasGenericParameters)
{
throw new GenerateWriterException($"Cannot generate writer for generic type {typeReference.Name}. Use a supported type or provide a custom writer", typeReference);
Expand All @@ -145,6 +145,13 @@ MethodDefinition GenerateWriter(TypeReference typeReference)
return GenerateClassOrStructWriterFunction(typeReference);
}

private MethodReference GetNetworkBehaviourWriter(TypeReference typeReference)
{
MethodReference writeFunc = module.ImportReference<NetworkWriter>((nw) => nw.WriteNetworkBehaviour(default));
Register(typeReference, writeFunc);
return writeFunc;
}

private MethodDefinition GenerateEnumWriteFunc(TypeReference typeReference)
{
MethodDefinition writerFunc = GenerateWriterFunc(typeReference);
Expand Down
231 changes: 231 additions & 0 deletions Assets/Tests/Runtime/ClientServer/NetworkBehaviorRpcTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
using System;
using System.Collections;
using Cysharp.Threading.Tasks;
using NSubstitute;
using UnityEngine;
using UnityEngine.TestTools;
using System.Linq;
using NUnit.Framework;

namespace Mirror.Tests
{
public class SampleBehaviorWithRpc : NetworkBehaviour
{
public event Action<NetworkIdentity> onSendNetworkIdentityCalled;
public event Action<GameObject> onSendGameObjectCalled;
public event Action<NetworkBehaviour> onSendNetworkBehaviourCalled;
public event Action<SampleBehaviorWithRpc> onSendNetworkBehaviourDerivedCalled;

[ClientRpc]
public void SendNetworkIdentity(NetworkIdentity value)
{
onSendNetworkIdentityCalled?.Invoke(value);
}

[ClientRpc]
public void SendGameObject(GameObject value)
{
onSendGameObjectCalled?.Invoke(value);
}

[ClientRpc]
public void SendNetworkBehaviour(NetworkBehaviour value)
{
onSendNetworkBehaviourCalled?.Invoke(value);
}

[ClientRpc]
public void SendNetworkBehaviourDerived(SampleBehaviorWithRpc value)
{
onSendNetworkBehaviourDerivedCalled?.Invoke(value);
}

[ServerRpc]
public void SendNetworkIdentityToServer(NetworkIdentity value)
{
onSendNetworkIdentityCalled?.Invoke(value);
}

[ServerRpc]
public void SendGameObjectToServer(GameObject value)
{
onSendGameObjectCalled?.Invoke(value);
}

[ServerRpc]
public void SendNetworkBehaviourToServer(NetworkBehaviour value)
{
onSendNetworkBehaviourCalled?.Invoke(value);
}

[ServerRpc]
public void SendNetworkBehaviourDerivedToServer(SampleBehaviorWithRpc value)
{
onSendNetworkBehaviourDerivedCalled?.Invoke(value);
}


}

public class NetworkBehaviorRPCTest : ClientServerSetup<SampleBehaviorWithRpc>
{
[UnityTest]
public IEnumerator SendNetworkIdentity() => UniTask.ToCoroutine(async () =>
{
Action<NetworkIdentity> callback = Substitute.For<Action<NetworkIdentity>>();
clientComponent.onSendNetworkIdentityCalled += callback;
serverComponent.SendNetworkIdentity(serverIdentity);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(clientIdentity);
});

[UnityTest]
public IEnumerator SendNetworkBehavior() => UniTask.ToCoroutine(async () =>
{
Action<NetworkBehaviour> callback = Substitute.For<Action<NetworkBehaviour>>();
clientComponent.onSendNetworkBehaviourCalled += callback;
serverComponent.SendNetworkBehaviour(serverComponent);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(clientComponent);
});

[UnityTest]
public IEnumerator SendNetworkBehaviorChild() => UniTask.ToCoroutine(async () =>
{
Action<SampleBehaviorWithRpc> callback = Substitute.For<Action<SampleBehaviorWithRpc>>();
clientComponent.onSendNetworkBehaviourDerivedCalled += callback;
serverComponent.SendNetworkBehaviourDerived(serverComponent);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(clientComponent);
});

[UnityTest]
public IEnumerator SendGameObject() => UniTask.ToCoroutine(async () =>
{
Action<GameObject> callback = Substitute.For<Action<GameObject>>();
clientComponent.onSendGameObjectCalled += callback;
serverComponent.SendGameObject(serverPlayerGO);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(clientPlayerGO);
});

[Test]
public void SendInvalidGO()
{
Action<GameObject> callback = Substitute.For<Action<GameObject>>();
clientComponent.onSendGameObjectCalled += callback;

// this object does not have a NI, so this should error out
Assert.Throws<InvalidOperationException>(() =>
{
serverComponent.SendGameObject(serverGo);
});
}

[UnityTest]
public IEnumerator SendNullNetworkIdentity() => UniTask.ToCoroutine(async () =>
{
Action<NetworkIdentity> callback = Substitute.For<Action<NetworkIdentity>>();
clientComponent.onSendNetworkIdentityCalled += callback;
serverComponent.SendNetworkIdentity(null);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(null);
});

[UnityTest]
public IEnumerator SendNullNetworkBehavior() => UniTask.ToCoroutine(async () =>
{
Action<NetworkBehaviour> callback = Substitute.For<Action<NetworkBehaviour>>();
clientComponent.onSendNetworkBehaviourCalled += callback;
serverComponent.SendNetworkBehaviour(null);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(null);
});

[UnityTest]
public IEnumerator SendNullNetworkBehaviorChild() => UniTask.ToCoroutine(async () =>
{
Action<SampleBehaviorWithRpc> callback = Substitute.For<Action<SampleBehaviorWithRpc>>();
clientComponent.onSendNetworkBehaviourDerivedCalled += callback;
serverComponent.SendNetworkBehaviourDerived(null);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(null);
});

[UnityTest]
public IEnumerator SendNullGameObject() => UniTask.ToCoroutine(async () =>
{
Action<GameObject> callback = Substitute.For<Action<GameObject>>();
clientComponent.onSendGameObjectCalled += callback;
serverComponent.SendGameObject(null);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(null);
});


[UnityTest]
public IEnumerator SendNetworkIdentityToServer() => UniTask.ToCoroutine(async () =>
{
Action<NetworkIdentity> callback = Substitute.For<Action<NetworkIdentity>>();
serverComponent.onSendNetworkIdentityCalled += callback;
clientComponent.SendNetworkIdentityToServer(clientIdentity);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(serverIdentity);
});

[UnityTest]
public IEnumerator SendNetworkBehaviorToServer() => UniTask.ToCoroutine(async () =>
{
Action<NetworkBehaviour> callback = Substitute.For<Action<NetworkBehaviour>>();
serverComponent.onSendNetworkBehaviourCalled += callback;
clientComponent.SendNetworkBehaviourToServer(clientComponent);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(serverComponent);
});

[UnityTest]
public IEnumerator SendNetworkBehaviorChildToServer() => UniTask.ToCoroutine(async () =>
{
Action<SampleBehaviorWithRpc> callback = Substitute.For<Action<SampleBehaviorWithRpc>>();
serverComponent.onSendNetworkBehaviourDerivedCalled += callback;
clientComponent.SendNetworkBehaviourDerivedToServer(clientComponent);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(serverComponent);
});

[UnityTest]
public IEnumerator SendGameObjectToServer() => UniTask.ToCoroutine(async () =>
{
Action<GameObject> callback = Substitute.For<Action<GameObject>>();
serverComponent.onSendGameObjectCalled += callback;
clientComponent.SendGameObjectToServer(clientPlayerGO);
await UniTask.WaitUntil(() => callback.ReceivedCalls().Any());
callback.Received().Invoke(serverPlayerGO);
});

[Test]
public void SendInvalidGOToServer()
{
Action<GameObject> callback = Substitute.For<Action<GameObject>>();
serverComponent.onSendGameObjectCalled += callback;

// this object does not have a NI, so this should error out
Assert.Throws<InvalidOperationException>(() =>
{
clientComponent.SendGameObjectToServer(clientGo);
});
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Runtime/ClientServer/NetworkBehaviorRpcTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 428ca63

Please sign in to comment.