NetworkList changes made since the last tick are applied twice on a newly synchronized client
Maintainer thường phản hồi trong vòng 2 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 55/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Lĩnh vực
- networking
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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
- 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.");
};
}
}
- Start a server (dedicated or host).
- Connect a client.
- 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.Countends 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.0anddevelop-3.x.xas 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
WriteFieldSynchronizationinNetworkList<T>to write the list as of the last sent delta, e.g. a copy refreshed inResetDirty, mirroringNetworkVariable<T>'s previous value. - Flush pending deltas in
HandleConnectionApprovalbefore the client becomes an observer, asForceSendIfDirtyOnNetworkShowdoes 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.
- Ngôn ngữ chính
- C#
- Star
- 2.3k
- Fork
- 464
- Merge trung bình
- 2 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 15
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Unity-Technologies/com.unity.netcode.gameobjects
-
Dedicated server: mixed-authority NetworkObject is removed from NetworkTransformUpdate, so its owner-authoritative NetworkTransform never updates server-sideCó thể đã có người làm @NoelStephensUnity đã nhận 11 ngày trước. Đang mởstat:imported stat:reply-needed type:bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 68/100
Unity-Technologies/com.unity.netcode.gameobjects#4159 · 5 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
NetworkAnimator: Conditional triggered transitions to Exit node log false errorsCó thể đã có người làm @NoelStephensUnity đã nhận 196 ngày trước. Đang mởstat:awaiting-response stat:awaiting-triage stat:Investigating type:bug
Unity-Technologies/com.unity.netcode.gameobjects#3912 · 7 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Tracking type:feature-2.x
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Unity-Technologies/com.unity.netcode.gameobjects#3870 · 5 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Please error, or at least warn, when a managed type is included in INetworkSerializeByMemcpyĐang mởTracking type:feature
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Unity-Technologies/com.unity.netcode.gameobjects#3830 · 7 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
stat:awaiting-response stat:imported type:bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 38/100
Unity-Technologies/com.unity.netcode.gameobjects#3802 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
Tất cả issue của Unity-Technologies/com.unity.netcode.gameobjects
Issue tương tự
-
[Doc Gap] Document new --enable-public-network-access breaking change for azurebackup vault createĐang mởcopilot documentation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
Maintainer thường phản hồi trong vòng 1 ngày
-
area-dashboard
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
0 - Backlog Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
BrighterCommand/Brighter#4539 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
area-networking
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
dotnet/aspnetcore#69671 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Ignored test: FileLocalDataSourceTests.retries_loading_fileCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mởtest
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
NethermindEth/nethermind#14274 ·
Maintainer thường phản hồi trong vòng 1 ngày