Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: grouping syncvars before sending #963

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Assets/Mirage/Runtime/INetworkPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public interface IVisibilityTracker
{
void AddToVisList(NetworkIdentity identity);
void RemoveFromVisList(NetworkIdentity identity);
bool IsVisible(NetworkIdentity identity);
void RemoveAllVisibleObjects();
}

Expand Down
2 changes: 1 addition & 1 deletion Assets/Mirage/Runtime/INetworkServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public interface INetworkServer

NetworkWorld World { get; }

SyncVarSender SyncVarSender { get; }
SyncVarSenderBase SyncVarSender { get; }

IReadOnlyCollection<INetworkPlayer> Players { get; }

Expand Down
29 changes: 18 additions & 11 deletions Assets/Mirage/Runtime/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using Mirage.Collections;
using Mirage.Logging;
Expand Down Expand Up @@ -384,7 +385,7 @@ public void SetDirtyBit(ulong dirtyBit)
/// This clears all the dirty bits that were set on this script by SetDirtyBits();
/// <para>This is automatically invoked when an update is sent for this object, but can be called manually as well.</para>
/// </summary>
public void ClearAllDirtyBits()
public void MarkAsSynced()
{
lastSyncTime = Time.time;
SyncVarDirtyBits = 0L;
Expand Down Expand Up @@ -414,19 +415,25 @@ bool AnySyncObjectDirty()
return false;
}

public bool IsDirty()
/// <returns>if it is time to sync and Any Var is dirty</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool NeedsSync()
{
if (Time.time - lastSyncTime >= syncInterval)
{
return SyncVarDirtyBits != 0L || AnySyncObjectDirty();
}
return false;
return TimeToSync() && AnyVarDirty();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TimeToSync()
{
return Time.time - lastSyncTime >= syncInterval;
}

// true if this component has data that has not been
// synchronized. Note that it may not synchronize
// right away because of syncInterval
public bool StillDirty()
/// <summary>
/// If any syncvar or syncObject is dirty
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AnyVarDirty()
{
return SyncVarDirtyBits != 0L || AnySyncObjectDirty();
}
Expand Down
157 changes: 102 additions & 55 deletions Assets/Mirage/Runtime/NetworkIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,6 @@ public NetworkBehaviour[] NetworkBehaviours

NetworkBehaviour[] components = GetComponentsInChildren<NetworkBehaviour>(true);

if (components.Length > byte.MaxValue)
throw new InvalidOperationException("Only 255 NetworkBehaviour per gameobject allowed");

networkBehavioursCache = components;
return networkBehavioursCache;
}
Expand Down Expand Up @@ -782,13 +779,16 @@ internal void StopClient()
/// </description></item>
/// </list>
/// </remarks>
void OnSerialize(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
void SerializeBehaviour(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
{
comp.OnSerialize(writer, initialState);
if (logger.LogEnabled()) logger.Log("OnSerializeSafely written for object=" + comp.name + " component=" + comp.GetType() + " sceneId=" + sceneId);

// serialize a barrier to be checked by the deserializer
writer.WriteByte(Barrier);

// we can't mark as synced inside OnSerialize because it is virutal and user could override it
comp.MarkAsSynced();
}

/// <summary>
Expand All @@ -799,47 +799,47 @@ void OnSerialize(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
/// <param name="initialState"></param>
/// <param name="ownerWriter"></param>
/// <param name="observersWriter"></param>
internal (int ownerWritten, int observersWritten) OnSerializeAll(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
internal (int ownerWritten, int observersWritten) SerializeAllBehaviours(bool initialState, NetworkWriter ownerWriter, NetworkWriter observersWriter)
{
int ownerWritten = 0;
int observersWritten = 0;

// check if components are in byte.MaxRange just to be 100% sure
// that we avoid overflows
NetworkBehaviour[] components = NetworkBehaviours;

// serialize all components
for (int i = 0; i < components.Length; ++i)
for (int i = 0; i < components.Length; i++)
{
// is this component dirty?
// -> always serialize if initialState so all components are included in spawn packet
// -> note: IsDirty() is false if the component isn't dirty or sendInterval isn't elapsed yet
NetworkBehaviour comp = components[i];
if (initialState || comp.IsDirty())

// remember start position in case we need to copy it into observers writer too
int startBitPosition = ownerWriter.BitPosition;

// only calculate isDirty if not intital
bool isDirtyOrInitial = initialState || comp.NeedsSync();

// only write isDirty when not initial state
// if initial is false, then isDirtyOrInitial will be isDirty
if (!initialState)
{
if (logger.LogEnabled()) logger.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState);
ownerWriter.WriteBoolean(isDirtyOrInitial);
}

// remember start position in case we need to copy it into
// observers writer too
int startBitPosition = ownerWriter.BitPosition;
// then write values if dirty
if (isDirtyOrInitial)
{
if (logger.LogEnabled()) logger.Log($"OnSerializeAllSafely: {name} -> {comp.GetType()} initial={initialState}");

// todo dont serialize Owner State if SyncMode==Owner and no owner

// write index as byte [0..255]
ownerWriter.WriteByte((byte)i);
// serialize into ownerWriter first (owner always gets everything!)
SerializeBehaviour(comp, ownerWriter, initialState);

// serialize into ownerWriter first
// (owner always gets everything!)
OnSerialize(comp, ownerWriter, initialState);
ownerWritten++;

// copy into observersWriter too if SyncMode.Observers
// -> we copy instead of calling OnSerialize again because
// we don't know what magic the user does in OnSerialize.
// -> it's not guaranteed that calling it twice gets the
// same result
// -> it's not guaranteed that calling it twice doesn't mess
// with the user's OnSerialize timing code etc.
// => so we just copy the result without touching
// OnSerialize again
// just copy from 1 buffer to another, faster than serializing again
if (comp.syncMode == SyncMode.Observers)
{
int bitLength = ownerWriter.BitPosition - startBitPosition;
Expand All @@ -852,13 +852,15 @@ void OnSerialize(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
return (ownerWritten, observersWritten);
}

// Determines if there are changes in any component that have not
// been synchronized yet. Probably due to not meeting the syncInterval
internal bool StillDirty()
/// <summary>
/// Checks if any behaviour is dirty
/// </summary>
/// <returns></returns>
internal bool AnyBehaviourDirty()
{
foreach (NetworkBehaviour behaviour in NetworkBehaviours)
{
if (behaviour.StillDirty())
if (behaviour.AnyVarDirty())
return true;
}
return false;
Expand All @@ -885,20 +887,16 @@ internal void OnDeserializeAll(NetworkReader reader, bool initialState)
reader.ObjectLocator = Client != null ? Client.World : null;
// deserialize all components that were received
NetworkBehaviour[] components = NetworkBehaviours;
// check if we can read atleast 1 byte
while (reader.CanReadBytes(1))

// serialize all components
for (int i = 0; i < components.Length; i++)
{
// todo replace index with bool for if next component in order has changed or not
// the index below was an alternative to a mask, but now we have bitpacking we can just use a bool for each NB index
// read & check index [0..255]
byte index = reader.ReadByte();
if (index < components.Length)
// initail or ReadIsDirty
if (initialState || reader.ReadBoolean())
{
// deserialize this component
OnDeserialize(components[index], reader, initialState);
OnDeserialize(components[i], reader, initialState);
}
}

}

/// <summary>
Expand Down Expand Up @@ -1209,7 +1207,7 @@ private void ResetEvents()
_onStopServer.Reset();
}

internal void UpdateVars()
internal void UpdateVars_Legacy()
{
if (observers.Count > 0)
{
Expand All @@ -1229,7 +1227,7 @@ void SendUpdateVarsMessage()
using (PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter(), observersWriter = NetworkWriterPool.GetWriter())
{
// serialize all the dirty components and send
(int ownerWritten, int observersWritten) = OnSerializeAll(false, ownerWriter, observersWriter);
(int ownerWritten, int observersWritten) = SerializeAllBehaviours(false, ownerWriter, observersWriter);
if (ownerWritten > 0 || observersWritten > 0)
{
var varsMessage = new UpdateVarsMessage
Expand All @@ -1256,15 +1254,6 @@ void SendUpdateVarsMessage()
varsMessage.payload = observersWriter.ToArraySegment();
SendToRemoteObservers(varsMessage, false);
}

// clear dirty bits only for the components that we serialized
// DO NOT clean ALL component's dirty bits, because
// components can have different syncIntervals and we don't
// want to reset dirty bits for the ones that were not
// synced yet.
// (we serialized only the IsDirty() components, or all of
// them if initialState. clearing the dirty ones is enough.)
ClearDirtyComponentsDirtyBits();
}
}
}
Expand Down Expand Up @@ -1308,21 +1297,22 @@ internal void ClearAllComponentsDirtyBits()
{
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
comp.ClearAllDirtyBits();
comp.MarkAsSynced();
}
}

/// <summary>
/// Clear only dirty component's dirty bits. ignores components which
/// may be dirty but not ready to be synced yet (because of syncInterval)
/// </summary>
[System.Obsolete("We do this inside Seralize", true)]
internal void ClearDirtyComponentsDirtyBits()
{
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
if (comp.IsDirty())
if (comp.NeedsSync())
{
comp.ClearAllDirtyBits();
comp.MarkAsSynced();
}
}
}
Expand All @@ -1334,5 +1324,62 @@ void ResetSyncObjects()
comp.ResetSyncObjects();
}
}

struct UpdateCache
{
public bool hasCache;
public PooledNetworkWriter ownerWriter;
public PooledNetworkWriter observersWriter;
}
UpdateCache updateCache;

internal NetworkWriter GetCachedUpdate(INetworkPlayer player)
{
if (!updateCache.hasCache)
{
CreateUpdateCache();
}

if (Owner == player)
return updateCache.ownerWriter;
else
return updateCache.observersWriter;

}

private void CreateUpdateCache()
{
updateCache.hasCache = true;

updateCache.observersWriter = NetworkWriterPool.GetWriter();
updateCache.ownerWriter = NetworkWriterPool.GetWriter();

// serialize all the dirty components and send
(int ownerWritten, int observersWritten) = SerializeAllBehaviours(false, updateCache.ownerWriter, updateCache.observersWriter);

if (ownerWritten == 0)
{
updateCache.ownerWriter.Release();
updateCache.ownerWriter = null;
}

if (observersWritten == 0)
{
updateCache.observersWriter.Release();
updateCache.observersWriter = null;
}
}

internal void ClearCachedUpdate()
{
updateCache.observersWriter?.Release();
updateCache.ownerWriter?.Release();

updateCache.observersWriter = null;
updateCache.ownerWriter = null;
updateCache.hasCache = false;

ClearAllComponentsDirtyBits();
}
}
}
7 changes: 7 additions & 0 deletions Assets/Mirage/Runtime/NetworkPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Mirage.Logging;
using Mirage.Serialization;
using Mirage.SocketLayer;
Expand Down Expand Up @@ -175,6 +176,12 @@ public void RemoveFromVisList(NetworkIdentity identity)
visList.Remove(identity);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsVisible(NetworkIdentity identity)
{
return visList.Contains(identity);
}

/// <summary>
/// Removes all objects that this player can see
/// <para>This is called when loading a new scene</para>
Expand Down
Loading