mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
perf: client serialization in one iteration!
This commit is contained in:
parent
d8e33f933f
commit
eb4561fdd2
@ -1746,65 +1746,47 @@ static void BroadcastToServer(bool unreliableBaselineElapsed)
|
||||
if (identity != null)
|
||||
{
|
||||
// 'Reliable' sync: send Reliable components over reliable.
|
||||
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
||||
using (NetworkWriterPooled writerReliable = NetworkWriterPool.Get(),
|
||||
writerUnreliableDelta = NetworkWriterPool.Get(),
|
||||
writerUnreliableBaseline = NetworkWriterPool.Get())
|
||||
{
|
||||
// get serialization for this entity viewed by this connection
|
||||
// (if anything was serialized this time)
|
||||
identity.SerializeClient_ReliableComponents(writer);
|
||||
if (writer.Position > 0)
|
||||
// serialize reliable and unreliable components in only one iteration.
|
||||
// serializing reliable and unreliable separately in two iterations would be too costly.
|
||||
identity.SerializeClient(writerReliable, writerUnreliableBaseline, writerUnreliableDelta, unreliableBaselineElapsed);
|
||||
|
||||
// any reliable components serialization?
|
||||
if (writerReliable.Position > 0)
|
||||
{
|
||||
// send state update message
|
||||
EntityStateMessage message = new EntityStateMessage
|
||||
{
|
||||
netId = identity.netId,
|
||||
payload = writer.ToArraySegment()
|
||||
payload = writerReliable.ToArraySegment()
|
||||
};
|
||||
Send(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 'Unreliable' sync: send Unreliable components over unreliable
|
||||
// state is 'initial' for reliable baseline, and 'not initial' for unreliable deltas.
|
||||
// note that syncInterval is always ignored for unreliable in order to have tick aligned [SyncVars].
|
||||
// even if we pass SyncMethod.Reliable, it serializes with initialState=true.
|
||||
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
||||
{
|
||||
// get serialization for this entity viewed by this connection
|
||||
// (if anything was serialized this time)
|
||||
identity.SerializeClient_UnreliableComponents(false, writer);
|
||||
|
||||
// we always need the unreliable delta no matter what.
|
||||
// this ensures we can smoothly sync even during reliable baseline ticks.
|
||||
// (do this before baseline, since baseline clears dirty bits)
|
||||
if (writer.Position > 0)
|
||||
// any unreliable components serialization?
|
||||
// we always send unreliable deltas to ensure interpolation always has a data point that arrives immediately.
|
||||
if (writerUnreliableDelta.Position > 0)
|
||||
{
|
||||
EntityStateMessageUnreliableDelta message = new EntityStateMessageUnreliableDelta
|
||||
{
|
||||
// baselineTick: the last unreliable baseline to compare against
|
||||
baselineTick = identity.lastUnreliableBaselineSent,
|
||||
netId = identity.netId,
|
||||
payload = writer.ToArraySegment()
|
||||
payload = writerUnreliableDelta.ToArraySegment()
|
||||
};
|
||||
|
||||
Send(message, Channels.Unreliable);
|
||||
|
||||
// quake sends unreliable messages twice to make up for message drops.
|
||||
// this double bandwidth, but allows for smaller buffer time / faster sync.
|
||||
// best to turn this off unless the game is extremely fast paced.
|
||||
if (unreliableRedundancy) Send(message, Channels.Unreliable);
|
||||
}
|
||||
}
|
||||
|
||||
// if it's for a baseline sync, then send a reliable baseline message too.
|
||||
// this will likely arrive slightly after the unreliable delta above.
|
||||
if (unreliableBaselineElapsed)
|
||||
{
|
||||
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
||||
// time for unreliable baseline sync?
|
||||
// we always send this after the unreliable delta,
|
||||
// so there's a higher chance that it arrives after the delta.
|
||||
// in other words: so that the delta can still be used against previous baseline.
|
||||
if (unreliableBaselineElapsed)
|
||||
{
|
||||
// get serialization for this entity viewed by this connection
|
||||
// (if anything was serialized this time)
|
||||
identity.SerializeClient_UnreliableComponents(true, writer);
|
||||
|
||||
if (writer.Position > 0)
|
||||
if (writerUnreliableBaseline.Position > 0)
|
||||
{
|
||||
// remember last sent baseline tick for this entity.
|
||||
// (byte) to minimize bandwidth. we don't need the full tick,
|
||||
@ -1816,7 +1798,7 @@ static void BroadcastToServer(bool unreliableBaselineElapsed)
|
||||
{
|
||||
baselineTick = identity.lastUnreliableBaselineSent,
|
||||
netId = identity.netId,
|
||||
payload = writer.ToArraySegment()
|
||||
payload = writerUnreliableBaseline.ToArraySegment()
|
||||
};
|
||||
Send(message, Channels.Reliable);
|
||||
}
|
||||
|
@ -1265,7 +1265,9 @@ internal void SerializeServer_Broadcast_UnreliableComponents(bool isBaseline, Ne
|
||||
}
|
||||
}
|
||||
|
||||
internal void SerializeClient_ReliableComponents(NetworkWriter writer)
|
||||
// serialize Reliable and Unreliable components in one iteration.
|
||||
// having two separate functions doing two iterations would be too costly.
|
||||
internal void SerializeClient(NetworkWriter writerReliable, NetworkWriter writerUnreliableBaseline, NetworkWriter writerUnreliableDelta, bool unreliableBaseline)
|
||||
{
|
||||
// ensure NetworkBehaviours are valid before usage
|
||||
ValidateComponents();
|
||||
@ -1278,7 +1280,8 @@ internal void SerializeClient_ReliableComponents(NetworkWriter writer)
|
||||
// instead of writing a 1 byte index per component,
|
||||
// we limit components to 64 bits and write one ulong instead.
|
||||
// the ulong is also varint compressed for minimum bandwidth.
|
||||
ulong dirtyMask = ClientDirtyMask_ReliableComponents();
|
||||
ulong dirtyMaskReliable = ClientDirtyMask_ReliableComponents();
|
||||
ulong dirtyMaskUnreliable = ClientDirtyMask_UnreliableComponents();
|
||||
|
||||
// varint compresses the mask to 1 byte in most cases.
|
||||
// instead of writing an 8 byte ulong.
|
||||
@ -1289,25 +1292,28 @@ internal void SerializeClient_ReliableComponents(NetworkWriter writer)
|
||||
|
||||
// if nothing dirty, then don't even write the mask.
|
||||
// otherwise, every unchanged object would send a 1 byte dirty mask!
|
||||
if (dirtyMask != 0) Compression.CompressVarUInt(writer, dirtyMask);
|
||||
if (dirtyMaskReliable != 0) Compression.CompressVarUInt(writerReliable, dirtyMaskReliable);
|
||||
if (dirtyMaskUnreliable != 0) Compression.CompressVarUInt(writerUnreliableDelta, dirtyMaskUnreliable);
|
||||
if (dirtyMaskUnreliable != 0) Compression.CompressVarUInt(writerUnreliableBaseline, dirtyMaskUnreliable);
|
||||
|
||||
// serialize all components
|
||||
// perf: only iterate if dirty mask has dirty bits.
|
||||
if (dirtyMask != 0)
|
||||
if (dirtyMaskReliable != 0 || dirtyMaskUnreliable != 0)
|
||||
{
|
||||
// serialize all components
|
||||
for (int i = 0; i < components.Length; ++i)
|
||||
{
|
||||
NetworkBehaviour comp = components[i];
|
||||
|
||||
// RELIABLE SERIALIZATION //////////////////////////////////
|
||||
// is this component dirty?
|
||||
// reuse the mask instead of calling comp.IsDirty() again here.
|
||||
if (IsDirty(dirtyMask, i))
|
||||
if (IsDirty(dirtyMaskReliable, i))
|
||||
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||
{
|
||||
// serialize into writer.
|
||||
// server always knows initialState, we never need to send it
|
||||
comp.Serialize(writer, false);
|
||||
comp.Serialize(writerReliable, false);
|
||||
|
||||
// clear dirty bits for the components that we serialized.
|
||||
// do not clear for _all_ components, only the ones that
|
||||
@ -1317,57 +1323,26 @@ internal void SerializeClient_ReliableComponents(NetworkWriter writer)
|
||||
// was elapsed, as then they wouldn't be synced.
|
||||
comp.ClearAllDirtyBits();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SerializeClient_UnreliableComponents(bool isBaseline, NetworkWriter writer)
|
||||
{
|
||||
// ensure NetworkBehaviours are valid before usage
|
||||
ValidateComponents();
|
||||
NetworkBehaviour[] components = NetworkBehaviours;
|
||||
|
||||
// check which components are dirty.
|
||||
// this is quite complicated with SyncMode + SyncDirection.
|
||||
// see the function for explanation.
|
||||
//
|
||||
// instead of writing a 1 byte index per component,
|
||||
// we limit components to 64 bits and write one ulong instead.
|
||||
// the ulong is also varint compressed for minimum bandwidth.
|
||||
ulong dirtyMask = ClientDirtyMask_UnreliableComponents();
|
||||
|
||||
// varint compresses the mask to 1 byte in most cases.
|
||||
// instead of writing an 8 byte ulong.
|
||||
// 7 components fit into 1 byte. (previously 7 bytes)
|
||||
// 11 components fit into 2 bytes. (previously 11 bytes)
|
||||
// 16 components fit into 3 bytes. (previously 16 bytes)
|
||||
// TODO imer: server knows amount of comps, write N bytes instead
|
||||
|
||||
// if nothing dirty, then don't even write the mask.
|
||||
// otherwise, every unchanged object would send a 1 byte dirty mask!
|
||||
if (dirtyMask != 0) Compression.CompressVarUInt(writer, dirtyMask);
|
||||
|
||||
// serialize all components
|
||||
// perf: only iterate if dirty mask has dirty bits.
|
||||
if (dirtyMask != 0)
|
||||
{
|
||||
// serialize all components
|
||||
for (int i = 0; i < components.Length; ++i)
|
||||
{
|
||||
NetworkBehaviour comp = components[i];
|
||||
|
||||
// UNRELIABLE COMPONENTS ///////////////////////////////////
|
||||
// is this component dirty?
|
||||
// reuse the mask instead of calling comp.IsDirty() again here.
|
||||
if (IsDirty(dirtyMask, i))
|
||||
else if (IsDirty(dirtyMaskUnreliable, i))
|
||||
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||
{
|
||||
// serialize into writer.
|
||||
comp.Serialize(writer, isBaseline);
|
||||
// we always send the unreliable delta no matter what
|
||||
comp.Serialize(writerUnreliableDelta, false);
|
||||
|
||||
// for unreliable components, only clear dirty bits after the reliable baseline.
|
||||
// unreliable deltas aren't guaranteed to be delivered, no point in clearing bits.
|
||||
if (isBaseline) comp.ClearAllDirtyBits();
|
||||
// sometimes we need the unreliable baseline
|
||||
if (unreliableBaseline)
|
||||
{
|
||||
comp.Serialize(writerUnreliableBaseline, true);
|
||||
|
||||
// for unreliable components, only clear dirty bits after the reliable baseline.
|
||||
// unreliable deltas aren't guaranteed to be delivered, no point in clearing bits.
|
||||
comp.ClearAllDirtyBits();
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////////////////
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ public void SerializeClient_NotInitial_NotDirty_WritesNothing()
|
||||
// clientComp.value = "42";
|
||||
|
||||
// serialize client object
|
||||
clientIdentity.SerializeClient_ReliableComponents(ownerWriter);
|
||||
clientIdentity.SerializeClient(ownerWriter, new NetworkWriter(), new NetworkWriter(), false);
|
||||
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
@ -267,7 +267,7 @@ public void SerializeAndDeserialize_ClientToServer_NOT_OWNED()
|
||||
comp2.value = "67890";
|
||||
|
||||
// serialize all
|
||||
identity.SerializeClient_ReliableComponents(ownerWriter);
|
||||
identity.SerializeClient(ownerWriter, new NetworkWriter(), new NetworkWriter(), false);
|
||||
|
||||
// shouldn't sync anything. because even though it's ClientToServer,
|
||||
// we don't own this one so we shouldn't serialize & sync it.
|
||||
|
Loading…
Reference in New Issue
Block a user