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 =)
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!
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
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
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
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?
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 ) ) This works: Code (csharp): public class TestScript : MonoBehaviour { public int count; public int pID; public short[] testArray; public void Start() { count = 2; pID = Network.player.GetHashCode(); } void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if (stream.isWriting) { stream.Serialize(ref count); stream.Serialize(ref pID); testArray = new short[count]; for (int i = 0; i < count; i++) { testArray[i] = (short)i; stream.Serialize(ref testArray[i]); } } else { stream.Serialize(ref count); stream.Serialize(ref pID); Debug.Log("PID: " + pID + " - Count: " + count); testArray = new short[count]; for (int i = 0; i < count; i++) { stream.Serialize(ref testArray[i]); Debug.Log("PID: " + pID + " - value: " + testArray[i]); } } } } This does not work: Code (csharp): public class TestScript : MonoBehaviour { void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { int count = 2; int pID = Network.player.GetHashCode(); if (stream.isWriting) { stream.Serialize(ref count); stream.Serialize(ref pID); short[] testArray = new short[count]; for (int i = 0; i < count; i++) { testArray[i] = (short)i; stream.Serialize(ref testArray[i]); } } else { stream.Serialize(ref count); stream.Serialize(ref pID); Debug.Log("PID: " + pID + " - Count: " + count); short[] testArray = new short[count]; for (int i = 0; i < count; i++) { stream.Serialize(ref testArray[i]); Debug.Log("PID: " + pID + " - value: " + testArray[i]); } } } } 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
This works: Code (csharp): public class TestScript : MonoBehaviour { public int count; public int pID; public short[] testArray; public void Start() { count = 2; pID = Network.player.GetHashCode(); } void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if (stream.isWriting) { stream.Serialize(ref count); stream.Serialize(ref pID); if (count > 0) { testArray = new short[count]; for (int i = 0; i < count; i++) { testArray[i] = (short)i; stream.Serialize(ref testArray[i]); } } } else { stream.Serialize(ref count); stream.Serialize(ref pID); Debug.Log("PID: " + pID + " - Count: " + count); if (count <= 300 count > 0) { testArray = new short[count]; for (int i = 0; i < count; i++) { stream.Serialize(ref testArray[i]); Debug.Log("PID: " + pID + " - value: " + testArray[i]); } } } } } This makes Unity hang (sooo it dosnt work either): Code (csharp): public class TestScript : MonoBehaviour { public int count; public int pID; public short[] testArray; public void Start() { count = 2; pID = Network.player.GetHashCode(); } void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if (stream.isWriting) { stream.Serialize(ref count); stream.Serialize(ref pID); if (count > 0) { testArray = new short[count]; for (int i = 0; i < count; i++) { testArray[i] = (short)i; stream.Serialize(ref testArray[i]); } } } else { stream.Serialize(ref count); stream.Serialize(ref pID); Debug.Log("PID: " + pID + " - Count: " + count); if (count <= 300 count > 0) { testArray = new short[count]; for (int i = 0; i < count; i++) { stream.Serialize(ref testArray[i]); Debug.Log("PID: " + pID + " - value: " + testArray[i]); } } } count = (count % 300); count++; } } Difference is that the first block always has the same array size while the latter has an array size going from 1-300. =(
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): public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if(stream.isReading) { int count = 0; stream.Serialize( ref count ); for(int i = 0; i < count; i ++) { int num = 0; stream.Serialize(ref num); receiveCount[num] = ((int)receiveCount[num]) + 1; dispStr = "Received: " + num + "\n" + dispStr; // If we are the server, keep track of all received packets to relay them later. if(Network.isServer) resendPackets.Add(num); } // If count != 0, we are a client that receives relayed packets. stream.Serialize(ref count); for(int i = 0; i < count; i ++) { int num = 0; stream.Serialize(ref num); dispStr = "Received Relay: " + num + "\n" + dispStr; // Dont do ++ if this message came from the local player if( num != localPlayerNum) receiveCount[num] = ((int)receiveCount[num]) + 1; } } else if(stream.isWriting) { // Send regular packets from this local player stream.Serialize( ref sendCount ); for(int i = 0; i < sendCount; i ++) { int num = localPlayerNum; stream.Serialize(ref num); receiveCount[num] = ((int)receiveCount[num]) + 1; dispStr = "Sent: " + num + "\n" + dispStr; } sendCount = 0; // If sendRelayCount != 0, we are the server relaying packets to the other clients int sendRelayCount = resendPackets.Count; stream.Serialize(ref sendRelayCount); for(int i = 0; i < sendRelayCount; i ++) { int num = (int)resendPackets[i]; stream.Serialize(ref num); dispStr = "Relayed: " + num + "\n" + dispStr; } resendPackets.Clear(); } } Hope this helps you perlohmann!
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
Well you can't. What I do is, I create an object with the shown script attatched to it for each connected player.
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): public void Start() { if (this.networkView != null) { if (this.networkView.isMine) { Debug.Log("networkView is mine! - ViewID: " + this.networkView.viewID.ToString()); } else { Debug.Log("networkView is not mine! - ViewID: " + this.networkView.viewID.ToString()); } } } public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if(stream.isReading) { int count = 0; stream.Serialize( ref count ); if (Network.isServer !this.networkView.isMine count > 0) { Debug.Log("Stream.isReading on viewID: " + this.networkView.viewID.ToString()); } for(int i = 0; i < count; i ++) { int num = 0; stream.Serialize(ref num); receiveCount[num] = ((int)receiveCount[num]) + 1; dispStr = "Received: " + num + "\n" + dispStr; Debug.Log("Receiving on view: " + this.networkView.viewID.ToString()); // If we are the server, keep track of all received packets to relay them later. if(Network.isServer) resendPackets.Add(num); } // If count != 0, we are a client that receives relayed packets. stream.Serialize(ref count); for(int i = 0; i < count; i ++) { int num = 0; stream.Serialize(ref num); dispStr = "Received Relay: " + num + "\n" + dispStr; // Dont do ++ if this message came from the local player if( num != localPlayerNum) { receiveCount[num] = ((int)receiveCount[num]) + 1; } else { Debug.Log("Received own relayed packet"); } } } else if(stream.isWriting) { // Send regular packets from this local player stream.Serialize( ref sendCount ); if (Network.isServer !this.networkView.isMine (sendCount > 0 || resendPackets.Count > 0)) { Debug.Log("Stream.isWriting on viewID: " + this.networkView.viewID.ToString()); } for(int i = 0; i < sendCount; i ++) { int num = localPlayerNum; stream.Serialize(ref num); receiveCount[num] = ((int)receiveCount[num]) + 1; dispStr = "Sent: " + num + "\n" + dispStr; } sendCount = 0; // If sendRelayCount != 0, we are the server relaying packets to the other clients int sendRelayCount = resendPackets.Count; if (sendRelayCount != 0) { Debug.Log("Server is relaying from viewID: " + this.networkView.viewID.ToString()); } stream.Serialize(ref sendRelayCount); for(int i = 0; i < sendRelayCount; i ++) { int num = (int)resendPackets[i]; stream.Serialize(ref num); dispStr = "Relayed: " + num + "\n" + dispStr; } resendPackets.Clear(); } } 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
Thats an insightful observation. The workaround in my test project works, so check that out. I've succesfully adapted it to my project. http://rapidshare.com/files/237231029/Variable_Update_Size_Bug.zip
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)
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.
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): public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { if (stream.isReading) { //reading if (Network.isServer !networkView.isMine) { //store for relay } //AND act on the received data } else { //writing if (Network.isServer !networkView.isMine /* has relay data? */) { //send as relay //after send. reset relay buffer. } else { //OR send normally } } } Hope anybody can use it =) //Perlohmann
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.