Search Unity

networkview issues.

Discussion in 'Multiplayer' started by perlohmann, May 15, 2009.

  1. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    Im having issues with regards to the voice chat Im implementing.

    The voice data is transfered from the clients to the server correctly but voice data is not distributed to the other clients (worth to note, Im using a client/server architecture) except for the server voice.

    The really weird thing is that Im using the same procedures as the network view in my player which gets distributed perfectly.

    The only difference data wise is that the data in the player is fixed size (more or less a transform) while the data is of variable length in my voice component.
    formed like this:

    Im running out of ideas on this issue so I was thinking if anybody has had similar problems that could help jump start my brain again....


    little more specifics:
    An example of a setup could be with three computers. One being a server while the other two are clients.

    In this case the server player is able to hear everything that either client say. and every client is able to hear what the server player say. But the clients cannot hear what each other are saying.

    On the code side. every client generate a local view id which is distributed through the server to other clients and everything is being set up correctly so that the correct client owns the view id of the local instance.

    Am I wrong in assuming that the server will relay incomming data to the other clients when it itself does not own the network view id? This is working with my players so I cant see why not.

    //Perlohmann

    There is no such thing as stupid feedback on this one... as long as it is on topic =)
     
  2. Der Dude

    Der Dude

    Joined:
    Aug 7, 2006
    Posts:
    213
    Are you using OnSerializeNetworkView by any chance?

    I've had this problem with variable "update" sizes as well. I really forgot about it because using RPCs was fine (until now at least).
    I had this problem with shots not being transferred to everyone.

    I really forgot about it, and didn't manage to write a proper bug report. But its on my todo list now, if you don't do it first ;)

    To your situation: Try to handle the data transmission either via static sized updates or RPCs and see if it changes anything.

    Good Luck!
     
  3. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    Yes Im using OnSerialize.....

    Ill try and use static sizes cuzz I cant really use RPC's for voice data..... (reliable transfer would be a problem for voice data)
    It just aint pretty with respect to static sizes as if there are more data than there is place for in the array you'll lose a voice packet or more, and increasing the size means alot of data is transmitted with nothing in it. =(

    //perlohmann
     
  4. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    I would consider adding a distinct network channel for the voice transfer. If you use a 3rd party library, they potentially already offer such capabilities.

    Also your two points somehow work against each other.
    Either you care about data lose (lose a voice packet or more) or you don't care (can't use RPCs as it is reliable).
    Especially if you use unreliable UDP, you can run into the lost packet problem even if the array could hold the data, as it is just never received / received correctly.
    If you use reliable UDP, then I'm not sure why you would want to avoid RPC
     
  5. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    no not really. I am using unreliable UDP and i dont really care if packets are lost.... I just care if too many is lost.

    Since setting the size to something fixed would potential increase the number of lost packets (not counting for packets lost on the network) there is a higher chance that the voice will be unrecognizable.
    Increasing the size would introduce a large amount of transmitted data with no content.

    Im using a self developed dll plugin using speex, the speex specific jitterbuffer and portaudio.
    Would like to integrate it into the unity network so users would not need to open ports or me to consider nat punchthrough although it might have to be the best solution not to use the unity networking.


    a little more info:
    my voice packets are 20 ms of length and is encoded using Speex 8k sampling rate when encoded each 20 ms voice packet is 38 bytes long plus an additional 4 bytes timestamp and 2 bytes player ID. which give 44 bytes or 2,2 KB/sec.

    so each increase in the fixed size is doubling the data sent and it just get worse for the server for each additional player that need to hear the voice.

    but anyways need to test if it indeed is the dynamic size that is the problem.

    //perlohmann
     
  6. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    Apparently if you dont send the same number of parameters on each serialize they dont get relayed to clients, although the server do get the data. :roll:


    Is this an issue Unity intends to fix?
     
  7. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    Think Ive fixed it to some degree dunno if it is working as intended by it apears that local variables are not relayed to anybody else but the server.

    In order to test this you should run 3 x instances of your program. (start build app 1, then use the editor play, and last start a 2nd build app)

    Create a gameObject and put a NetworkView and TestScript in it. Have the networkview observe the TestScript and have the viewID be generated by each local app (in my case each game object is a player and is dynamicly instantiated from a prefab).

    This means that each app has 3x instances of the gameobject with the scripts. One is the app's local object while the other two represents each of the other app's and is "owned" by them respectivly.

    (code might contain errors as Ive written this solely in this small message box :eek:) )

    This works:

    Code (csharp):
    1.  
    2. public class TestScript : MonoBehaviour
    3. {
    4.    public int count;
    5.    public int pID;
    6.    public short[] testArray;
    7.  
    8.    public void Start()
    9.    {
    10.       count = 2;
    11.       pID = Network.player.GetHashCode();
    12.    }
    13.  
    14.    void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    15.    {
    16.       if (stream.isWriting)
    17.       {
    18.          stream.Serialize(ref count);
    19.          stream.Serialize(ref pID);
    20.          testArray = new short[count];
    21.          for (int i = 0; i < count; i++)
    22.          {
    23.             testArray[i] = (short)i;
    24.             stream.Serialize(ref testArray[i]);
    25.          }  
    26.       }
    27.       else
    28.       {
    29.          stream.Serialize(ref count);
    30.          stream.Serialize(ref pID);
    31.          Debug.Log("PID: " + pID + " - Count: " + count);
    32.          testArray = new short[count];
    33.          for (int i = 0; i < count; i++)
    34.          {
    35.             stream.Serialize(ref testArray[i]);
    36.             Debug.Log("PID: " + pID + " - value: " + testArray[i]);
    37.          }  
    38.       }
    39.    }
    40. }
    41.  
    This does not work:
    Code (csharp):
    1.  
    2. public class TestScript : MonoBehaviour
    3. {
    4.    void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    5.    {
    6.       int count = 2;
    7.       int pID = Network.player.GetHashCode();
    8.       if (stream.isWriting)
    9.       {
    10.          stream.Serialize(ref count);
    11.          stream.Serialize(ref pID);
    12.          short[] testArray = new short[count];
    13.          for (int i = 0; i < count; i++)
    14.          {
    15.             testArray[i] = (short)i;
    16.             stream.Serialize(ref testArray[i]);
    17.          }  
    18.       }
    19.       else
    20.       {
    21.          stream.Serialize(ref count);
    22.          stream.Serialize(ref pID);
    23.          Debug.Log("PID: " + pID + " - Count: " + count);
    24.          short[] testArray = new short[count];
    25.          for (int i = 0; i < count; i++)
    26.          {
    27.             stream.Serialize(ref testArray[i]);
    28.             Debug.Log("PID: " + pID + " - value: " + testArray[i]);
    29.          }  
    30.       }
    31.    }
    32. }
    33.  
    Difference is that the one that isnt working uses local vars for the values to be transmitted.

    And before ppl state that it might not make sense to use local vars for the transmitting my answer is maybe not, but can you foresee every programming issue? But anyways you would expect it to work in both cases and if your not aware of this bug you can use alot of time hunting it down.

    //Perlohmann
     
  8. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    This works:

    Code (csharp):
    1.  
    2. public class TestScript : MonoBehaviour
    3. {
    4.    public int count;
    5.    public int pID;
    6.    public short[] testArray;
    7.  
    8.    public void Start()
    9.    {
    10.       count = 2;
    11.       pID = Network.player.GetHashCode();
    12.    }
    13.  
    14.     void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    15.     {
    16.         if (stream.isWriting)
    17.         {
    18.             stream.Serialize(ref count);
    19.             stream.Serialize(ref pID);
    20.             if (count > 0)
    21.             {
    22.                 testArray = new short[count];
    23.                 for (int i = 0; i < count; i++)
    24.                 {
    25.                     testArray[i] = (short)i;
    26.                     stream.Serialize(ref testArray[i]);
    27.                 }
    28.             }
    29.         }
    30.         else
    31.         {
    32.             stream.Serialize(ref count);
    33.             stream.Serialize(ref pID);
    34.             Debug.Log("PID: " + pID + " - Count: " + count);
    35.             if (count <= 300  count > 0)
    36.             {
    37.                 testArray = new short[count];
    38.                 for (int i = 0; i < count; i++)
    39.                 {
    40.                     stream.Serialize(ref testArray[i]);
    41.                     Debug.Log("PID: " + pID + " - value: " + testArray[i]);
    42.                 }
    43.             }
    44.         }
    45.     }
    46. }
    47.  
    This makes Unity hang (sooo it dosnt work either):

    Code (csharp):
    1.  
    2. public class TestScript : MonoBehaviour
    3. {
    4.     public int count;
    5.     public int pID;
    6.     public short[] testArray;
    7.  
    8.     public void Start()
    9.     {
    10.        count = 2;
    11.        pID = Network.player.GetHashCode();
    12.     }
    13.  
    14.     void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    15.     {
    16.         if (stream.isWriting)
    17.         {
    18.             stream.Serialize(ref count);
    19.             stream.Serialize(ref pID);
    20.             if (count > 0)
    21.             {
    22.                 testArray = new short[count];
    23.                 for (int i = 0; i < count; i++)
    24.                 {
    25.                     testArray[i] = (short)i;
    26.                     stream.Serialize(ref testArray[i]);
    27.                 }
    28.             }
    29.         }
    30.         else
    31.         {
    32.             stream.Serialize(ref count);
    33.             stream.Serialize(ref pID);
    34.             Debug.Log("PID: " + pID + " - Count: " + count);
    35.             if (count <= 300  count > 0)
    36.             {
    37.                 testArray = new short[count];
    38.                 for (int i = 0; i < count; i++)
    39.                 {
    40.                     stream.Serialize(ref testArray[i]);
    41.                     Debug.Log("PID: " + pID + " - value: " + testArray[i]);
    42.                 }
    43.             }
    44.         }
    45.         count = (count % 300);
    46.         count++;        
    47.     }
    48. }
    49.  
    Difference is that the first block always has the same array size while the latter has an array size going from 1-300.

    =(
     
  9. Der Dude

    Der Dude

    Joined:
    Aug 7, 2006
    Posts:
    213
    Your observations yield the same results as mine.

    When writing a test project for the bug report, I had an idea for a fix;

    Variable sized updates are received by the server. The server receives all variable sized updates. So a workaround is to let the server relay those messages to the clients.

    Here is the test project. Build the project, start multiple instances (one of which must be the server). Then click "Send packet" to send a packet. A TextArea will log all local activity.

    Here it the code of OnSerializeNetworkView function:

    A thing to watch out for is that each client will receive the messages he sends, as the server relays a packet from a client to ALL clients. You have to filter those out.

    Code (csharp):
    1.  
    2. public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) {
    3.    
    4.         if(stream.isReading) {
    5.        
    6.             int count = 0;
    7.             stream.Serialize( ref count );
    8.            
    9.             for(int i = 0; i < count; i ++) {
    10.            
    11.                 int num = 0;
    12.                 stream.Serialize(ref num);
    13.                 receiveCount[num] = ((int)receiveCount[num]) + 1;
    14.                 dispStr = "Received: " + num + "\n" + dispStr;
    15.                
    16.                 // If we are the server, keep track of all received packets to relay them later.
    17.                 if(Network.isServer)
    18.                     resendPackets.Add(num);
    19.             }
    20.            
    21.             // If count != 0, we are a client that receives relayed packets.
    22.             stream.Serialize(ref count);
    23.            
    24.             for(int i = 0; i < count; i ++) {
    25.            
    26.                 int num = 0;
    27.                 stream.Serialize(ref num);
    28.                
    29.                 dispStr = "Received Relay: " + num + "\n" + dispStr;
    30.                
    31.                 // Dont do ++ if this message came from the local player
    32.                 if( num != localPlayerNum)
    33.                     receiveCount[num] = ((int)receiveCount[num]) + 1;
    34.             }
    35.            
    36.         } else if(stream.isWriting) {
    37.        
    38.             // Send regular packets from this local player
    39.             stream.Serialize( ref sendCount );
    40.            
    41.             for(int i = 0; i < sendCount; i ++) {
    42.            
    43.                 int num = localPlayerNum;
    44.                 stream.Serialize(ref num);
    45.                 receiveCount[num] = ((int)receiveCount[num]) + 1;
    46.                 dispStr = "Sent: " + num + "\n" + dispStr;
    47.                
    48.             }
    49.            
    50.             sendCount = 0;
    51.            
    52.             // If sendRelayCount != 0, we are the server relaying packets to the other clients
    53.             int sendRelayCount = resendPackets.Count;
    54.            
    55.             stream.Serialize(ref sendRelayCount);
    56.            
    57.             for(int i = 0; i < sendRelayCount; i ++) {
    58.            
    59.                 int num = (int)resendPackets[i];
    60.                 stream.Serialize(ref num);
    61.                 dispStr = "Relayed: " + num + "\n" + dispStr;
    62.             }
    63.            
    64.             resendPackets.Clear();
    65.            
    66.         }
    67.        
    68.     }
    69.  
    Hope this helps you perlohmann!
     
  10. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    report it with a test project so its known
     
  11. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    you could prob filter those out by using networkView.SetScope(...). I think you still receive messages from clients that has been put out of scope but Im not 100% sure (havnt verified it).

    But yea that might just work. Didnt know you could send eventhough you were'nt the "owner" of the viewID.
    Gonna try it out =) thx a bunch.

    //Perlohmann
     
  12. Der Dude

    Der Dude

    Joined:
    Aug 7, 2006
    Posts:
    213
    Well you can't.

    What I do is, I create an object with the shown script attatched to it for each connected player.
     
  13. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    Yes and no. Apparently the server will call OnSerializeNetworkView with a writing stream even though it does not own the viewID, (thought this was done behind the scene) with this in mind it makes perfect sense why dynamic sizes does not work, since the exact same code is run on the "server" instance as on the client.

    It should be posible to use dynamic sized packets in a similar way as you did with the "relay buffer".

    with regards to the client receiving its own packets... I havnt noticed any packets being relayed to the original client so I think that the network is at least that cleaver ;o)

    Ive added some debug info + a start function to your code to verify my points.

    Code (csharp):
    1.  
    2. public void Start()
    3. {
    4.    if (this.networkView != null)
    5.    {
    6.       if (this.networkView.isMine)
    7.       {
    8.          Debug.Log("networkView is mine! - ViewID: " + this.networkView.viewID.ToString());
    9.       }
    10.       else
    11.       {
    12.          Debug.Log("networkView is not mine! - ViewID: " + this.networkView.viewID.ToString());
    13.       }
    14.    }
    15. }
    16.    
    17.  
    18. public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) {
    19.    
    20.    if(stream.isReading) {
    21.        
    22.       int count = 0;
    23.       stream.Serialize( ref count );
    24.       if (Network.isServer  !this.networkView.isMine  count > 0)
    25.       {
    26.          Debug.Log("Stream.isReading on viewID: " + this.networkView.viewID.ToString());
    27.       }
    28.       for(int i = 0; i < count; i ++) {
    29.    
    30.          int num = 0;
    31.          stream.Serialize(ref num);
    32.          receiveCount[num] = ((int)receiveCount[num]) + 1;
    33.          dispStr = "Received: " + num + "\n" + dispStr;
    34.          Debug.Log("Receiving on view: " + this.networkView.viewID.ToString());
    35.          // If we are the server, keep track of all received packets to relay them later.
    36.          if(Network.isServer)
    37.             resendPackets.Add(num);
    38.       }
    39.            
    40.       // If count != 0, we are a client that receives relayed packets.
    41.       stream.Serialize(ref count);
    42.            
    43.       for(int i = 0; i < count; i ++) {
    44.    
    45.          int num = 0;
    46.          stream.Serialize(ref num);
    47.                
    48.          dispStr = "Received Relay: " + num + "\n" + dispStr;
    49.                
    50.          // Dont do ++ if this message came from the local player
    51.          if( num != localPlayerNum)
    52.          {
    53.             receiveCount[num] = ((int)receiveCount[num]) + 1;
    54.          }
    55.          else
    56.          {
    57.             Debug.Log("Received own relayed packet");
    58.          }
    59.       }
    60.    } else if(stream.isWriting) {
    61.        
    62.       // Send regular packets from this local player
    63.       stream.Serialize( ref sendCount );
    64.  
    65.       if (Network.isServer  !this.networkView.isMine  (sendCount > 0 || resendPackets.Count > 0))
    66.       {
    67.          Debug.Log("Stream.isWriting on viewID: " + this.networkView.viewID.ToString());
    68.       }
    69.        
    70.       for(int i = 0; i < sendCount; i ++) {
    71.        
    72.          int num = localPlayerNum;
    73.          stream.Serialize(ref num);
    74.          receiveCount[num] = ((int)receiveCount[num]) + 1;
    75.          dispStr = "Sent: " + num + "\n" + dispStr;
    76.                
    77.       }
    78.            
    79.       sendCount = 0;
    80.            
    81.       // If sendRelayCount != 0, we are the server relaying packets to the other clients
    82.       int sendRelayCount = resendPackets.Count;
    83.       if (sendRelayCount != 0)
    84.       {
    85.     Debug.Log("Server is relaying from viewID: " + this.networkView.viewID.ToString());
    86.       }
    87.       stream.Serialize(ref sendRelayCount);
    88.            
    89.       for(int i = 0; i < sendRelayCount; i ++) {
    90.            
    91.          int num = (int)resendPackets[i];
    92.          stream.Serialize(ref num);
    93.          dispStr = "Relayed: " + num + "\n" + dispStr;
    94.       }
    95.            
    96.       resendPackets.Clear();
    97.            
    98.    }
    99.        
    100. }
    101.  
    EDIT:

    So the only difference between a NetworkView on the server and a NetworkView on the client is that the server will call OnSerializeNetworkView(...) twice.
    Once with a reading stream and once with a writing stream.

    The client NetworkView will only call OnSerializeNetworkView(...) once with either a reading or writing stream depending on who owns the NetworkView.

    EDIT 2:

    It is indead 2 x seperate OnSerializeNetworkView calls on the server side, and not some kind of fancy readable and writeable stream. This makes any ordering of stream.isReading/isWriting statements irrelevant.

    Just in case anyone was wondering ;o)

    //Perlohmann
     
  14. Der Dude

    Der Dude

    Joined:
    Aug 7, 2006
    Posts:
    213
  15. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    yup already did =) was where I tested for it. Thats also why you can paste the code directly from the previous post into it and get the debug information as nothing has been removed or renamed.

    very useful btw. ;o)
     
  16. Der Dude

    Der Dude

    Joined:
    Aug 7, 2006
    Posts:
    213
    Ah, ok ;)

    So to sum up our results...

    To make a script send/receive variable amounts of data, the server must do the following:

    1. OnSerializeNetworkView (reading):
    Store all received data in a List.

    2. OnSerializeNetworkView (writing):
    Put the contents of the List into the stream.
    Clear the List.
     
  17. perlohmann

    perlohmann

    Joined:
    Feb 12, 2009
    Posts:
    221
    Yes that about does it =)

    I dont nessearily think you need a list though (if Im correct, you only need to store 1 "packet" before you can relay it).

    NB! --> this is not verified and I dont really have an answer on how to test it, but it makes sense if it functions the following way.

    The server will always read first and then consequently write. There should not be multiple reads (on the same NetworkView) before a write (it seams logical that it works this way but you never can be sure with someone elses code ;oD).

    There are some cases where you dont want to relay (because it is the server networkview that is the originator) so you cant use stuff like Network.isServer on its own. But you can use it together with networkView.isMine.

    This is kinda a psudo code (never learned to spell that freaking word)
    Code (csharp):
    1.  
    2. public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    3. {
    4.    if (stream.isReading)
    5.    {
    6.       //reading
    7.       if (Network.isServer  !networkView.isMine)
    8.       {
    9.          //store for relay
    10.       }
    11.       //AND act on the received data
    12.    }
    13.    else
    14.    {
    15.       //writing
    16.       if (Network.isServer  !networkView.isMine  /* has relay data? */)
    17.       {
    18.          //send as relay
    19.          //after send. reset relay buffer.
    20.       }
    21.       else
    22.       {
    23.          //OR send normally
    24.       }
    25.    }
    26. }
    27.  
    Hope anybody can use it =)

    //Perlohmann
     
  18. Der Dude

    Der Dude

    Joined:
    Aug 7, 2006
    Posts:
    213
    I think you hit the nail right on the head there. However, you somehow have to store your packet for the second call to OnSerializeNetworkView. So you either have to have a "packet" variable declared outside of this function, or a more general solution would be a List. With a List you simply throw all data received on there and write it back to the stream on the second call. No interpretation needed.

    I submitted this as a documentation-bug, as the documentation states:

    As you have proven, this is utterly false.