There is no problem when the client disconnects, but there is a problem when the host disconnects

I’m saving the player’s item information when they lose connection. When the client shuts down, and the host is on, the DonDestroyWithOwner works and the item won’t be erased.

However, an error occurred in the RemoveOwnerShip portion when the host was shut down.

Code :

public void SaveDisconnectPlayerData(ulong clientId)
{
    if (!isHosting) return;

    print("SaveDisconnectPlayerData");
    ulong playerObjectId = PlayerManager.Instance.PlayerObjectIdDictionary[clientId];
    ulong steamId = PlayerManager.Instance.SteamIdMapper[clientId];

    Player player = PlayerManager.Instance.PlayerObjectDictionary[clientId];

    if (player != null)
    {
        // print("player is not null");
        foreach (var container in player.PlayerInventory.ItemContainer.Containers)
        {
            container.BeforeSerialize();

            DisconnectPlayerItem(container);
        }

        foreach (var slot in player.PlayerInventory.ItemContainer.Equipment.equipmentSlotList)
        {
            slot.BeforeSerialize();

            if (slot.currentItemNetworkId != 0 && NetworkManager.Singleton != null
            && NetworkManager.Singleton.SpawnManager != null)
            {
                if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(slot.currentItemNetworkId, out NetworkObject itemObject))
                {
                    Item curItem = itemObject.GetComponent<Item>();
                    itemObject.DontDestroyWithOwner = true;

                    if (curItem is ContainerItem containerItem)
                    {
                        foreach (var con in containerItem.ItemContainer.Containers)
                        {
                            con.BeforeSerialize();

                            DisconnectPlayerItem(con);
                        }
                    }

                    if (!NetworkManager.Singleton.ShutdownInProgress && itemObject.IsSpawned)
                    {
                        itemObject.RemoveOwnership();
                        PlayerManager.Instance.DisableItems.Add(curItem.ID, curItem);
                    }
                }
            }
        }

        ES3.Save($"{steamId}InventoryItems", player.PlayerInventory.ItemContainer.Containers, saveFilePath);
        ES3.Save($"{steamId}EquipItems", player.PlayerInventory.ItemContainer.Equipment.equipmentSlotList, saveFilePath);

        // ES3.Save($"{steamId}Position", player.transform.position, saveFilePath);
        // ES3.Save($"{steamId}Rotation", player.transform.rotation, saveFilePath);

        ES3.Save($"{steamId}Data", new PlayerSaveData(player), saveFilePath);
        // player.PlayerInventory.ItemContainer
    }

}

Error:

NullReferenceException: Object reference not set to an instance of an object
Unity.Netcode.NetworkVariableBase.MarkNetworkBehaviourDirty () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/NetworkVariable/NetworkVariableBase.cs:193)
Unity.Netcode.NetworkVariableBase.SetDirty (System.Boolean isDirty) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/NetworkVariable/NetworkVariableBase.cs:143)
Unity.Netcode.NetworkBehaviour.MarkVariablesDirty (System.Boolean dirty) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkBehaviour.cs:945)
Unity.Netcode.NetworkObject.MarkVariablesDirty (System.Boolean dirty) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkObject.cs:1437)
Unity.Netcode.NetworkSpawnManager.ChangeOwnership (Unity.Netcode.NetworkObject networkObject, System.UInt64 clientId) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Spawning/NetworkSpawnManager.cs:267)
Unity.Netcode.NetworkSpawnManager.RemoveOwnership (Unity.Netcode.NetworkObject networkObject) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Spawning/NetworkSpawnManager.cs:245)
Unity.Netcode.NetworkObject.RemoveOwnership () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkObject.cs:900)
NetworkGameManager.SaveDisconnectPlayerData (System.UInt64 clientId) (at Assets/00_SHJ/Scripts/Network/NetworkGameManager.cs:653)
PlayerManager.DisconnectPlayer (System.UInt64 clientId) (at Assets/00_PJW/Scripts/Managers/PlayerManager.cs:718)
PlayerManager.OnDisconnectedClientRpc (System.UInt64 clientId) (at Assets/00_PJW/Scripts/Managers/PlayerManager.cs:694)
PlayerManager.__rpc_handler_3136636770 (Unity.Netcode.NetworkBehaviour target, Unity.Netcode.FastBufferReader reader, Unity.Netcode.__RpcParams rpcParams) (at <879e854e1eed400697ebade85d897197>:0)
Unity.Netcode.RpcMessageHelpers.Handle (Unity.Netcode.NetworkContext& context, Unity.Netcode.RpcMetadata& metadata, Unity.Netcode.FastBufferReader& payload, Unity.Netcode.__RpcParams& rpcParams) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Messaging/Messages/RpcMessages.cs:70)
Rethrow as Exception: Unhandled RPC exception!
UnityEngine.Debug:LogException(Exception)
Unity.Netcode.RpcMessageHelpers:Handle(NetworkContext&, RpcMetadata&, FastBufferReader&, __RpcParams&) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Messaging/Messages/RpcMessages.cs:74)
Unity.Netcode.ClientRpcMessage:Handle(NetworkContext&) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Messaging/Messages/RpcMessages.cs:159)
Unity.Netcode.NetworkBehaviour:__endSendClientRpc(FastBufferWriter&, UInt32, ClientRpcParams, RpcDelivery) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkBehaviour.cs:253)
PlayerManager:OnDisconnectedClientRpc(UInt64) (at Assets/00_PJW/Scripts/Managers/PlayerManager.cs:692)
PlayerManager:OnClientDisconnect(UInt64) (at Assets/00_PJW/Scripts/Managers/PlayerManager.cs:686)
NetworkGameManager:OnClientDisconnected(UInt64) (at Assets/00_SHJ/Scripts/Network/NetworkGameManager.cs:248)
Unity.Netcode.NetworkConnectionManager:InvokeOnClientDisconnectCallback(UInt64) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:138)
Unity.Netcode.NetworkConnectionManager:OnClientDisconnectFromServer(UInt64) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:1013)
Unity.Netcode.NetworkConnectionManager:DisconnectRemoteClient(UInt64) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:1040)
Unity.Netcode.NetworkConnectionManager:Shutdown() (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:1140)
Unity.Netcode.NetworkManager:ShutdownInternal() (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkManager.cs:1199)
Unity.Netcode.NetworkManager:ModeChanged(PlayModeStateChange) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkManager.cs:663)


I understand that Soft ShutDown will cause other clients to hang up first, but I don’t know why the result is different from when I hang up on the client side.


After activating DonDestoryWithOwner on all prefabs, the storage itself works well, but the error occurs.

Error :

NullReferenceException: Object reference not set to an instance of an object
Unity.Netcode.NetworkVariableBase.MarkNetworkBehaviourDirty () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/NetworkVariable/NetworkVariableBase.cs:193)
Unity.Netcode.NetworkVariableBase.SetDirty (System.Boolean isDirty) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/NetworkVariable/NetworkVariableBase.cs:143)
Unity.Netcode.NetworkBehaviour.MarkVariablesDirty (System.Boolean dirty) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkBehaviour.cs:945)
Unity.Netcode.NetworkObject.MarkVariablesDirty (System.Boolean dirty) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkObject.cs:1437)
Unity.Netcode.NetworkSpawnManager.ChangeOwnership (Unity.Netcode.NetworkObject networkObject, System.UInt64 clientId) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Spawning/NetworkSpawnManager.cs:267)
Unity.Netcode.NetworkSpawnManager.RemoveOwnership (Unity.Netcode.NetworkObject networkObject) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Spawning/NetworkSpawnManager.cs:245)
Unity.Netcode.NetworkObject.RemoveOwnership () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkObject.cs:900)
Unity.Netcode.NetworkConnectionManager.OnClientDisconnectFromServer (System.UInt64 clientId) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:984)
Unity.Netcode.NetworkConnectionManager.DisconnectRemoteClient (System.UInt64 clientId) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:1040)
Unity.Netcode.NetworkConnectionManager.Shutdown () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Connection/NetworkConnectionManager.cs:1140)
Unity.Netcode.NetworkManager.ShutdownInternal () (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkManager.cs:1199)
Unity.Netcode.NetworkManager.ModeChanged (UnityEditor.PlayModeStateChange change) (at ./Library/PackageCache/com.unity.netcode.gameobjects@1.9.1/Runtime/Core/NetworkManager.cs:663)
UnityEditor.EditorApplication.Internal_PlayModeStateChanged (UnityEditor.PlayModeStateChange state) (at <51e2d50a25be4e0e8f1b2720c516435c>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)

It’s unclear when you call the “save” method. But if this occurs in OnClientDisconnect this is too late. You have to assume at this point network is shutting down and destroying network objects.

You generally have two ways of solving this: if a client or host disconnects purposefully ie through a button then you would call your save method and only after that is done, you shutdown the network. However an involuntary disconnect would not save data.

The alternative and far better approach is to keep everything that needs to be saved separate from any MonoBehaviour object, at least those that may get destroyed when networking ends.

Say you want to save the player’s inventory. Then make sure the player inventory is a regular class which contains all the items in the inventory, such as the item name, its inventory slot, the asset it references, and so forth. Then if the actual networked player inventory gets destroyed, you still have that data available for saving.

Separation of concerns. Model view controller. The view part here being the visible inventory, the controller is the Behaviour containing the inventory, and the model is an instance of the inventory class stored elsewhere, ie in an InventoryItems object that is non-networked and marked DontDestroyOnLoad.

I understand what you said, but once I use Don’t DestroyWithOwner, the storage itself seems to work well. However, the above error does not occur in the code I call. I don’t know why that error appears.