Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Network Object with Network Variable not Deleting

Discussion in 'Netcode for GameObjects' started by runette_unity, Nov 21, 2023.

  1. runette_unity

    runette_unity

    Joined:
    Feb 20, 2020
    Posts:
    14
    I have a problem - or possibly two problems that occur together:

    I have some Network Objects that are programmatically created. Some have a particular Network Variable (details below). If the code tries to delete (using NetworkObject.Destroy()) these particular GO they do not get deleted (just orphaned since the code successfully deletes their parent) and I also get the same number of the following error messages as there are GO that failed to delete :

    [Netcode] [NT TICK DUPLICATE] Server already sent an update on tick 1251 and 
    is attempting to send again on the same network tick!


    I don't know if the two are the same error or a coincidence. It would be good to understand the error message but I can find no reference to it anywhere. I can kind of guess but - isn't Netcode supposed to cache between ticks?

    The Network variable spec is


    Code (CSharp):
    1.     public NetworkVariable<SerializableMesh> umesh = new();
    2.  
    3.     public override void OnNetworkSpawn()
    4.     {
    5.         umesh.OnValueChanged += SetMesh;
    6.     }
    7.  
    8.     public override void OnNetworkDespawn()
    9.     {
    10.         umesh.OnValueChanged -= SetMesh;
    11.     }
    The Serializeble is


    Code (CSharp):
    1.  
    2.     public class SerializableMesh : INetworkSerializable, IEquatable<SerializableMesh>
    3.     {
    4.         private Vector3[] vertices;
    5.  
    6.         private Color32[] colors;
    7.         private Vector2[] uvs;
    8.         private int[] tris;
    9.  
    10.         // INetworkSerializable
    11.         public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    12.         {
    13.             if (serializer.IsReader) {
    14.                 // De-Serialize the data being synchronized
    15.                 var reader = serializer.GetFastBufferReader();
    16.                 reader.ReadValueSafe(out int vertexCount);
    17.                 if (vertexCount == 0) return;
    18.                 reader.ReadValueSafe(out int triCount);
    19.                 vertices = new Vector3[vertexCount];
    20.                 reader.ReadValueSafe(out vertices);
    21.                 colors = new Color32[vertexCount];
    22.                 reader.ReadValueSafe(out colors);
    23.                 uvs = new Vector2[vertexCount];
    24.                 reader.ReadValueSafe(out uvs);
    25.                 tris = new int[triCount];
    26.                 reader.ReadValueSafe(out tris);
    27.             } else {
    28.                 var writer = serializer.GetFastBufferWriter();
    29.                 writer.WriteValueSafe(vertices.Length);
    30.                 writer.WriteValueSafe(tris.Length);
    31.                 writer.WriteValueSafe(vertices);
    32.                 writer.WriteValueSafe(colors);
    33.                 writer.WriteValueSafe(uvs);
    34.                 writer.WriteValueSafe(tris);
    35.             }
    36.         }
    37.  
    38.         public static implicit operator Mesh(SerializableMesh mesh) {
    39.             // create a new mesh and broadcast that
    40.             Mesh tmesh = new();
    41.             if (mesh.vertices.Length > 64000 || mesh.tris.Length > 64000)
    42.                 tmesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
    43.             tmesh.SetVertices(mesh.vertices);
    44.             tmesh.SetColors(mesh.colors);
    45.             tmesh.SetUVs(0,mesh.uvs);
    46.             tmesh.SetTriangles(mesh.tris, 0);
    47.             tmesh.RecalculateNormals();
    48.             tmesh.RecalculateTangents();
    49.             tmesh.RecalculateBounds();
    50.             return tmesh;
    51.         }
    52.  
    53.         public static implicit operator SerializableMesh(Mesh mesh){
    54.             SerializableMesh smesh = new()
    55.             {
    56.                 vertices = mesh.vertices,
    57.                 colors = mesh.colors32,
    58.                 uvs = mesh.uv,
    59.                 tris = mesh.triangles
    60.             };
    61.             return smesh;
    62.         }
    63.    
    64.         public bool Equals(SerializableMesh other)
    65.         {
    66.             return vertices.Length == other.vertices.Length;
    67.         }
    68.     }
    69.  
     
  2. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    There are two separate but possibly related issues here.
    Regarding the orphaned GOs, I would need to see the rest of the code that actually applies the SerializableMesh (i.e. SetMesh and any dependent methods there) and the code that destroys the GO in order to be able to tell you why you are getting orphaned GOs.

    Regarding the "[Netcode] [NT TICK DUPLICATE]" message, this is actually a NetworkTransform (NT) message that really "shouldn't happen" but I stuck that there in the event it did...which most of the time I have seen this issue is when there the NT is running in owner authoritative (i.e. not server... and I just realized the message needs to be updated) mode and is attempting to send an update for the same network tick (which for interpolation that can cause minor issues). Just curious, do you get any exceptions during this time when you destroy the GO?

    But first, let's focus on the first issue and then circle back to sending on a duplicate tick after that (if you can provide more script it will help me better understand what the SerializableMesh is being applied to...like do you create a new GO , add a MeshFilter component, apply the SerializableMesh to that, and then parent the GO under something or do you apply it to an existing GO MeshFilter component...etc).
     
  3. runette_unity

    runette_unity

    Joined:
    Feb 20, 2020
    Posts:
    14
    The GO is instantiated from a Prefab programmatically. That Pefab includes MeshFilter, MeshRenderer and MeshCollider components. The SetMesh() Method is copied below.

    This has all been working very well as a MonoBeahviour for a couple of years. I just say that for information ...

    The SetMesh method is :


    Code (CSharp):
    1.     private void SetMesh(SerializableMesh previousValue, SerializableMesh newValue)
    2.     {
    3.         MeshFilter mf = GetComponent<MeshFilter>();
    4.         MeshCollider[] mc = GetComponents<MeshCollider>();
    5.         Mesh mesh = newValue;
    6.         mf.mesh = mesh;
    7.         Mesh imesh = new()
    8.         {
    9.             indexFormat = mesh.indexFormat,
    10.  
    11.             vertices = mesh.vertices,
    12.             triangles = mesh.triangles.Reverse().ToArray(),
    13.             uv = mesh.uv
    14.         };
    15.  
    16.         imesh.RecalculateBounds();
    17.         imesh.RecalculateNormals();
    18.  
    19.         try
    20.         {
    21.             mc[0].sharedMesh = mesh;
    22.             mc[1].sharedMesh = imesh;
    23.         }
    24.         catch (Exception e)
    25.         {
    26.             Debug.Log(e.ToString());
    27.         }
    28.     }
     
  4. runette_unity

    runette_unity

    Joined:
    Feb 20, 2020
    Posts:
    14
    .. and sorrry - the code for deletion ... There are multiple small meshes (and other geometric shapes that don't need a SerializableMesh since for them the actual mesh is included in the Prefab) under an Umbrella GO. This is the delete code from the umbrella ... The Non-SerializableMesh GO don't have this problem.


    Code (CSharp):
    1.             for (int i = 0; i <transform.childCount; i++ )
    2.             {
    3.                 NetworkObject.Destroy(transform.GetChild(i).gameObject);
    4.             }
     
  5. runette_unity

    runette_unity

    Joined:
    Feb 20, 2020
    Posts:
    14
    and the GO are spawned as follows
    Code (CSharp):
    1.         public bool Spawn(Transform parent)
    2.         {
    3.             NetworkObject no = gameObject.GetComponent<NetworkObject>();
    4.             try
    5.             {
    6.                 no.Spawn();
    7.             }
    8.             catch (Exception e)
    9.             {
    10.                 _ = e;
    11.                 return false;
    12.             }
    13.             return no.TrySetParent(parent);
    14.         }
     
  6. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    Ok, have patience with me... I want to make sure I understand the flow.

    You have a parent GameObject (let's call that the RootParent) with a NetworkObject component. At some point in time you spawn (n) number of of children, each with a NetworkObject component.
    • Shortly (after or before?) this you apply serialized mesh data to a NetworkVariable of each child?
      • Or some of the children?
    • Then at some point in time after everything is spawned and parented you destroy each child parented under the RootParent?

    Admittedly, I am still semi-fuzzy on the entire order of operations... but looking at the code provided I would recommend doing this:
    Code (CSharp):
    1.             for (int i = 0; i < transform.childCount; i++)
    2.             {
    3.                 transform.GetChild(i).GetComponent<NetworkObject>().Despawn();
    4.             }
    Despawning the children NetworkObjects should destroy the GameObject each child is associated with.

    If that doesn't work, then perhaps you could fill in some of the gaps in my current understanding outlined above?
     
  7. runette_unity

    runette_unity

    Joined:
    Feb 20, 2020
    Posts:
    14
    Ok - that makes sense. For some reason I was thinking I had to DeSpawn and then Destroy .. I guess that was creating some sort of race ...

    I ended up doing something like this added to the base script for all objects - since there are some GO just used for grouping and so there are more then one layer in the hierarchy. This sort of burns the tree down from the leaves first.


    Code (CSharp):
    1.         public void Destroy()
    2.         {
    3.             for (int i = transform.childCount -1; i>=0;  i--)
    4.             {
    5.                 if(transform.GetChild(i).TryGetComponent(out VirgisFeature com )){
    6.                     com.Destroy();
    7.                     NetworkObject no = com.GetComponent<NetworkObject>();
    8.                     no.Despawn();
    9.                 }
    10.             }
    11.         }
    This works without any error messages or orphan NOs.

    Thanks for your help and pointers
     
    NoelStephens_Unity likes this.
  8. gnovos

    gnovos

    Joined:
    Apr 29, 2020
    Posts:
    26
    I am getting zillions of "[Netcode] [NT TICK DUPLICATE] Server already sent an update on tick 0 and is attempting to send again on the same network tick!" messages all of a sudden and I'm not sure I understood Noel's explanation. Is there any way to add more to the stack trace so I can see what objects are actually causing this? I'm at a loss where to go next.
     
    brianduper likes this.
  9. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    @gnovos
    That issue should be handled in the up-coming v1.8.0 update and the log message shouldn't be spamming like that as well (that is my fault and apologies for that). The underlying issue has to do with a deeper issue in the network tick code on the client side where it can receive and process update just prior to the network tick being rolled over to the next one.

    If you want, you can replace your com.unity.netcode.gameobjects entry in the manifest.json file with this:
    Code (CSharp):
    1.   "com.unity.netcode.gameobjects": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop",
    Which should make that message go away until v1.8.0 is available as a package.
     
    VirThi and gnovos like this.
  10. gnovos

    gnovos

    Joined:
    Apr 29, 2020
    Posts:
    26
    Thanks, that fixed it for now! I'll keep an eye out for when the official 1.8.0 package comes out.
     
    VirThi likes this.
  11. VirThi

    VirThi

    Joined:
    Jan 19, 2021
    Posts:
    1
    Thanks for the answer, worked like a charm here :D
     
    NoelStephens_Unity likes this.
  12. Zeboga

    Zeboga

    Joined:
    Apr 1, 2023
    Posts:
    1

    saved me some time thanks
     
    NoelStephens_Unity likes this.