Unity-Technologies / Unity-Technologies/com.unity.netcode.gameobjects

Dedicated server: mixed-authority NetworkObject is removed from NetworkTransformUpdate, so its owner-authoritative NetworkTransform never updates server-side

Đang mở
#4,159 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

stat:import type:bug
Ngôn ngữ chính
C#
Star
2.3k
Fork
461
Merge trung bình
3 ngày 16 giờ
Pull request đã merge (30 ngày)
20

Mô tả

Description

On a dedicated server (NetworkManager.StartServer(), i.e. IsServer && !IsConnectedClient), the server's copy of an owner-authoritative NetworkTransform never updates when the same NetworkObject also carries one or more server-authoritative NetworkTransforms on child objects. The owning client keeps sending state, the server receives and interpolates it, but the transform is never written — it stays at the position given at connection approval for the lifetime of the object. A host with the identical prefab is unaffected.

Root cause (traced in 2.13.2 source and confirmed by reading the registry at runtime, details below): NetworkManager.NetworkTransformUpdate — the collection that drives non-authority OnUpdate(), which is the only code path that applies an interpolated position to the transform — is keyed per NetworkObject with no reference count. Every NetworkTransform.InternalInitialization calls:

if (CanCommitToTransform) m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); // removes the whole NetworkObject
else                      m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true);  // adds it

So on a NetworkObject with mixed authority, every authority-side transform's initialization removes the entire object, evicting its non-authority siblings. Spawn initialization runs in component order (root first), so the non-authority root adds the object and each authority-side child then removes it.

A host survives because NetworkTransform.InternalOnNetworkPostSpawn ends with:

if (!CanCommitToTransform && m_CachedNetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing)
    NonAuthorityFinalizeSynchronization();

whose InternalInitialization re-run on the non-authority root re-adds the object after the children evicted it. IsConnectedClient is false on a dedicated server, so that never runs there; and the finalize's only other caller, NotifyNetworkObjectsSynchronized, fires once at server start, never for a late-joining client's object. So the first block of that same method deliberately raises IsSynchronizing ("special case for client-server where a server is spawning an owner authoritative NetworkObject") and nothing on a pure server ever completes it.

Reproduce Steps
  1. Player prefab: root has a NetworkTransform subclass with OnIsServerAuthoritative() => false (owner authority). Add one or more child GameObjects each with a plain NetworkTransform left at AuthorityMode = Server. (Ours: two owner-authoritative on root/child, eleven server-authoritative children used as replicated pose slots.)
  2. Start a dedicated server with StartServer() (not StartHost()), client-server topology.
  3. Connect one client; its player object is spawned server-side with SpawnAsPlayerObject(clientId).
  4. On the client, move the player well away from the spawn point.
  5. On the server, read client.PlayerObject.transform.position, and check NetworkManager.NetworkTransformUpdate.ContainsKey(playerObject.NetworkObjectId).
Actual Outcome
  • Server-side position stays at the connection-approval spawn position indefinitely (we measured (0.00, 1.00, 0.00) while the client stood 110 m away).
  • NetworkTransformUpdate does not contain the player NetworkObject (ContainsKey == false).
  • Client-side the owner-authoritative transform reports CanCommitToTransform = true and produces state each tick; server-side it reports CanCommitToTransform = false, IsSpawned = true, enabled = true — willing to receive, but OnUpdate() never runs.
  • Remove the server-authoritative children (or run the same prefab on a host): position tracks correctly.
Expected Outcome

The non-authority root should remain registered for OnUpdate() regardless of how many authority-side sibling transforms share the NetworkObject, on a dedicated server exactly as on a host.

Screenshots

N/A — runtime readouts above.

Environment
  • OS: Windows 11 Pro
  • Unity Version: 6000.3.22f1
  • Netcode Version: 2.13.2 (registry)
  • Netcode Commit: n/a (registry package)
  • Netcode Topology: Client-Server, dedicated server (StartServer) — not reproducible on a host
Additional Context

Two things we ruled out while tracing it, in case they save time: SynchronizeState.IsSynchronizing is not what blocks reception — it reads True on the server before and after the workaround and no stage of the receive path (TransformStateUpdate, OnNetworkStateChanged, ApplyUpdatedState, UpdateInterpolation) gates on it; and setting the serialized AuthorityMode to Owner on the root changes nothing, since CanCommitToTransform derives from IsServerAuthoritative().

Workaround we are shipping, in our NetworkTransform subclass — it re-runs the finalize on a server that is not also a client, which re-registers the object via InternalInitialization:

protected override void InternalOnNetworkPostSpawn()
{
    base.InternalOnNetworkPostSpawn();

    var networkManager = NetworkManager;
    if (networkManager == null || !networkManager.IsServer || networkManager.IsConnectedClient)
        return;
    if (CanCommitToTransform)
        return;

    // NetworkTransform.InternalOnNetworkSessionSynchronized is NonAuthorityFinalizeSynchronization()
    // followed by an empty NetworkBehaviour base, so this is the finalize and nothing else.
    base.InternalOnNetworkSessionSynchronized();
}

It works, but only for transforms of a type we control — a plain NetworkTransform set to owner authority on such an object is still evicted. A reference count on NetworkTransformUpdate (or registering per transform rather than per object) would fix it at the source; alternatively the post-spawn finalize could run for a non-connected-client server too, since its first block already targets exactly that case.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu từ NetworkManager.NetworkTransformUpdate và NetworkTransform.InternalInitialization, sau đó lần theo NetworkTransform.InternalOnNetworkPostSpawn và NonAuthorityFinalizeSynchronization. Tái hiện bằng StartServer() với các NetworkObjects có authority hỗn hợp, đồng thời xác minh rằng root không có authority vẫn được đăng ký và vị trí phía server của nó đi theo client sở hữu trên một dedicated server.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
csharp, unity
Lĩnh vực
networking
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
68/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.