Skip to content

Commit

Permalink
feat: adding sync settings
Browse files Browse the repository at this point in the history
  • Loading branch information
James-Frowen committed Mar 31, 2023
1 parent fb5c2a6 commit 53ceb2a
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 0 deletions.
159 changes: 159 additions & 0 deletions Assets/Mirage/Runtime/SyncSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine;

namespace Mirage
{
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct SyncSettings
{
[FieldOffset(0)]
public SyncFrom From;
[FieldOffset(1)]
public SyncTo To;
[FieldOffset(2)]
public SyncTiming Timing;

[Tooltip("Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)")]
[FieldOffset(4)]
public float Interval;

public static readonly SyncSettings Default = new SyncSettings
{
From = SyncFrom.Server,
To = SyncTo.Owner | SyncTo.Observers,
Interval = 0.1f,
};

public void UpdateTime(ref float nextSyncTime, float now)
{
switch (Timing)
{
case SyncTiming.Variable:
// atlesat Interval before next sync
nextSyncTime = now + Interval;
break;
case SyncTiming.Fixed:
// just add Interval, so that it syncs 1/Interval times per second
// see SyncTiming.Fixed for example
nextSyncTime += Interval;
break;
default:
case SyncTiming.NoInterval:
// always sync
nextSyncTime = now;
break;
}
}

public bool ShouldSyncFrom(NetworkIdentity identity)
{
if ((From & SyncFrom.Server) != 0 && identity.IsServer)
{
// only write if either owner or ObserversOnly
// otherwise we can just skip the component
return (To & SyncTo.Observers) != 0;
}

if ((From & SyncFrom.Owner) != 0 && identity.HasAuthority)
{
// if from owner, must be to server
return (To & SyncTo.Server) != 0;
}

return false;
}

public bool CopyToObservers()
{
if ((From & SyncFrom.Server) != 0)
{
return (To & SyncTo.ObserversOnly) != 0;
}

return false;
}

public static bool IsValidDirection(SyncFrom from, SyncTo to)
{
if ((from & SyncFrom.Owner) != 0)
{
// if from owner,
// server must be included in SyncTo
// Observers is optional
if ((to & SyncTo.Server) == 0)
return false;
}

if ((from & SyncFrom.Server) != 0)
{
// if from server,
// must be to Owner or Observers
// Observers is optional
if ((to & SyncTo.Owner) == 0)
return false;
}

return true;
}
}


[Flags]
public enum SyncFrom : byte
{
None = 0,
/// <summary>
/// syncs from Owner to Server or
/// </summary>
Owner = 1,
Server = 2,
}
[Flags]
public enum SyncTo : byte
{
None = 0,
Owner = 1,
ObserversOnly = 2,
Server = 4,

Observers = Owner | ObserversOnly,
ServerAndObservers = Server | ObserversOnly
}

public enum SyncTiming : byte
{
/// <summary>
/// Will wait for atleast <see cref="SyncSettings.Interval"/> after last sync before sending again.
/// <para>
/// Best used when values dont change often, or for non-time-critical data.
/// </para>
/// <para>
/// Will send less often than <see cref="Fixed"/> for the same <see cref="SyncSettings.Interval"/>.
/// </para>
/// </summary>
Variable = 0,

/// <summary>
/// Will ensure data is sent every <see cref="SyncSettings.Interval"/> if changed.
/// <para>
/// Best used for data that changes often and you want (1/<see cref="SyncSettings.Interval"/>) updates per second
/// </para>
/// </summary>
/// <remarks>
/// <b>Example of Fixed vs Variable</b>
/// <para>
/// if Interval = 0.1. Then Synctimes will look like: 0, 0.1, 0.2, 0.3, etc.
/// </para>
/// <para>
/// Compared to <see cref="Variable"/> where the values depend more on the timedelta, for example they might look like: 0, 0.11, 0.215, 0.32, etc
/// </para>
/// </remarks>
Fixed = 1,

/// <summary>
/// Ignores Interval and will send changes in next update
/// </summary>
NoInterval = 2,
}
}
11 changes: 11 additions & 0 deletions Assets/Mirage/Runtime/SyncSettings.cs.meta

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

75 changes: 75 additions & 0 deletions Assets/Tests/Runtime/SyncDirectionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using NUnit.Framework;

namespace Mirage.Tests.Runtime.Serialization
{
public class SyncDirectionTest : TestBase
{
[Test]
public void FromToNoneIsValid()
{
var valid = SyncSettings.IsValidDirection(SyncFrom.None, SyncTo.None);
Assert.IsTrue(valid);
}

[Test]
public void FromNoneToAnyIsInvalid()
{
for (var i = 1; i < 8; i++)
{
var valid = SyncSettings.IsValidDirection(SyncFrom.None, (SyncTo)i);
Assert.IsFalse(valid);
}
}
public void FromAnyToNoneIsInvalid()
{
for (var i = 1; i < 4; i++)
{
var valid = SyncSettings.IsValidDirection((SyncFrom)i, SyncTo.None);
Assert.IsFalse(valid);
}
}

[Test]
// cases are in order, 1 to 7, (bit mask)
[TestCase(SyncTo.Owner, ExpectedResult = false)]
[TestCase(SyncTo.ObserversOnly, ExpectedResult = false)]
[TestCase(SyncTo.Owner | SyncTo.ObserversOnly, ExpectedResult = false)]
[TestCase(SyncTo.Server, ExpectedResult = true)]
[TestCase(SyncTo.Server | SyncTo.Owner, ExpectedResult = false)]
[TestCase(SyncTo.Server | SyncTo.ObserversOnly, ExpectedResult = true)]
[TestCase(SyncTo.Server | SyncTo.Owner | SyncTo.ObserversOnly, ExpectedResult = false)]
public bool FromOwner(SyncTo to)
{
return SyncSettings.IsValidDirection(SyncFrom.Owner, to);
}

[Test]
// cases are in order, 1 to 7, (bit mask)
[TestCase(SyncTo.Owner, ExpectedResult = true)]
[TestCase(SyncTo.ObserversOnly, ExpectedResult = true)]
[TestCase(SyncTo.Owner | SyncTo.ObserversOnly, ExpectedResult = true)]
[TestCase(SyncTo.Server, ExpectedResult = false)]
[TestCase(SyncTo.Server | SyncTo.Owner, ExpectedResult = false)]
[TestCase(SyncTo.Server | SyncTo.ObserversOnly, ExpectedResult = false)]
[TestCase(SyncTo.Server | SyncTo.Owner | SyncTo.ObserversOnly, ExpectedResult = false)]
public bool FromServer(SyncTo to)
{
return SyncSettings.IsValidDirection(SyncFrom.Server, to);
}


[Test]
// cases are in order, 1 to 7, (bit mask)
[TestCase(SyncTo.Owner, ExpectedResult = false)] // can't go just to owner, (would mean from.owner is going nowhere)
[TestCase(SyncTo.ObserversOnly, ExpectedResult = false)] // same as above, owner needs to send to server
[TestCase(SyncTo.Owner | SyncTo.ObserversOnly, ExpectedResult = false)] // same
[TestCase(SyncTo.Server, ExpectedResult = false)] // same, but for server instead
[TestCase(SyncTo.Server | SyncTo.Owner, ExpectedResult = true)]
[TestCase(SyncTo.Server | SyncTo.ObserversOnly, ExpectedResult = true)] // server changes only go to observers, but owner changes will go to server.
[TestCase(SyncTo.Server | SyncTo.Owner | SyncTo.ObserversOnly, ExpectedResult = true)]
public bool FromOwnerAndServer(SyncTo to)
{
return SyncSettings.IsValidDirection(SyncFrom.Owner | SyncFrom.Server, to);
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Runtime/SyncDirectionTest.cs.meta

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

0 comments on commit 53ceb2a

Please sign in to comment.