Skip to content

Commit

Permalink
feat(Syncvar): adding InitialOnly to syncvar (#941)
Browse files Browse the repository at this point in the history
allows a syncvar to only be used for spawn message, meaning it wont be sent a 2nd time if changed later on server
  • Loading branch information
James-Frowen committed Sep 22, 2021
1 parent cda3795 commit abf4637
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Assets/Mirage/Runtime/CustomAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public class SyncVarAttribute : PropertyAttribute
{
///<summary>A function that should be called on the client when the value changes.</summary>
public string hook;
/// <summary>
/// If true, this syncvar will only be sent with spawn message, any other changes will not be sent to existing objects
/// </summary>
public bool initialOnly;
}

/// <summary>
Expand Down
30 changes: 29 additions & 1 deletion Assets/Mirage/Weaver/Processors/SyncVarProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ void ProcessSyncVar(FoundSyncVar syncVar)


MethodDefinition get = GenerateSyncVarGetter(syncVar);
MethodDefinition set = GenerateSyncVarSetter(syncVar);
MethodDefinition set = syncVar.InitialOnly
? GenerateSyncVarSetterInitialOnly(syncVar)
: GenerateSyncVarSetter(syncVar);

//NOTE: is property even needed? Could just use a setter function?
//create the property
Expand Down Expand Up @@ -163,6 +165,26 @@ MethodDefinition GenerateSyncVarGetter(FoundSyncVar syncVar)
return get;
}

MethodDefinition GenerateSyncVarSetterInitialOnly(FoundSyncVar syncVar)
{
// todo reduce duplicate code with this and GenerateSyncVarSetter
FieldDefinition fd = syncVar.FieldDefinition;
TypeReference originalType = syncVar.OriginalType;
string originalName = syncVar.OriginalName;

//Create the set method
MethodDefinition set = fd.DeclaringType.AddMethod("set_Network" + originalName, MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig);
ParameterDefinition valueParam = set.AddParam(originalType, "value");
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;

ILProcessor worker = set.Body.GetILProcessor();
WriteStoreField(worker, valueParam, syncVar);
worker.Append(worker.Create(OpCodes.Ret));

return set;
}
MethodDefinition GenerateSyncVarSetter(FoundSyncVar syncVar)
{
FieldDefinition fd = syncVar.FieldDefinition;
Expand Down Expand Up @@ -422,6 +444,9 @@ void GenerateSerialization()
// start at number of syncvars in parent
foreach (FoundSyncVar syncVar in behaviour.SyncVars)
{
// dont need to write field here if syncvar is InitialOnly
if (syncVar.InitialOnly) { continue; }

helper.WriteIfSyncVarDirty(syncVar, () =>
{
// Generates a call to the writer for that field
Expand Down Expand Up @@ -473,6 +498,9 @@ void GenerateDeserialization()
// conditionally read each syncvar
foreach (FoundSyncVar syncVar in behaviour.SyncVars)
{
// dont need to write field here if syncvar is InitialOnly
if (syncVar.InitialOnly) { continue; }

helper.WriteIfSyncVarDirty(syncVar, () =>
{
DeserializeField(worker, helper.Method, helper.ReaderParameter, syncVar);
Expand Down
8 changes: 8 additions & 0 deletions Assets/Mirage/Weaver/Processors/SyncVars/FoundSyncVar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public FoundSyncVar(ModuleDefinition module, FoundNetworkBehaviour behaviour, Fi

public bool HasHookMethod { get; private set; }
public MethodDefinition HookMethod { get; private set; }
public bool InitialOnly { get; private set; }

/// <summary>
/// Changing the type of the field to the wrapper type, if one exists
Expand Down Expand Up @@ -87,8 +88,15 @@ public void ProcessAttributes(Writers writers, Readers readers)
{
HookMethod = HookMethodFinder.GetHookMethod(FieldDefinition, OriginalType);
HasHookMethod = HookMethod != null;
InitialOnly = GetInitialOnly(FieldDefinition);

ValueSerializer = ValueSerializerFinder.GetSerializer(this, writers, readers);
}

static bool GetInitialOnly(FieldDefinition fieldDefinition)
{
CustomAttribute attr = fieldDefinition.GetCustomAttribute<SyncVarAttribute>();
return attr.GetField(nameof(SyncVarAttribute.initialOnly), false);
}
}
}
89 changes: 89 additions & 0 deletions Assets/Tests/Runtime/ClientServer/SyncVarInitialOnlyTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace Mirage.Tests.Runtime.ClientServer
{
public class SyncVarInitialOnly : NetworkBehaviour
{
[SyncVar(initialOnly = true)]
public int weaponIndex = 1;

[SyncVar]
public int health = 100;

[SyncVar(initialOnly = true)]
public float otherValue = 13f;
}

public class SyncVarInitialOnlyTest : ClientServerSetup<SyncVarInitialOnly>
{
[Test]
public void ChangingInitOnlyVarWontSetBehaviourDirty()
{
serverComponent.weaponIndex = 10;
Assert.That(serverComponent.IsDirty(), Is.False);

serverComponent.otherValue = 5.2f;
Assert.That(serverComponent.IsDirty(), Is.False);

serverComponent.health = 20;
Assert.That(serverComponent.IsDirty(), Is.True);
}

[UnityTest]
public IEnumerator InitOnlyIsntSentToClient()
{
serverComponent.weaponIndex = 10;
serverComponent.health = 50;
serverComponent.otherValue = 5.2f;

yield return new WaitForSeconds(0.2f);

Assert.That(clientComponent.weaponIndex, Is.EqualTo(1), "Should not have changed");
Assert.That(clientComponent.health, Is.EqualTo(50));
Assert.That(clientComponent.otherValue, Is.EqualTo(13f));
}

[UnityTest]
public IEnumerator BothSyncVarsAreSetIntially()
{
var prefab = new GameObject("BothSyncVarsAreSet", typeof(NetworkIdentity), typeof(SyncVarInitialOnly));
NetworkIdentity identity = prefab.GetComponent<NetworkIdentity>();
identity.AssetId = Guid.NewGuid();

clientObjectManager.RegisterPrefab(identity);

var clone = GameObject.Instantiate(prefab);
SyncVarInitialOnly behaviour = clone.GetComponent<SyncVarInitialOnly>();
behaviour.weaponIndex = 3;
behaviour.health = 20;
behaviour.otherValue = 5.2f;

serverObjectManager.Spawn(clone);
uint netId = behaviour.NetId;

yield return null;
yield return null;

client.World.TryGetIdentity(netId, out NetworkIdentity clientClient);
SyncVarInitialOnly clientBehaviour = clientClient.GetComponent<SyncVarInitialOnly>();

Assert.That(clientBehaviour.weaponIndex, Is.EqualTo(3));
Assert.That(clientBehaviour.health, Is.EqualTo(20));
Assert.That(clientBehaviour.otherValue, Is.EqualTo(5.2f));

behaviour.weaponIndex = 2;
behaviour.health = 30;
behaviour.otherValue = 7.3f;

yield return new WaitForSeconds(0.2f);

Assert.That(clientBehaviour.weaponIndex, Is.EqualTo(3), "does not change");
Assert.That(clientBehaviour.health, Is.EqualTo(30));
Assert.That(clientBehaviour.otherValue, Is.EqualTo(5.2f));
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Runtime/ClientServer/SyncVarInitialOnlyTest.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/Weaver/SyncVarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public void SyncVarsValid()
IsSuccess();
}

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

[Test]
public void SyncVarArraySegment()
{
Expand Down
10 changes: 10 additions & 0 deletions Assets/Tests/Weaver/SyncVarTests~/SyncVarsValidInitialOnly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Mirage;

namespace SyncVarTests.SyncVarsValidInitialOnly
{
class SyncVarsValidInitialOnly : NetworkBehaviour
{
[SyncVar(initialOnly = true)] int var;
[SyncVar(initialOnly = false)] int var2;
}
}

0 comments on commit abf4637

Please sign in to comment.