Skip to content

Commit

Permalink
feat: LocalPlayer attribute now throws error (#277)
Browse files Browse the repository at this point in the history
* feat: LocalPlayer attribute now throws error

If you want to get an error you do this:
```cs
[LocalPlayer]
public void ClientFunctionOnly() {}
```

If you don't want to get an error, you do this:
```cs
[LocalPlayer(error = false)]
public void ClientFunctionOnly() {}
```

BREAKING CHANGE: [LocalPlayerCallback] is now [LocalPlayer(error = false)]

* Local Player guard

Co-authored-by: Paul Pacheco <paul.pacheco@aa.com>
  • Loading branch information
paulpach and paulpach committed Jul 14, 2020
1 parent fb8b429 commit 15aa537
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ public static void ProcessMethodAttributes(TypeDefinition td, MethodDefinition m
InjectHasAuthorityGuard(td, md, attr);
break;
case "Mirror.LocalPlayerAttribute":
InjectLocalPlayerGuard(td, md, true);
break;
case "Mirror.LocalPlayerCallbackAttribute":
InjectLocalPlayerGuard(td, md, false);
InjectLocalPlayerGuard(td, md, attr);
break;
default:
break;
Expand Down Expand Up @@ -115,8 +112,10 @@ static void InjectHasAuthorityGuard(TypeDefinition td, MethodDefinition md, Cust
worker.InsertBefore(top, worker.Create(OpCodes.Ret));
}

static void InjectLocalPlayerGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
static void InjectLocalPlayerGuard(TypeDefinition td, MethodDefinition md, CustomAttribute attribute)
{
bool throwError = attribute.GetField<bool>("error", true);

if (!Weaver.IsNetworkBehaviour(td))
{
Weaver.Error($"Local Player method {md.Name} must be declared in a NetworkBehaviour", md);
Expand All @@ -128,10 +127,11 @@ static void InjectLocalPlayerGuard(TypeDefinition td, MethodDefinition md, bool
worker.InsertBefore(top, worker.Create(OpCodes.Ldarg_0));
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkBehaviourIsLocalPlayer));
worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
if (logWarning)
if (throwError)
{
worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Local Player] function '" + md.FullName + "' called on nonlocal player"));
worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
worker.InsertBefore(top, worker.Create(OpCodes.Newobj, Weaver.MethodInvocationExceptionConstructor));
worker.InsertBefore(top, worker.Create(OpCodes.Throw));
}

InjectGuardParameters(md, worker, top);
Expand Down
18 changes: 9 additions & 9 deletions Assets/Mirror/Runtime/CustomAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,17 @@ public class HasAuthorityAttribute : Attribute

/// <summary>
/// Prevents nonlocal players from running this method.
/// <para>Prints a warning if a nonlocal player tries to execute this method.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class LocalPlayerAttribute : Attribute { }

/// <summary>
/// Prevents a nonlocal player from running this method.
/// <para>No warning is printed.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class LocalPlayerCallbackAttribute : Attribute { }
public class LocalPlayerAttribute : Attribute
{
/// <summary>
/// If true, when the method is called from a client, it throws an error
/// If false, no error is thrown, but the method won't execute
/// useful for unity built in methods such as Await, Update, Start, etc.
/// </summary>
public bool error = true;
}

/// <summary>
/// Converts a string property into a Scene property in the inspector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public void NetworkBehaviourHasAuthority()
CheckAddedCode(Weaver.NetworkBehaviourHasAuthority, "WeaverClientServerAttributeTests.NetworkBehaviourHasAuthority.NetworkBehaviourHasAuthority", "HasAuthorityMethod");
}

[Test]
public void NetworkBehaviourLocalPlayer()
{
Assert.That(weaverErrors, Is.Empty);
CheckAddedCode(Weaver.NetworkBehaviourIsLocalPlayer, "WeaverClientServerAttributeTests.NetworkBehaviourLocalPlayer.NetworkBehaviourLocalPlayer", "LocalPlayerMethod");
}

/// <summary>
/// Checks that first Instructions in MethodBody is addedString
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Mirror;

namespace WeaverClientServerAttributeTests.NetworkBehaviourLocalPlayer
{
class NetworkBehaviourLocalPlayer : NetworkBehaviour
{
[LocalPlayer]
void LocalPlayerMethod()
{
// test method
}
}
}
58 changes: 56 additions & 2 deletions Assets/Mirror/Tests/Runtime/GuardsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class ExampleGuards : NetworkBehaviour
public bool clientCallbackFunctionCalled;
public bool hasAuthorityCalled;
public bool hasAuthorityNoErrorCalled;
public bool localPlayerCalled;
public bool localPlayerNoErrorCalled;

[Server]
public void CallServerFunction()
Expand Down Expand Up @@ -47,6 +49,18 @@ public void CallAuthorityNoErrorFunction()
{
hasAuthorityNoErrorCalled = true;
}

[LocalPlayer]
public void CallLocalPlayer()
{
localPlayerCalled = true;
}

[LocalPlayer(error = false)]
public void CallLocalPlayerNoError()
{
localPlayerNoErrorCalled = true;
}
}

public class GuardsTests : ClientServerSetup<ExampleGuards>
Expand Down Expand Up @@ -131,7 +145,6 @@ public void GuardHasAuthorityError()
{
var obj = new GameObject("randomObject", typeof(NetworkIdentity), typeof(ExampleGuards));
ExampleGuards guardedComponent = obj.GetComponent<ExampleGuards>();

Assert.Throws<MethodInvocationException>( () =>
{
guardedComponent.CallAuthorityFunction();
Expand All @@ -145,10 +158,51 @@ public void GuardHasAuthorityNoError()
{
var obj = new GameObject("randomObject", typeof(NetworkIdentity), typeof(ExampleGuards));
ExampleGuards guardedComponent = obj.GetComponent<ExampleGuards>();

guardedComponent.CallAuthorityNoErrorFunction();
Assert.That(guardedComponent.hasAuthorityNoErrorCalled, Is.False);


Object.Destroy(obj);
}

[Test]
public void CanCallLocalPlayer()
{
clientComponent.CallLocalPlayer();
Assert.That(clientComponent.localPlayerCalled, Is.True);
}

[Test]
public void CanCallLocalPlayerNoError()
{
clientComponent.CallLocalPlayerNoError();
Assert.That(clientComponent.localPlayerNoErrorCalled, Is.True);
}

[Test]
public void GuardLocalPlayer()
{
var obj = new GameObject("randomObject", typeof(NetworkIdentity), typeof(ExampleGuards));
ExampleGuards guardedComponent = obj.GetComponent<ExampleGuards>();

Assert.Throws<MethodInvocationException>(() =>
{
guardedComponent.CallLocalPlayer();
});

Object.Destroy(obj);
}

[Test]
public void GuardLocalPlayerNoError()
{
var obj = new GameObject("randomObject", typeof(NetworkIdentity), typeof(ExampleGuards));
ExampleGuards guardedComponent = obj.GetComponent<ExampleGuards>();

guardedComponent.CallLocalPlayerNoError();
Assert.That(guardedComponent.localPlayerNoErrorCalled, Is.False);

Object.Destroy(obj);
}
}
Expand Down

0 comments on commit 15aa537

Please sign in to comment.