ok let's go

This commit is contained in:
mischa 2023-08-03 13:14:02 +08:00
parent b65292cacd
commit da47f71eef
4 changed files with 21 additions and 60 deletions

View File

@ -159,7 +159,17 @@ protected void SetSyncVarHookGuard(ulong dirtyBit, bool value)
// called once it becomes dirty, not called again while already dirty. // called once it becomes dirty, not called again while already dirty.
// we only want to follow the .netIdentity memory indirection once. // we only want to follow the .netIdentity memory indirection once.
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
void OnBecameDirty() => netIdentity.OnBecameDirty(); void OnBecameDirty()
{
// previously, NetworkBehaviour.OnBecameDirty would call
// NetworkIdentity.OnBecameDirty, which would insert into dirtyObjects.
//
// instead, let's try to insert directly.
// avoids the NetworkIdentity indirection.
// the only indirection is NetworkServer.dirtyObjects.
// we insert 'netIdentity', which is a reference.
NetworkServer.dirtySpawned.Add(netIdentity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
void SetSyncObjectDirtyBit(ulong dirtyBit) void SetSyncObjectDirtyBit(ulong dirtyBit)

View File

@ -806,36 +806,6 @@ internal void OnStopLocalPlayer()
} }
} }
// NetworkBehaviour OnBecameDirty calls NetworkIdentity callback with index
bool addedToDirtySpawned = false;
internal void OnBecameDirty()
{
// ensure either isServer or isClient are set.
// 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?");
if (isServer)
{
// only add to dirty spawned once.
// don't run the insertion twice.
if (!addedToDirtySpawned)
{
// insert into server dirty objects if not inserted yet
// TODO keep a bool so we don't insert all the time?
// only add if observed.
// otherwise no point in adding + iterating from broadcast.
if (observers.Count > 0)
{
NetworkServer.dirtySpawned.Add(this);
addedToDirtySpawned = true;
}
}
}
}
// build dirty mask for server owner & observers (= all dirty components). // build dirty mask for server owner & observers (= all dirty components).
// faster to do it in one iteration instead of iterating separately. // faster to do it in one iteration instead of iterating separately.
(ulong, ulong, ulong) ServerDirtyMasks(bool initialState) (ulong, ulong, ulong) ServerDirtyMasks(bool initialState)
@ -1000,10 +970,6 @@ internal void SerializeServer(bool initialState, NetworkWriter ownerWriter, Netw
// the NetworkIdentity from dirtyObjects just yet. // the NetworkIdentity from dirtyObjects just yet.
// otherwise if we remove before it was synced, we would miss a sync. // otherwise if we remove before it was synced, we would miss a sync.
pendingDirty = pendingMask != 0; pendingDirty = pendingMask != 0;
// if none are still pending, this will be removed from dirtyObjects.
// in that case, clear our flag (the flag is only for performance).
if (!pendingDirty) addedToDirtySpawned = false;
} }
// serialize components into writer on the client. // serialize components into writer on the client.

View File

@ -56,11 +56,8 @@ public static partial class NetworkServer
// otherwise each NetworkBehaviour would need an Update() to wait until // otherwise each NetworkBehaviour would need an Update() to wait until
// syncInterval is elapsed, which is more expansive then simply adding // syncInterval is elapsed, which is more expansive then simply adding
// a few false positives here. // a few false positives here.
// internal static readonly HashSet<NetworkIdentity> dirtySpawned =
// NetworkIdentity adds itself to dirtySpawned exactly once. new HashSet<NetworkIdentity>();
// we can safely use a List<T> here, faster than a Dictionary with enumerators.
internal static readonly List<NetworkIdentity> dirtySpawned =
new List<NetworkIdentity>();
/// <summary>Single player mode can use dontListen to not accept incoming connections</summary> /// <summary>Single player mode can use dontListen to not accept incoming connections</summary>
// see also: https://github.com/vis2k/Mirror/pull/2595 // see also: https://github.com/vis2k/Mirror/pull/2595
@ -1665,6 +1662,7 @@ static NetworkWriter SerializeForConnection(
return null; return null;
} }
static readonly List<NetworkIdentity> dirtyRemoved = new List<NetworkIdentity>();
static void BroadcastDirtySpawned() static void BroadcastDirtySpawned()
{ {
// PULL-Broadcasting vs. PUSH-Broadcasting: // PULL-Broadcasting vs. PUSH-Broadcasting:
@ -1691,10 +1689,8 @@ static void BroadcastDirtySpawned()
// only iterate NetworkIdentities which we know to be dirty. // only iterate NetworkIdentities which we know to be dirty.
// for example, in an MMO we don't need to iterate NPCs, // for example, in an MMO we don't need to iterate NPCs,
// item drops, idle monsters etc. every Broadcast. // item drops, idle monsters etc. every Broadcast.
for (int i = 0; i < dirtySpawned.Count; ++i) foreach (NetworkIdentity identity in dirtySpawned)
{ {
NetworkIdentity identity = dirtySpawned[i];
// make sure it's not null or destroyed. // make sure it's not null or destroyed.
// (which can happen if someone uses // (which can happen if someone uses
// GameObject.Destroy instead of // GameObject.Destroy instead of
@ -1741,8 +1737,7 @@ static void BroadcastDirtySpawned()
// List.RemoveAt(i) is O(N). // List.RemoveAt(i) is O(N).
// instead, use O(1) swap-remove from Rust. // instead, use O(1) swap-remove from Rust.
// dirtySpawned.RemoveAt(i); // dirtySpawned.RemoveAt(i);
dirtyRemoved.Add(identity);
dirtySpawned.SwapRemove(i);
// the last element was moved to 'i'. // the last element was moved to 'i'.
// count was reduced by 1. // count was reduced by 1.
@ -1757,6 +1752,11 @@ static void BroadcastDirtySpawned()
else Debug.LogWarning($"Found 'null' entry in dirtySpawned. Please call NetworkServer.Destroy to destroy networked objects. Don't use GameObject.Destroy."); else Debug.LogWarning($"Found 'null' entry in dirtySpawned. Please call NetworkServer.Destroy to destroy networked objects. Don't use GameObject.Destroy.");
} }
} }
// safely remove after iterating
foreach (NetworkIdentity identity in dirtyRemoved)
dirtySpawned.Remove(identity);
dirtyRemoved.Clear();
} }
// helper function to check a connection for inactivity and disconnect if necessary // helper function to check a connection for inactivity and disconnect if necessary

View File

@ -60,20 +60,5 @@ public static bool TryDequeue<T>(this Queue<T> source, out T element)
return false; return false;
} }
#endif #endif
// List.RemoveAt is O(N).
// implement Rust's swap-remove O(1) removal technique.
public static void SwapRemove<T>(this List<T> list, int index)
{
// we can only swap if we have at least two elements
if (list.Count >= 2)
{
// copy last element to index
list[index] = list[list.Count - 1];
}
// remove last element
list.RemoveAt(list.Count - 1);
}
} }
} }