perf: NetworkServer.Broadcast serialization lookup removed. serializations are now cached and rebuilt in NetworkIdentity based on timestamp.

=> way easier
=> way faster because we don't need to recycle two writers for every .spawned at the end of broadcast
This commit is contained in:
vis2k 2021-05-27 12:24:06 +08:00
parent fe3b7ae57c
commit 089e5bbb59
2 changed files with 64 additions and 77 deletions

View File

@ -19,6 +19,16 @@ namespace Mirror
// to everyone etc.
public enum Visibility { Default, ForceHidden, ForceShown }
public struct NetworkIdentitySerialization
{
public float tickTimeStamp;
public NetworkWriter ownerWriter;
public NetworkWriter observersWriter;
// TODO there is probably a more simple way later
public int ownerWritten;
public int observersWritten;
}
/// <summary>NetworkIdentity identifies objects across the network.</summary>
[DisallowMultipleComponent]
[AddComponentMenu("Network/NetworkIdentity")]
@ -144,6 +154,19 @@ internal set
[Tooltip("Visibility can overwrite interest management. ForceHidden can be useful to hide monsters while they respawn. ForceShown can be useful for score NetworkIdentities that should always broadcast to everyone in the world.")]
public Visibility visible = Visibility.Default;
// broadcasting serializes all entities around a player for each player.
// we don't want to serialize one entity twice in the same tick.
// so we cache the last serialization and remember the timestamp so we
// know which Update it was serialized.
// (timestamp is the same while inside Update)
// => this way we don't need to pool thousands of writers either.
// => way easier to store them per object
NetworkIdentitySerialization lastSerialization = new NetworkIdentitySerialization
{
ownerWriter = new NetworkWriter(),
observersWriter = new NetworkWriter()
};
/// <summary>Prefab GUID used to spawn prefabs across the network.</summary>
//
// The AssetId trick:
@ -903,6 +926,32 @@ internal void OnSerializeAllSafely(bool initialState, NetworkWriter ownerWriter,
}
}
// get cached serialization for this tick (or serialize if none yet)
internal NetworkIdentitySerialization GetSerializationAtTick(float tickTimeStamp)
{
// serialize fresh if tick is newer than last one
if (lastSerialization.tickTimeStamp < tickTimeStamp)
{
// reset
lastSerialization.ownerWriter.Position = 0;
lastSerialization.observersWriter.Position = 0;
// serialize
OnSerializeAllSafely(false,
lastSerialization.ownerWriter,
out lastSerialization.ownerWritten,
lastSerialization.observersWriter,
out lastSerialization.observersWritten);
// set timestamp
lastSerialization.tickTimeStamp = tickTimeStamp;
//Debug.Log($"{name} (netId={netId}) serialized for tick={tickTimeStamp}");
}
// return it
return lastSerialization;
}
void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
{
// read header as 4 bytes and calculate this chunk's start+end

View File

@ -1370,70 +1370,11 @@ public static void RebuildObservers(NetworkIdentity identity, bool initialize)
}
// broadcasting ////////////////////////////////////////////////////////
// cache NetworkIdentity serializations
// Update() shouldn't serialize multiple times for multiple connections
struct Serialization
{
public PooledNetworkWriter ownerWriter;
public PooledNetworkWriter observersWriter;
// TODO there is probably a more simple way later
public int ownerWritten;
public int observersWritten;
}
static Dictionary<NetworkIdentity, Serialization> serializations =
new Dictionary<NetworkIdentity, Serialization>();
// helper function to get an entity's serialization with caching
static Serialization GetEntitySerialization(NetworkIdentity identity)
{
// multiple connections might be observed by the
// same NetworkIdentity, but we don't want to
// serialize them multiple times. look it up first.
//
// IMPORTANT: don't forget to return them to pool!
// TODO make this easier later. for now aim for
// feature parity to not break projects.
// TODO let the entity cache it's own serialization
// and recompute only if it was dirty.
if (!serializations.ContainsKey(identity))
{
// serialize all the dirty components.
// one version for owner, one for observers.
PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter();
PooledNetworkWriter observersWriter = NetworkWriterPool.GetWriter();
identity.OnSerializeAllSafely(false, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
serializations[identity] = new Serialization
{
ownerWriter = ownerWriter,
observersWriter = observersWriter,
ownerWritten = ownerWritten,
observersWritten = observersWritten
};
// 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.)
//
// NOTE: this is what we did before push->pull
// broadcasting. let's keep doing this for
// feature parity to not break anyone's project.
// TODO make this more simple / unnecessary later.
identity.ClearDirtyComponentsDirtyBits();
}
// return the serialization
return serializations[identity];
}
// helper function to get the right serialization for a connection
static NetworkWriter GetEntitySerializationForConnection(NetworkIdentity identity, NetworkConnectionToClient connection)
{
// get serialization for this entity (cached)
Serialization serialization = GetEntitySerialization(identity);
NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(Time.time);
// is this entity owned by this connection?
bool owned = identity.connectionToClient == connection;
@ -1458,20 +1399,6 @@ static NetworkWriter GetEntitySerializationForConnection(NetworkIdentity identit
return null;
}
// helper function to clean up cached serializations
static void CleanupSerializations()
{
// return serialized writers to pool, clear set
// TODO this is for feature parity before push->pull change.
// make this more simple / unnecessary later.
foreach (Serialization entry in serializations.Values)
{
NetworkWriterPool.Recycle(entry.ownerWriter);
NetworkWriterPool.Recycle(entry.observersWriter);
}
serializations.Clear();
}
// helper function to clear dirty bits of all spawned entities
static void ClearSpawnedDirtyBits()
{
@ -1517,6 +1444,20 @@ static void BroadcastToConnection(NetworkConnectionToClient connection)
};
connection.Send(message);
}
// 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.)
//
// NOTE: this is what we did before push->pull
// broadcasting. let's keep doing this for
// feature parity to not break anyone's project.
// TODO make this more simple / unnecessary later.
identity.ClearDirtyComponentsDirtyBits();
}
// spawned list should have no null entries because we
// always call Remove in OnObjectDestroy everywhere.
@ -1582,9 +1523,6 @@ static void Broadcast()
connection.Update();
}
// return serialized writers to pool
CleanupSerializations();
// TODO we already clear the serialized component's dirty bits above
// might as well clear everything???
//