Skip to content

Commit

Permalink
feat: Support gameobjects in syncvars (#513)
Browse files Browse the repository at this point in the history
GameObjects can now be synchronized from server to client.
They also support lazy loading, so if a gameobject has not been spawned yet
it will still return the correct GO when it is eventually spawned
  • Loading branch information
paulpach committed Dec 29, 2020
1 parent 41a3063 commit 29fb101
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using UnityEngine;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using PropertyAttributes = Mono.Cecil.PropertyAttributes;
Expand Down Expand Up @@ -250,6 +251,8 @@ private static TypeReference WrapType(ModuleDefinition module, TypeReference typ
// change the type of the field to a wrapper NetworkIDentitySyncvar
return module.ImportReference<NetworkIdentitySyncvar>();
}
if (typeReference.Is<GameObject>())
return module.ImportReference<GameObjectSyncvar>();
return typeReference;
}

Expand All @@ -260,12 +263,15 @@ private static TypeReference UnwrapType(ModuleDefinition module, TypeReference t
// change the type of the field to a wrapper NetworkIDentitySyncvar
return module.ImportReference<NetworkIdentity>();
}
if (typeReference.Is<GameObjectSyncvar>())
return module.ImportReference<GameObject>();
return typeReference;
}

private static bool IsWrapped(TypeReference typeReference)
{
return typeReference.Is<NetworkIdentitySyncvar>();
return typeReference.Is<NetworkIdentitySyncvar>() ||
typeReference.Is<GameObjectSyncvar>();
}

public void ProcessSyncVars(TypeDefinition td)
Expand Down
77 changes: 77 additions & 0 deletions Assets/Mirror/Runtime/GameObjectSyncvar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using UnityEngine;

namespace Mirror
{

/// <summary>
/// backing struct for a NetworkIdentity when used as a syncvar
/// the weaver will replace the syncvar with this struct.
/// </summary>
public struct GameObjectSyncvar
{
/// <summary>
/// The network client that spawned the parent object
/// used to lookup the identity if it exists
/// </summary>
internal NetworkClient client;
internal uint netId;

internal GameObject gameObject;

internal uint NetId => gameObject != null ? gameObject.GetComponent<NetworkIdentity>().NetId : netId;

public GameObject Value
{
get
{
if (gameObject != null)
return gameObject;

if (client != null)
{
client.Spawned.TryGetValue(netId, out NetworkIdentity result);
if (result != null)
return result.gameObject;
}

return null;
}

set
{
if (value == null)
netId = 0;
gameObject = value;
}
}
}


public static class GameObjectSerializers
{
public static void WriteNetworkIdentity(this NetworkWriter writer, GameObjectSyncvar id)
{
writer.WritePackedUInt32(id.NetId);
}

public static GameObjectSyncvar ReadNetworkIdentity(this NetworkReader reader)
{
uint netId = reader.ReadPackedUInt32();

NetworkIdentity identity = null;
if (!(reader.Client is null))
reader.Client.Spawned.TryGetValue(netId, out identity);

if (!(reader.Server is null))
reader.Server.Spawned.TryGetValue(netId, out identity);


return new GameObjectSyncvar
{
client = reader.Client,
netId = netId,
gameObject = identity != null ? identity.gameObject : null
};
}
}
}
11 changes: 11 additions & 0 deletions Assets/Mirror/Runtime/GameObjectSyncvar.cs.meta

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

6 changes: 6 additions & 0 deletions Assets/Tests/Editor/Weaver/WeaverSyncVarHookTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public void FindsHookWithNetworkIdentity()
IsSuccess();
}

[Test]
public void FindsHookWithGameObject()
{
IsSuccess();
}

[Test]
public void FindsHookWithOtherOverloadsInOrder()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Mirror;
using UnityEngine;

namespace WeaverSyncVarHookTests.FindsHookWithGameObject
{
class FindsHookWithGameObject : NetworkBehaviour
{
[SyncVar(hook = nameof(onTargetChanged))]
GameObject target;

void onTargetChanged(GameObject oldValue, GameObject newValue)
{

}
}
}
77 changes: 77 additions & 0 deletions Assets/Tests/Runtime/GameObjectSyncvarTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections;
using Cysharp.Threading.Tasks;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace Mirror.Tests
{
public class SampleBehaviorWithGO : NetworkBehaviour
{
[SyncVar]
public GameObject target;
}

public class GameObjectSyncvarTest : ClientServerSetup<SampleBehaviorWithGO>
{
[Test]
public void IsNullByDefault()
{
// out of the box, target should be null in the client

Assert.That(clientComponent.target, Is.Null);
}

[UnityTest]
public IEnumerator ChangeTarget() => UniTask.ToCoroutine(async () =>
{
serverComponent.target = serverPlayerGO;
await UniTask.WaitUntil(() => clientComponent.target != null);
Assert.That(clientComponent.target, Is.SameAs(clientPlayerGO));
});

[Test]
public void UpdateAfterSpawn()
{
// this situation can happen when the client does nto see an object
// but the object is assigned in a syncvar.
// this can easily happen during spawn if spawning in an unexpected order
// or if there is AOI in play.
// in this case we would have a valid net id, but we would not
// find the object at spawn time

var goSyncvar = new GameObjectSyncvar
{
client = client,
netId = serverIdentity.NetId,
gameObject = null,
};

Assert.That(goSyncvar.Value, Is.SameAs(clientPlayerGO));
}

[UnityTest]
public IEnumerator SpawnWithTarget() => UniTask.ToCoroutine(async () =>
{
// create an object, set the target and spawn it
UnityEngine.GameObject newObject = UnityEngine.Object.Instantiate(playerPrefab);
SampleBehaviorWithGO newBehavior = newObject.GetComponent<SampleBehaviorWithGO>();
newBehavior.target = serverPlayerGO;
serverObjectManager.Spawn(newObject);
// wait until the client spawns it
uint newObjectId = newBehavior.NetId;
await UniTask.WaitUntil(() => client.Spawned.ContainsKey(newObjectId));
// check if the target was set correctly in the client
var newClientObject = client.Spawned[newObjectId];
var newClientBehavior = newClientObject.GetComponent<SampleBehaviorWithGO>();
Assert.That(newClientBehavior.target, Is.SameAs(clientPlayerGO));
// cleanup
serverObjectManager.Destroy(newObject);
});
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Runtime/GameObjectSyncvarTest.cs.meta

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

0 comments on commit 29fb101

Please sign in to comment.