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)
|
if (identity != null)
|
||||||
{
|
{
|
||||||
// 'Reliable' sync: send Reliable components over reliable.
|
// '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
|
// serialize reliable and unreliable components in only one iteration.
|
||||||
// (if anything was serialized this time)
|
// serializing reliable and unreliable separately in two iterations would be too costly.
|
||||||
identity.SerializeClient_ReliableComponents(writer);
|
identity.SerializeClient(writerReliable, writerUnreliableBaseline, writerUnreliableDelta, unreliableBaselineElapsed);
|
||||||
if (writer.Position > 0)
|
|
||||||
|
// any reliable components serialization?
|
||||||
|
if (writerReliable.Position > 0)
|
||||||
{
|
{
|
||||||
// send state update message
|
// send state update message
|
||||||
EntityStateMessage message = new EntityStateMessage
|
EntityStateMessage message = new EntityStateMessage
|
||||||
{
|
{
|
||||||
netId = identity.netId,
|
netId = identity.netId,
|
||||||
payload = writer.ToArraySegment()
|
payload = writerReliable.ToArraySegment()
|
||||||
};
|
};
|
||||||
Send(message);
|
Send(message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 'Unreliable' sync: send Unreliable components over unreliable
|
// any unreliable components serialization?
|
||||||
// state is 'initial' for reliable baseline, and 'not initial' for unreliable deltas.
|
// we always send unreliable deltas to ensure interpolation always has a data point that arrives immediately.
|
||||||
// note that syncInterval is always ignored for unreliable in order to have tick aligned [SyncVars].
|
if (writerUnreliableDelta.Position > 0)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
EntityStateMessageUnreliableDelta message = new EntityStateMessageUnreliableDelta
|
EntityStateMessageUnreliableDelta message = new EntityStateMessageUnreliableDelta
|
||||||
{
|
{
|
||||||
|
// baselineTick: the last unreliable baseline to compare against
|
||||||
baselineTick = identity.lastUnreliableBaselineSent,
|
baselineTick = identity.lastUnreliableBaselineSent,
|
||||||
netId = identity.netId,
|
netId = identity.netId,
|
||||||
payload = writer.ToArraySegment()
|
payload = writerUnreliableDelta.ToArraySegment()
|
||||||
};
|
};
|
||||||
|
|
||||||
Send(message, Channels.Unreliable);
|
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.
|
// time for unreliable baseline sync?
|
||||||
// this will likely arrive slightly after the unreliable delta above.
|
// we always send this after the unreliable delta,
|
||||||
if (unreliableBaselineElapsed)
|
// 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.
|
||||||
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
|
if (unreliableBaselineElapsed)
|
||||||
{
|
{
|
||||||
// get serialization for this entity viewed by this connection
|
if (writerUnreliableBaseline.Position > 0)
|
||||||
// (if anything was serialized this time)
|
|
||||||
identity.SerializeClient_UnreliableComponents(true, writer);
|
|
||||||
|
|
||||||
if (writer.Position > 0)
|
|
||||||
{
|
{
|
||||||
// remember last sent baseline tick for this entity.
|
// remember last sent baseline tick for this entity.
|
||||||
// (byte) to minimize bandwidth. we don't need the full tick,
|
// (byte) to minimize bandwidth. we don't need the full tick,
|
||||||
@ -1816,7 +1798,7 @@ static void BroadcastToServer(bool unreliableBaselineElapsed)
|
|||||||
{
|
{
|
||||||
baselineTick = identity.lastUnreliableBaselineSent,
|
baselineTick = identity.lastUnreliableBaselineSent,
|
||||||
netId = identity.netId,
|
netId = identity.netId,
|
||||||
payload = writer.ToArraySegment()
|
payload = writerUnreliableBaseline.ToArraySegment()
|
||||||
};
|
};
|
||||||
Send(message, Channels.Reliable);
|
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
|
// ensure NetworkBehaviours are valid before usage
|
||||||
ValidateComponents();
|
ValidateComponents();
|
||||||
@ -1278,7 +1280,8 @@ internal void SerializeClient_ReliableComponents(NetworkWriter writer)
|
|||||||
// instead of writing a 1 byte index per component,
|
// instead of writing a 1 byte index per component,
|
||||||
// we limit components to 64 bits and write one ulong instead.
|
// we limit components to 64 bits and write one ulong instead.
|
||||||
// the ulong is also varint compressed for minimum bandwidth.
|
// 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.
|
// varint compresses the mask to 1 byte in most cases.
|
||||||
// instead of writing an 8 byte ulong.
|
// 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.
|
// if nothing dirty, then don't even write the mask.
|
||||||
// otherwise, every unchanged object would send a 1 byte dirty 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
|
// serialize all components
|
||||||
// perf: only iterate if dirty mask has dirty bits.
|
// perf: only iterate if dirty mask has dirty bits.
|
||||||
if (dirtyMask != 0)
|
if (dirtyMaskReliable != 0 || dirtyMaskUnreliable != 0)
|
||||||
{
|
{
|
||||||
// serialize all components
|
// serialize all components
|
||||||
for (int i = 0; i < components.Length; ++i)
|
for (int i = 0; i < components.Length; ++i)
|
||||||
{
|
{
|
||||||
NetworkBehaviour comp = components[i];
|
NetworkBehaviour comp = components[i];
|
||||||
|
|
||||||
|
// RELIABLE SERIALIZATION //////////////////////////////////
|
||||||
// is this component dirty?
|
// is this component dirty?
|
||||||
// reuse the mask instead of calling comp.IsDirty() again here.
|
// reuse the mask instead of calling comp.IsDirty() again here.
|
||||||
if (IsDirty(dirtyMask, i))
|
if (IsDirty(dirtyMaskReliable, i))
|
||||||
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||||
{
|
{
|
||||||
// serialize into writer.
|
// serialize into writer.
|
||||||
// server always knows initialState, we never need to send it
|
// 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.
|
// clear dirty bits for the components that we serialized.
|
||||||
// do not clear for _all_ components, only the ones that
|
// 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.
|
// was elapsed, as then they wouldn't be synced.
|
||||||
comp.ClearAllDirtyBits();
|
comp.ClearAllDirtyBits();
|
||||||
}
|
}
|
||||||
}
|
// UNRELIABLE COMPONENTS ///////////////////////////////////
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
// is this component dirty?
|
// is this component dirty?
|
||||||
// reuse the mask instead of calling comp.IsDirty() again here.
|
// 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)
|
// if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||||
{
|
{
|
||||||
// serialize into writer.
|
// we always send the unreliable delta no matter what
|
||||||
comp.Serialize(writer, isBaseline);
|
comp.Serialize(writerUnreliableDelta, false);
|
||||||
|
|
||||||
// for unreliable components, only clear dirty bits after the reliable baseline.
|
// sometimes we need the unreliable baseline
|
||||||
// unreliable deltas aren't guaranteed to be delivered, no point in clearing bits.
|
if (unreliableBaseline)
|
||||||
if (isBaseline) comp.ClearAllDirtyBits();
|
{
|
||||||
|
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";
|
// clientComp.value = "42";
|
||||||
|
|
||||||
// serialize client object
|
// serialize client object
|
||||||
clientIdentity.SerializeClient_ReliableComponents(ownerWriter);
|
clientIdentity.SerializeClient(ownerWriter, new NetworkWriter(), new NetworkWriter(), false);
|
||||||
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ public void SerializeAndDeserialize_ClientToServer_NOT_OWNED()
|
|||||||
comp2.value = "67890";
|
comp2.value = "67890";
|
||||||
|
|
||||||
// serialize all
|
// serialize all
|
||||||
identity.SerializeClient_ReliableComponents(ownerWriter);
|
identity.SerializeClient(ownerWriter, new NetworkWriter(), new NetworkWriter(), false);
|
||||||
|
|
||||||
// shouldn't sync anything. because even though it's ClientToServer,
|
// shouldn't sync anything. because even though it's ClientToServer,
|
||||||
// we don't own this one so we shouldn't serialize & sync it.
|
// we don't own this one so we shouldn't serialize & sync it.
|
||||||
|
Loading…
Reference in New Issue
Block a user