Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

NetworkList changes made since the last tick are applied twice on a newly synchronized client

未關閉
#4,179 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 2 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
55/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
活躍
技術堆疊
csharp, unity
領域
networking

研究方向

Start at NetworkList synchronization and compare its behavior with NetworkVariable's WriteFieldSynchronization fix from #3081. Trace NetworkBehaviour.WriteNetworkVariableData, SynchronizeNetworkObjects, and NetworkBehaviourUpdater.ProcessDirtyObjectServer, then reproduce the late-join case; done means the new client's list matches the server without duplicate events.

由索引模型根據 Issue 內容生成。

描述

stat:awaiting-triage stat:reply-needed type:bug
Description

When a client is synchronized while a NetworkList<T> on an already spawned object still has unsent changes, that client applies those changes twice: once from the synchronization snapshot and again from the next tick's delta. Add, Insert, Remove, RemoveAt and Clear aren't idempotent, so the client's list ends up with duplicate entries, and every later index-based event lands on the wrong element on that client. Clients that were already connected are unaffected.

#3081 fixed this for NetworkVariable<T>: WriteFieldSynchronization serializes the previous value while changes are pending. NetworkList<T> doesn't override it, so NetworkBehaviour.WriteNetworkVariableData falls back to WriteField and writes the current contents while m_DirtyEvents is still populated. By the next tick the new client is an observer, so NetworkBehaviourUpdater.ProcessDirtyObjectServer sends it the same events.

The frame ordering makes this deterministic, not a race. Connection requests and approvals are handled in EarlyUpdate (HandleConnectionApproval → SynchronizeNetworkObjects, which adds the observer and serializes the snapshot immediately), while deltas go out on the tick in PreUpdate. Any structural change made in Update or LateUpdate of the previous frame is therefore always still pending when the snapshot is taken. That includes changes made while completing an async ConnectionApprovalCallback (response.Pending = false in the same frame), which is how we hit it.

NGO already flushes pending deltas before NetworkShow (NetworkBehaviourUpdater.ForceSendIfDirtyOnNetworkShow) and before ownership changes (#3081), but not before synchronizing a connecting client.

Reproduce Steps
  1. Add this component to an in-scene placed NetworkObject (scene management on, connection approval off):
using Unity.Netcode;
using UnityEngine;

public class NetworkListLateJoinRepro : NetworkBehaviour
{
    private NetworkList<int> values = new();

    private void Update()
    {
        // One entry per frame; whatever was added after the last tick is still pending
        // when the next connection request is handled in EarlyUpdate.
        if (IsServer && IsSpawned) values.Add(Time.frameCount);
    }

    public override void OnNetworkSpawn()
    {
        if (IsServer) return;

        int newestInSnapshot = values.Count > 0 ? values[values.Count - 1] : -1;
        values.OnListChanged += change =>
        {
            if (change.Type == NetworkListEvent<int>.EventType.Add && change.Value <= newestInSnapshot)
                Debug.LogError($"{change.Value} was already in the synchronization snapshot and was added again.");
        };
    }
}
  1. Start a server (dedicated or host).
  2. Connect a client.
  3. See the error on the client: the entries the server added after its last tick, before it handled the connection request, arrive in the snapshot and again as Add events. The client's values.Count ends up higher than the server's.
Actual Outcome

The newly connected client applies the pending events on top of a snapshot that already contains them. It gets duplicate entries, and later Value/RemoveAt/Insert events hit the wrong indices on that client only. In our game, every entry the server added while completing the first player's approval (bots joining the match) appeared twice in that player's stats, player info and team lists, which broke the scoreboard.

Expected Outcome

The client's list matches the server's, the way NetworkVariable<T> behaves since #3081.

Screenshots

n/a

Environment
  • OS: Windows 11
  • Unity Version: 6000.6.0f1
  • Netcode Version: 2.13.3. The same code is in the 3.0.0 package and on develop-2.0.0 and develop-3.x.x as of 2026-10-03.
  • Netcode Commit hash: n/a (installed from the package registry)
  • Netcode Topology: Client-Server (dedicated server), scene management enabled
Additional Context

Possible fixes (either should work):

  • Override WriteFieldSynchronization in NetworkList<T> to write the list as of the last sent delta, e.g. a copy refreshed in ResetDirty, mirroring NetworkVariable<T>'s previous value.
  • Flush pending deltas in HandleConnectionApproval before the client becomes an observer, as ForceSendIfDirtyOnNetworkShow does for NetworkShow.

Workaround we're shipping, confirmed to fix it. It has to be compiled into Unity.Netcode.Runtime through an .asmref because these APIs are internal:

// INetworkHooks, registered with networkManager.MessageManager.Hook(...) in OnServerStarted.
// ConnectionApprovedMessage is sent after AddClient but before SynchronizeNetworkObjects makes
// the client an observer, so the flush only reaches clients that already have the old state.
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
{
    if (typeof(T) != typeof(ConnectionApprovedMessage)) return;
    networkManager.BehaviourUpdater.NetworkBehaviourUpdate();
}

This only works with scene management enabled. With it off, the snapshot travels inside ConnectionApprovedMessage itself, which is serialized before the hook runs.

Related: #3999 (same duplicate for changes made in OnNetworkSpawn), #2454 (NetworkShow), #2462, #1590.

主要語言
C#
星號
2.3k
分支
464
平均合併
2 天 18 小時
30 天內合併 PR
15

環境準備

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

Unity-Technologies/com.unity.netcode.gameobjects 的其他 Issue

查看 Unity-Technologies/com.unity.netcode.gameobjects 的全部 Issue

相似的 Issue

更多 C# Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。