problems with WWW

Hi I’m doing a multiuser game, and starting the synchronization of the users part. At the server side I have an servlet to login and a servlet to sync that receive the current user location and respond with a list of current users position.

For now I didn’t put in place the interpolation of the characters posistions, but its working fine to synchronize multiple users positions.

The problem I’m facing is that after some minutes, sometimes seconds the client just crashes.

Do I need to release anything that I’m missing ?
here is the code that I’m using
any help will be apreciated.

var speed: int = 5;
var syncInterval:float = 0.20;
var nextSync: float = 0;
var userObjects: Array = new Array();
var otherUserType: Transform;
var activeUsers: Array = new Array();
var inUse: boolean;

// local movement direction
var dx:float = 0;
var dy:float = 0;
var dz:float = 0;

// constansts of the data array position
var POS_NAME: int = 0;
var POS_X: int = 1;
var POS_Y: int = 2;
var POS_Z: int = 3;

function Update(){
    dx = Input.GetAxis("Horizontal") * speed * Time.deltaTime;
    dy = 0;
    dz = Input.GetAxis("Vertical") * speed * Time.deltaTime;
    
    transform.Translate(dx , dy, dz );
    sync(dx,dy,dz);
}

function sync(dx,dy,dz){
	
	if(MainScript.userName != ""  Time.time > nextSync  ! inUse){
		inUse = true;
		var start = Time.time;		
		
		// build the form to make the call
		var form = new WWWForm();
		form.AddField("name", MainScript.userName);
		form.AddField("x", transform.position.x.ToString() );
		form.AddField("y", transform.position.y.ToString() );
		form.AddField("z", transform.position.z.ToString() );

		// make the remote call	
		var url = MainScript.server + "/sync";
	    var localWWW = new WWW(url, form);
	    yield localWWW;
		
		form = null;	
		activeUsers = new Array();
	    
	    // parse the result.
	    var users = localWWW.text.Split(";"[0]);
	    for(i=0; i < users.length; i++){
	    	var oneUser = users[i].Split(","[0]);
	    	
	    	// keep this list to check if the existing users are all active
	    	activeUsers.Push(oneUser[POS_NAME]);
	    	var found: boolean = false;
	    		    		
	    	if( oneUser[POS_NAME] == MainScript.userName ){
	    		found = true;
	    	}
	    	
	    	// update each user with the new data
	    	for(n=0; n < userObjects.length  !found; n++){
	    			    		
	    		if( userObjects[n].GetComponent(UserInformationScript).userName == oneUser[POS_NAME] ){
	    			
					userObjects[n].position.x = float.Parse(oneUser[POS_X]);
					userObjects[n].position.y = float.Parse(oneUser[POS_Y]);
					userObjects[n].position.z = float.Parse(oneUser[POS_Z]);

	    		    found = true;
	    			break;
	    		}
	    	}
	    	
	    	// add the new object if its not found
	    	if(!found){
	    		var pos: Vector3 = new Vector3(float.Parse(oneUser[POS_X]), float.Parse(oneUser[POS_Y]), float.Parse(oneUser[POS_Z]) );
	    		var temp = Instantiate(otherUserType,pos,Quaternion.identity);
				temp.GetComponent(UserInformationScript).userName = oneUser[POS_NAME];
	    		userObjects.Push(temp);
	    	}

	    }
	    
	    // check if the existing user objects are all active.
	    for(n=0; n < userObjects.length; n++){
	    	found = false;
		    for(m=0; m < activeUsers.length; m++){
		    	if( userObjects[n].GetComponent(UserInformationScript).userName == activeUsers[m] ){
		    		found = true;
		    		break;
		    	}
		    }
		    
		    if(!found){
		    	userObjects.Remove(n);
		    	n--;
		    }
	    	
	    }

	    // release the WWW object
	    localWWW.Dispose();
	    localWWW = null;
	    
	    // reset the next sync time
	    nextSync = Time.time + syncInterval;
	    
		var elapsed: float = Time.time - start;
		MainScript.status = "elapsed = " + elapsed;
		inUse = false;
	}

}

made a simple change and its under test now.

moved the declaration of the WWW object to outside the update method, and removed the Dispose() call.

so this on the first line of the script

var localWWW: WWW;

this in the loca creation of the WWW object

// var localWWW = new WWW(url, form);
localWWW = new WWW(url, form);
yield localWWW;

and this at the end

// release the WWW object
// localWWW.Dispose();
localWWW = null;

the test is running for 5 minutes now …

oh well still crashing here is the crash report … I believe that is something related to the network …
any suggestions ??

Date/Time:       2010-12-30 20:12:02 -0200
OS Version:      10.6.5 (Build 10H574)
Architecture:    x86_64
Report Version:  6

Command:         unity client
Path:            /Users/hamiltonlima/Documents/ludumdare19/unity client/build/mac/hideandseek-test3.app/Contents/MacOS/unity client
Version:         Unity Player version 3.1.0f4 (3.1.0f4)
Parent:          launchd [266]

PID:             46764
Event:           hang
Duration:        7.72s (sampling started after 2 seconds)
Steps:           57 (100ms sampling interval)

Pageins:         3
Pageouts:        0


Process:         unity client [46764]
Path:            /Users/hamiltonlima/Documents/ludumdare19/unity client/build/mac/hideandseek-test3.app/Contents/MacOS/unity client
UID:             501


Process:         AirPort Base Station Agent [299]
Path:            /System/Library/CoreServices/AirPort Base Station Agent.app/Contents/MacOS/AirPort Base Station Agent
UID:             501

  Thread 9cf13d4   
  User stack:
    57 ??? (in AirPort Base Station Agent + 5692) [0x10000163c]
      57 NSApplicationMain + 364 (in AppKit) [0x7fff8561d1a8]
        57 -[NSApplication run] + 395 (in AppKit) [0x7fff8562448b]
          57 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 155 (in AppKit) [0x7fff8565e7a9]
            57 _DPSNextEvent + 718 (in AppKit) [0x7fff8565ee64]
              57 BlockUntilNextEventMatchingListInMode + 59 (in HIToolbox) [0x7fff82e0a5d8]
                57 ReceiveNextEventCommon + 310 (in HIToolbox) [0x7fff82e0a71f]
                  57 RunCurrentEventLoopInMode + 333 (in HIToolbox) [0x7fff82e0a91a]
                    57 CFRunLoopRunSpecific + 575 (in CoreFoundation) [0x7fff8084cdbf]
                      57 __CFRunLoopRun + 1698 (in CoreFoundation) [0x7fff8084d932]
                        57 mach_msg_trap + 10 (in libSystem.B.dylib) [0x7fff8224c2da]
  Kernel stack:
    57 ipc_mqueue_receive_continue + 0 [0x210ce6]

  Thread 9dbab7c    DispatchQueue 2
  User stack:
    57 start_wqthread + 13 (in libSystem.B.dylib) [0x7fff82266005]
      57 _pthread_wqthread + 353 (in libSystem.B.dylib) [0x7fff82266168]
        57 _dispatch_worker_thread2 + 252 (in libSystem.B.dylib) [0x7fff8226683e]
          57 _dispatch_queue_invoke + 185 (in libSystem.B.dylib) [0x7fff82266d14]
            57 kevent + 10 (in libSystem.B.dylib) [0x7fff8226516a]
  Kernel stack:
    57 kevent + 97 [0x473932]

  Thread 9dba3d4   
  User stack:
    57 thread_start + 13 (in libSystem.B.dylib) [0x7fff822853e9]
      57 _pthread_start + 331 (in libSystem.B.dylib) [0x7fff82285536]
        57 ??? (in AirPort Base Station Agent + 117988) [0x10001cce4]
          57 ??? (in AirPort Base Station Agent + 106856) [0x10001a168]
            57 ??? (in AirPort Base Station Agent + 118817) [0x10001d021]
              57 __semwait_signal + 10 (in libSystem.B.dylib) [0x7fff82286fca]
  Kernel stack:
    57 semaphore_wait_continue + 0 [0x22a3dd]

** the full crash report is attached.

462510–16240–$athanazio WWW crash report 2010-dec-30.txt.zip (28 KB)

doing some more investigation, when running one client the number of network connections on TIME_WAIT go to 140 :slight_smile: looks like there is a lot of left behind WWW connections hehehe.

and more around 140 for each client … will check if I can make this WWW connection reusable.

The code you have there is extremely bad

You call the WWW each frame, thats deadly basically, at worst you get even struck by thread context errors (not on osx but windows clients and webclients on windows won’t go anywhere anymore)

Also your webserver will suffer like hell.

you need to add a condition on when its meant to check that and call the whole sync function there, like 10 times a second, once a second, after each turn or whatever depending on the game type

ok first thanks for ur motivational comment :slight_smile:
second Im doing what you said, first line in the sync() function

function sync(dx,dy,dz){
	
	if(MainScript.userName != ""  Time.time > nextSync  ! inUse){
...

Time.time > nextSync to avoid the call on each update(),
and inUse to avoid a duplicate call if the time is not enough.

so I’m doing the call on each 0.2 secs, If I understand correctly the value from Time.time,
this is defined on the second line

var syncInterval:float = 0.20;

beside this details, any other extremelly bad part in the code ?

thanks.

oh must have missed that. I normally put these kind of checks in the place where the function is called because going into a coroutine (thats what you have there, although UnityScript unhappily stealths this kind of stuff) isn’t free.

Otherwise I don’t see anything extremely bad as you have the inUse variable although I would consider updating time and inuse right after the yield for www, so variable cpus speeds and correspondingly variable parse times for the response don’t impact the future transmissions

Generally I would consider don’t calling this from update at all.
Instead I would make this coroutine looping for the lifetime of the “play mode” and fire the coroutine off in start already.
That frees you from the presence of update.

Also the update code there is a bit troublesome: You actually sync the current delta of this frame, but you don’t sync each frame. that won’t work out technically like this as the representation on the server will be completely wrong basically.

What you would need is having the dx, dy, dz on the monobehaviour itself and sum it up on each change and then transmit that sum up to now so the state is correct and resetting it (ie setting the cached dx, dz states to 0) after fetching it for the WWW transmit in the WWWForm

alternatively don’t send the dx and instead send the current position at the time of sync. that gives the most accurate representation. (as you use WWW you don’t do any autorative handling on the server anyway so dx, dz won’t serve any verification purpose and sending the actual position is fine)

Thanks for the comments !

The problem about the WWW seems to be the nature of atomic network connection, that starts and closes right after, this seems to be using too many network resources on the machine, now Im changing to use TCPClient, and looking for a way to make the calls asynchronous.

as soon I have the code will post here.

WWW is async actually (thats why you can / must yield)

But whats not async and what really is eating the hair from your head is the string parsing.
Thats why such data normally is sent as XML or JSON so you can use those parsers which are significantly more optimized than the bruteforce parser you have there

TCPClient won’t get much from it as having multiple threads will only make code 10 times more complex, as callbacks / threads can not touch any class from the UnityEngine framework or you will get a bombing up application

As for the connection terminating: that makes sense in this case as WWW is not intend for continous data transfer, http itself isn’t really optimal for it. Thats why there is TCP sockets, UDP sockets and unity networking to communicate to a realtime server that does not die a hefty death in hell if you do something like this here like a webserver will if you have the hope to do permanent connections with several hundred users against a webserver.

Love this !! LOL

thanks again, and for the sake of documentation the version with sockets is running from now … started 3 clients and will go to sleep :slight_smile:
I hope to have good news tomorrow morning.

cheers

Looking forward to it :slight_smile:
And glad the total messup in that sentence still made sense … too late combined with too long sentence resulted in the expected “messy messy d’oh” :wink:

Yes ! while I was sleeping the crash test went ok ! 6 hours with 3 clients running on the same machine of the server :stuck_out_tongue: and the clients didnt crash !!

here is the code that I’m using for the socket calls

// code adapted from
// http://answers.unity3d.com/questions/8128/unity-project-and-3rd-party-apps/8183#8183
//

import System;
import System.IO;
import System.Net.Sockets;

static var socketReady:boolean = false;

static var mySocket:TcpClient;
static var theStream: NetworkStream;
static var theWriter: StreamWriter;
static var theReader: StreamReader;
static var Host:String = "localhost";
static var Port:int = 42000;
static var response:String;

static function setupSocket() {
    try {
        mySocket = new TcpClient(Host, Port);
        theStream = mySocket.GetStream();
        theWriter = new StreamWriter(theStream);
        theReader = new StreamReader(theStream);
        socketReady = true;
    }
    catch (e:Exception) {
        Debug.Log("Socket error: " + e);
    }
    
    return socketReady;
}

static function writeSocket(line:String) {
    
    if (!socketReady)
        return;
    
    var foo:String = line + "\n";
    theWriter.Write(foo);
    theWriter.Flush();
}

static function readSocket() {
    if (!socketReady){
        response = "";   
    } else {
   		//if (theStream.DataAvailable)
	    response = theReader.ReadLine();
    }
    
    return response;
}

static function closeSocket() {
    if (!socketReady)
        return;

    theWriter.Close();
    theReader.Close();
    mySocket.Close();
    socketReady = false;
}

and the calls are something like this

var data:String = "SYNC:"+ MainScript.userName +",";
data += transform.position.x.ToString()+",";
data += transform.position.y.ToString()+",";
data += transform.position.z.ToString();
		
TCPScript.writeSocket(data);
yield TCPScript.readSocket();
var users = TCPScript.response.Split(";"[0]);

Now I’m facing other problem :stuck_out_tongue: when the use is not in the list of existing ones anymore … but this should be discussed in other thread I think.

cheers and thanks for the comments !

1 Like