mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
perf: NetworkIdentity dirty masks via callbacks instead of iteration
This commit is contained in:
parent
9ba726011f
commit
91b0d43029
@ -150,10 +150,21 @@ protected void SetSyncVarHookGuard(ulong dirtyBit, bool value)
|
|||||||
syncVarHookGuard &= ~dirtyBit;
|
syncVarHookGuard &= ~dirtyBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// callback for both SyncObject and SyncVar dirty bit setters.
|
||||||
|
// called once it becomes dirty, not called again while already dirty.
|
||||||
|
// -> we only want to follow the .netIdentity memory indirection once
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void OnBecameDirty()
|
||||||
|
{
|
||||||
|
netIdentity.OnBecameDirty(this);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
void SetSyncObjectDirtyBit(ulong dirtyBit)
|
void SetSyncObjectDirtyBit(ulong dirtyBit)
|
||||||
{
|
{
|
||||||
|
bool clean = syncObjectDirtyBits == 0;
|
||||||
syncObjectDirtyBits |= dirtyBit;
|
syncObjectDirtyBits |= dirtyBit;
|
||||||
|
if (clean) OnBecameDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Set as dirty so that it's synced to clients again.</summary>
|
/// <summary>Set as dirty so that it's synced to clients again.</summary>
|
||||||
@ -161,7 +172,9 @@ void SetSyncObjectDirtyBit(ulong dirtyBit)
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SetSyncVarDirtyBit(ulong dirtyBit)
|
public void SetSyncVarDirtyBit(ulong dirtyBit)
|
||||||
{
|
{
|
||||||
|
bool clean = syncVarDirtyBits == 0;
|
||||||
syncVarDirtyBits |= dirtyBit;
|
syncVarDirtyBits |= dirtyBit;
|
||||||
|
if (clean) OnBecameDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Set as dirty to trigger OnSerialize & send. Dirty bits are cleared after the send.</summary>
|
/// <summary>Set as dirty to trigger OnSerialize & send. Dirty bits are cleared after the send.</summary>
|
||||||
|
@ -218,6 +218,14 @@ public static Dictionary<uint, NetworkIdentity> spawned
|
|||||||
// which means we can't allow > 64 components (it's enough).
|
// which means we can't allow > 64 components (it's enough).
|
||||||
const int MaxNetworkBehaviours = 64;
|
const int MaxNetworkBehaviours = 64;
|
||||||
|
|
||||||
|
// NetworkBehaviour's dirty masks.
|
||||||
|
// set from NetworkBehaviour.OnBecameDirty callback.
|
||||||
|
ulong serverOwnerInitialMask; // all initial components' bits are set
|
||||||
|
ulong serverOwnerDirtyMask; // only dirty components' bits are set
|
||||||
|
ulong serverObserversDirtyMask; // all initial components' bits are set
|
||||||
|
ulong serverObserversInitialMask; // only dirty components' bits are set
|
||||||
|
ulong clientDirtyMask; // only dirty components. client doesn't send initial.
|
||||||
|
|
||||||
// current visibility
|
// current visibility
|
||||||
//
|
//
|
||||||
// Default = use interest management
|
// Default = use interest management
|
||||||
@ -313,12 +321,30 @@ internal void InitializeNetworkBehaviours()
|
|||||||
NetworkBehaviours = GetComponents<NetworkBehaviour>();
|
NetworkBehaviours = GetComponents<NetworkBehaviour>();
|
||||||
ValidateComponents();
|
ValidateComponents();
|
||||||
|
|
||||||
|
// reset the masks before initializing them.
|
||||||
|
// during tests, we may create an identity, then change sync mode,
|
||||||
|
// then call Awake() to initialize the masks properly.
|
||||||
|
// if we don't reset them first, then we OR into the first state.
|
||||||
|
serverOwnerInitialMask = serverObserversInitialMask = 0;
|
||||||
|
|
||||||
// initialize each one
|
// initialize each one
|
||||||
for (int i = 0; i < NetworkBehaviours.Length; ++i)
|
for (int i = 0; i < NetworkBehaviours.Length; ++i)
|
||||||
{
|
{
|
||||||
NetworkBehaviour component = NetworkBehaviours[i];
|
NetworkBehaviour component = NetworkBehaviours[i];
|
||||||
component.netIdentity = this;
|
component.netIdentity = this;
|
||||||
component.ComponentIndex = (byte)i;
|
component.ComponentIndex = (byte)i;
|
||||||
|
|
||||||
|
ulong nthBit = (1u << component.ComponentIndex);
|
||||||
|
|
||||||
|
// build a mask with all owner components' bits set for initialState
|
||||||
|
// -> for initial, all components are synced to owner no matter what.
|
||||||
|
// -> so simply set a bit for every component index
|
||||||
|
serverOwnerInitialMask |= nthBit;
|
||||||
|
|
||||||
|
// build a mask with all observer components' bits set initialState
|
||||||
|
// -> for initial, only SyncMode.Observers components are synced
|
||||||
|
if (component.syncMode == SyncMode.Observers)
|
||||||
|
serverObserversInitialMask |= nthBit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,29 +859,28 @@ internal void OnStopAuthority()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build dirty mask for server owner & observers (= all dirty components).
|
// NetworkBehaviour OnBecameDirty calls NetworkIdentity callback with index
|
||||||
// faster to do it in one iteration instead of iterating separately.
|
internal void OnBecameDirty(NetworkBehaviour component)
|
||||||
(ulong, ulong) ServerDirtyMasks(bool initialState)
|
|
||||||
{
|
{
|
||||||
ulong ownerMask = 0;
|
ulong nthBit = (1u << component.ComponentIndex);
|
||||||
ulong observerMask = 0;
|
|
||||||
|
|
||||||
NetworkBehaviour[] components = NetworkBehaviours;
|
// ensure either isServer or isClient are set.
|
||||||
for (int i = 0; i < components.Length; ++i)
|
// ensures tests are obvious. without proper setup, it should throw.
|
||||||
|
if (!isClient && !isServer)
|
||||||
|
Debug.LogWarning("NetworkIdentity.OnBecameDirty(): neither isClient nor isServer are true. Improper setup?");
|
||||||
|
|
||||||
|
// set for server & client both.
|
||||||
|
// OnSerialize will decide how to use them.
|
||||||
|
if (isServer)
|
||||||
{
|
{
|
||||||
NetworkBehaviour component = components[i];
|
|
||||||
|
|
||||||
bool dirty = component.IsDirty();
|
|
||||||
ulong nthBit = (1u << i);
|
|
||||||
|
|
||||||
// owner needs to be considered for both SyncModes, because
|
// owner needs to be considered for both SyncModes, because
|
||||||
// Observers mode always includes the Owner.
|
// Observers mode always includes the Owner.
|
||||||
//
|
//
|
||||||
// for initial, it should always sync owner.
|
// for initial, it should always sync owner.
|
||||||
// for delta, only for ServerToClient and only if dirty.
|
// for delta, only for ServerToClient and only if dirty.
|
||||||
// ClientToServer comes from the owner client.
|
// ClientToServer comes from the owner client.
|
||||||
if (initialState || (component.syncDirection == SyncDirection.ServerToClient && dirty))
|
if (component.syncDirection == SyncDirection.ServerToClient)
|
||||||
ownerMask |= nthBit;
|
serverOwnerDirtyMask |= nthBit;
|
||||||
|
|
||||||
// observers need to be considered only in Observers mode
|
// observers need to be considered only in Observers mode
|
||||||
//
|
//
|
||||||
@ -863,21 +888,10 @@ internal void OnStopAuthority()
|
|||||||
// for delta, only if dirty.
|
// for delta, only if dirty.
|
||||||
// SyncDirection is irrelevant, as both are broadcast to
|
// SyncDirection is irrelevant, as both are broadcast to
|
||||||
// observers which aren't the owner.
|
// observers which aren't the owner.
|
||||||
if (component.syncMode == SyncMode.Observers && (initialState || dirty))
|
if (component.syncMode == SyncMode.Observers)
|
||||||
observerMask |= nthBit;
|
serverObserversDirtyMask |= nthBit;
|
||||||
}
|
}
|
||||||
|
if (isClient)
|
||||||
return (ownerMask, observerMask);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build dirty mask for client.
|
|
||||||
// server always knows initialState, so we don't need it here.
|
|
||||||
ulong ClientDirtyMask()
|
|
||||||
{
|
|
||||||
ulong mask = 0;
|
|
||||||
|
|
||||||
NetworkBehaviour[] components = NetworkBehaviours;
|
|
||||||
for (int i = 0; i < components.Length; ++i)
|
|
||||||
{
|
{
|
||||||
// on the client, we need to consider different sync scenarios:
|
// on the client, we need to consider different sync scenarios:
|
||||||
//
|
//
|
||||||
@ -887,16 +901,13 @@ ulong ClientDirtyMask()
|
|||||||
// serialize only if owned.
|
// serialize only if owned.
|
||||||
|
|
||||||
// on client, only consider owned components with SyncDirection to server
|
// on client, only consider owned components with SyncDirection to server
|
||||||
NetworkBehaviour component = components[i];
|
|
||||||
if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
if (isOwned && component.syncDirection == SyncDirection.ClientToServer)
|
||||||
{
|
{
|
||||||
// set the n-th bit if dirty
|
// set the n-th bit if dirty
|
||||||
// shifting from small to large numbers is varint-efficient.
|
// shifting from small to large numbers is varint-efficient.
|
||||||
if (component.IsDirty()) mask |= (1u << i);
|
clientDirtyMask |= (1u << component.ComponentIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if n-th component is dirty.
|
// check if n-th component is dirty.
|
||||||
@ -920,12 +931,9 @@ internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, Netw
|
|||||||
|
|
||||||
// check which components are dirty for owner / observers.
|
// check which components are dirty for owner / observers.
|
||||||
// this is quite complicated with SyncMode + SyncDirection.
|
// this is quite complicated with SyncMode + SyncDirection.
|
||||||
// see the function for explanation.
|
// see InitializeNetworkBehaviours and OnBecameDirty for explanations.
|
||||||
//
|
ulong ownerMask = initialState ? serverOwnerInitialMask : serverOwnerDirtyMask;
|
||||||
// instead of writing a 1 byte index per component,
|
ulong observerMask = initialState ? serverObserversInitialMask : serverObserversDirtyMask;
|
||||||
// we limit components to 64 bits and write one ulong instead.
|
|
||||||
// the ulong is also varint compressed for minimum bandwidth.
|
|
||||||
(ulong ownerMask, ulong observerMask) = ServerDirtyMasks(initialState);
|
|
||||||
|
|
||||||
// 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!
|
||||||
@ -985,7 +993,7 @@ internal void SerializeClient(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();
|
ulong dirtyMask = clientDirtyMask;
|
||||||
|
|
||||||
// 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.
|
||||||
@ -1325,6 +1333,10 @@ internal void Reset()
|
|||||||
// clear all component's dirty bits no matter what
|
// clear all component's dirty bits no matter what
|
||||||
internal void ClearAllComponentsDirtyBits()
|
internal void ClearAllComponentsDirtyBits()
|
||||||
{
|
{
|
||||||
|
serverOwnerDirtyMask = 0;
|
||||||
|
serverObserversDirtyMask = 0;
|
||||||
|
clientDirtyMask = 0;
|
||||||
|
|
||||||
foreach (NetworkBehaviour comp in NetworkBehaviours)
|
foreach (NetworkBehaviour comp in NetworkBehaviours)
|
||||||
{
|
{
|
||||||
comp.ClearAllDirtyBits();
|
comp.ClearAllDirtyBits();
|
||||||
|
@ -44,10 +44,18 @@ public void SerializeAndDeserializeAll()
|
|||||||
serverOwnerComp.syncMode = clientOwnerComp.syncMode = SyncMode.Owner;
|
serverOwnerComp.syncMode = clientOwnerComp.syncMode = SyncMode.Owner;
|
||||||
serverObserversComp.syncMode = clientObserversComp.syncMode = SyncMode.Observers;
|
serverObserversComp.syncMode = clientObserversComp.syncMode = SyncMode.Observers;
|
||||||
|
|
||||||
|
// syncMode was changed after spawning.
|
||||||
|
// need to reinitialize the initial masks.
|
||||||
|
serverIdentity.InitializeNetworkBehaviours();
|
||||||
|
|
||||||
// set unique values on server components
|
// set unique values on server components
|
||||||
serverOwnerComp.value = "42";
|
serverOwnerComp.value = "42";
|
||||||
serverObserversComp.value = 42;
|
serverObserversComp.value = 42;
|
||||||
|
|
||||||
|
// TODO FIX
|
||||||
|
// ownerWriter: has owner comp and observers comp (since it's for owner)
|
||||||
|
// observers writer: only has the comp for observers
|
||||||
|
|
||||||
// serialize server object
|
// serialize server object
|
||||||
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
|
|
||||||
@ -89,6 +97,10 @@ public void SerializationException()
|
|||||||
serverCompExc.syncMode = clientCompExc.syncMode = SyncMode.Observers;
|
serverCompExc.syncMode = clientCompExc.syncMode = SyncMode.Observers;
|
||||||
serverComp2.syncMode = clientComp2.syncMode = SyncMode.Owner;
|
serverComp2.syncMode = clientComp2.syncMode = SyncMode.Owner;
|
||||||
|
|
||||||
|
// syncMode was changed after spawning.
|
||||||
|
// need to reinitialize the initial masks.
|
||||||
|
serverIdentity.InitializeNetworkBehaviours();
|
||||||
|
|
||||||
// set unique values on server components
|
// set unique values on server components
|
||||||
serverComp2.value = "42";
|
serverComp2.value = "42";
|
||||||
|
|
||||||
@ -270,30 +282,35 @@ public void SerializeAndDeserialize_ClientToServer_NOT_OWNED()
|
|||||||
[Test]
|
[Test]
|
||||||
public void SerializeServer_OwnerMode_ClientToServer()
|
public void SerializeServer_OwnerMode_ClientToServer()
|
||||||
{
|
{
|
||||||
CreateNetworked(out GameObject _, out NetworkIdentity identity,
|
CreateNetworkedAndSpawn(
|
||||||
out SyncVarTest1NetworkBehaviour comp);
|
out _, out NetworkIdentity serverIdentity, out SyncVarTest1NetworkBehaviour serverComp,
|
||||||
|
out _, out NetworkIdentity clientIdentity, out SyncVarTest1NetworkBehaviour clientComp);
|
||||||
|
|
||||||
// pretend to be owned
|
// pretend to be owned
|
||||||
identity.isOwned = true;
|
serverIdentity.isOwned = true;
|
||||||
comp.syncMode = SyncMode.Owner;
|
serverComp.syncMode = SyncMode.Owner;
|
||||||
|
|
||||||
// set to CLIENT with some unique values
|
// set to CLIENT with some unique values
|
||||||
// and set connection to server to pretend we are the owner.
|
// and set connection to server to pretend we are the owner.
|
||||||
comp.syncDirection = SyncDirection.ClientToServer;
|
serverComp.syncDirection = SyncDirection.ClientToServer;
|
||||||
comp.value = 12345;
|
serverComp.value = 12345;
|
||||||
|
|
||||||
|
// syncMode was changed after spawning.
|
||||||
|
// need to reinitialize the initial masks.
|
||||||
|
serverIdentity.InitializeNetworkBehaviours();
|
||||||
|
|
||||||
// initial: should still write for owner
|
// initial: should still write for owner
|
||||||
identity.SerializeServer(true, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
Debug.Log("initial ownerWriter: " + ownerWriter);
|
Debug.Log("initial ownerWriter: " + ownerWriter);
|
||||||
Debug.Log("initial observerWriter: " + observersWriter);
|
Debug.Log("initial observerWriter: " + observersWriter);
|
||||||
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
||||||
Assert.That(observersWriter.Position, Is.EqualTo(0));
|
Assert.That(observersWriter.Position, Is.EqualTo(0));
|
||||||
|
|
||||||
// delta: ClientToServer comes from the client
|
// delta: ClientToServer comes from the client
|
||||||
++comp.value; // change something
|
++serverComp.value; // change something
|
||||||
ownerWriter.Position = 0;
|
ownerWriter.Position = 0;
|
||||||
observersWriter.Position = 0;
|
observersWriter.Position = 0;
|
||||||
identity.SerializeServer(false, ownerWriter, observersWriter);
|
serverIdentity.SerializeServer(false, ownerWriter, observersWriter);
|
||||||
Debug.Log("delta ownerWriter: " + ownerWriter);
|
Debug.Log("delta ownerWriter: " + ownerWriter);
|
||||||
Debug.Log("delta observersWriter: " + observersWriter);
|
Debug.Log("delta observersWriter: " + observersWriter);
|
||||||
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
Assert.That(ownerWriter.Position, Is.EqualTo(0));
|
||||||
@ -309,6 +326,7 @@ public void SerializeServer_ObserversMode_ClientToServer()
|
|||||||
out SyncVarTest1NetworkBehaviour comp);
|
out SyncVarTest1NetworkBehaviour comp);
|
||||||
|
|
||||||
// pretend to be owned
|
// pretend to be owned
|
||||||
|
identity.isServer = true;
|
||||||
identity.isOwned = true;
|
identity.isOwned = true;
|
||||||
comp.syncMode = SyncMode.Observers;
|
comp.syncMode = SyncMode.Observers;
|
||||||
|
|
||||||
@ -317,6 +335,10 @@ public void SerializeServer_ObserversMode_ClientToServer()
|
|||||||
comp.syncDirection = SyncDirection.ClientToServer;
|
comp.syncDirection = SyncDirection.ClientToServer;
|
||||||
comp.value = 12345;
|
comp.value = 12345;
|
||||||
|
|
||||||
|
// syncMode was changed after spawning.
|
||||||
|
// need to reinitialize the initial masks.
|
||||||
|
identity.InitializeNetworkBehaviours();
|
||||||
|
|
||||||
// initial: should write something for owner and observers
|
// initial: should write something for owner and observers
|
||||||
identity.SerializeServer(true, ownerWriter, observersWriter);
|
identity.SerializeServer(true, ownerWriter, observersWriter);
|
||||||
Debug.Log("initial ownerWriter: " + ownerWriter);
|
Debug.Log("initial ownerWriter: " + ownerWriter);
|
||||||
@ -324,6 +346,9 @@ public void SerializeServer_ObserversMode_ClientToServer()
|
|||||||
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
Assert.That(ownerWriter.Position, Is.GreaterThan(0));
|
||||||
Assert.That(observersWriter.Position, Is.GreaterThan(0));
|
Assert.That(observersWriter.Position, Is.GreaterThan(0));
|
||||||
|
|
||||||
|
// reset dirty bits after serializing
|
||||||
|
identity.ClearAllComponentsDirtyBits();
|
||||||
|
|
||||||
// delta: should only write for observers
|
// delta: should only write for observers
|
||||||
++comp.value; // change something
|
++comp.value; // change something
|
||||||
ownerWriter.Position = 0;
|
ownerWriter.Position = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user