Lagtastic

Works peachy on a lan across any distance though it gets ridiculously laggy. I used the blocking approach because I do not come close to understanding the non-blocking approach no matter how much I read about it. In fact I didn’t read anything at all on nonblocking, which is probably why my code is laggy, but I was still able to figure it out.

My last post was far too long so Ill ahve to give it in bits and pieces. Here are what I think are the most important two classes.

SocketAction - socket wraper

import java.io.*;
import java.net.*;

public class SocketAction extends Thread    {

    private BufferedReader inStream = null;
    protected PrintStream outStream = null;
    private ObjectOutputStream oos = null;
    private ObjectInputStream ois = null;
    
    private Socket socket = null;
    
    public SocketAction(Socket sock) {
    	super("SocketAction");
    	try {
            inStream = new BufferedReader(new InputStreamReader(sock.getInputStream()));
            outStream = new PrintStream(new
                BufferedOutputStream(sock.getOutputStream(), 1024), true);
            
            oos = new ObjectOutputStream(sock.getOutputStream());
            ois = new ObjectInputStream(sock.getInputStream());
            socket = sock;
        }
        catch (IOException e) {
            System.out.println("Couldn't initialize SocketAction: " + e);
            System.exit(1);
        }
    }
    
    public void send(Object o) throws IOException {
        oos.writeObject(o);
        oos.reset();
    }
    
    public void send(String s) {
        outStream.println(s);
    }

    public String receive() throws IOException {
        return inStream.readLine();
    }
    
    public Object receiveObject() throws IOException, ClassNotFoundException {
        return ois.readObject();
    }
    
    
    public boolean isConnected() {
        return ((inStream != null) && (outStream != null) && (socket != null));
    }
    
    protected void finalize () {
        if (socket != null) {
        try {
            socket.close();
        }
        catch (IOException e) {
            System.out.println("Couldn't close socket: " + e);
            }
        socket = null;
        }
    }
    
    public void closeConnections() {
        try {
            socket.close();
            socket = null;
        }
        catch (IOException e) {
            System.out.println("Couldn't close socket: " + e);
        }
    }
    
    public void reset() {
        try {
            inStream.reset();
        }
        catch (IOException e) {
        }
    }
}
        

Message - sent object

import java.io.*;

public class Message implements Serializable {
	private String title;
	private String message;
	private Object data;
	
	public Message(String title, String message) {
		this.title = title;
		this.message = message;
	}
	
	public Message(String title, String message, Object data) {
		this(title, message);
		this.data = data;
	}
	
	public String getTitle() {
		return title;
	}
	
	public String getMessage() {
		return message;
	}
	
	public Object getData() {
		return data;
	}
}

HostListener - clients communication with host

import java.io.*;
import java.net.*;

public class HostListener extends Thread {
    private SocketAction host;
    private boolean running = true;
    private String name;
    
    public HostListener(String name) {
        this.name = name;
    }
    
    public boolean connectTo(String ip) {
    	try {
            host = new SocketAction(new Socket(ip, 64652));
        }
        catch (UnknownHostException e) {
        	e.printStackTrace();
        	return false;
        }
        catch (IOException e) {
        	e.printStackTrace();
        	return false;
        }
        send("init:" + name);
        start();
        return true;
    }
    
    public void run() {
        try {
            while (running) {
           		Object o = host.receiveObject();
           		processObject(o);
            }
        }
        catch (IOException e) {
        	System.err.println(e);
        }
        catch (ClassNotFoundException e) {
        	System.err.println(e);
        }
    }
    
    public void processMessage(String message) {
    	
    }
    
    public synchronized void processObject(Object o) {
    	Message message = (Message)o;
    	if (message.getTitle().equals("state")) {
    		GameManager.INSTANCE.adjustWorld(message.getMessage());
    	}
    	else if (message.getTitle().equals("new")) {
    		GameManager.INSTANCE.getWorld().addSprite((Sprite)message.getData());
    	}
    	else if (message.getTitle().equals("remove")) {
    		GameManager.INSTANCE.getWorld().removeSpriteForID(Integer.parseInt(message.getMessage()));
    	}
    }
    
    public void send(String s) {
        host.send(s);
    }
}

Any point in the right direction would be appriciated

Didnt fit in first one.

Client listener - hosts way of communicating with clients

import java.io.*;
import java.net.*;
import java.util.*;

public class ClientListener extends Thread {
	private World world;
    private LinkedList<Client> clients;
    private ServerSocket serv;
    private boolean running = true;
    private ResourceManager rm = ResourceManager.INSTANCE;
    
    public ClientListener(World world) {
    	this.world = world;
        try {
            serv = new ServerSocket(64652);
        }
        catch (IOException e) {
        }
        clients = new LinkedList<Client>();
        start();
    }
    
    public Client getHost() {
    	return clients.get(0);
    }
    
    public void processInput(Tank tank, String message) {
        message = message.substring(5);
        String[] parts = message.split(";");
        tank.setVelocityX(0.0f);
        tank.setVelocityY(0.0f);
        
        if (parts[0].equals("1")) {
            tank.setVelocityY(tank.getVelocityY() - Tank.SPEED);
        }
        else if (parts[1].equals("1")) {
            tank.setVelocityY(tank.getVelocityY() + Tank.SPEED);
        }
        else if (parts[2].equals("1")) {
            tank.setVelocityX(tank.getVelocityX() - Tank.SPEED);
        }
        else if (parts[3].equals("1")) {
            tank.setVelocityX(tank.getVelocityX() + Tank.SPEED);
        }
        if (parts[4].equals("1")) {
            tank.fire();
        }
    }
    
    public synchronized boolean processMessage(Client client, String message) {
    	if (message.equals("quit")) {
            return false;
        }
        if (message.startsWith("input")) {
        	if (client.getTank() != null) {
        		processInput(client.getTank(), message);
        	}
        }
        if (message.startsWith("init")) {
       		message = message.substring(5);
       		Tank tank = generateTank(message);
       		client.setTank(tank);
       		world.addSprite(tank);
       		sendWorldTo(client.getSock());
        }
        
        return true;
    }
    
    public synchronized void sendString(String s) {
        for (Client c : clients) {
            c.getSock().send(s);
        }
    }
    
    public synchronized void sendObject(Object o) {
        try {
            for (int i = 0 ; i < clients.size() ; i++) {
            	Client c = clients.get(i);
                c.getSock().send(o);
            }   
        }
        catch (IOException e) {
        }
    }
    
    public void run() {
        try  {
            while (running) {
                SocketAction sock = new SocketAction(serv.accept());
                
                newCommer(sock);
            }
        }
        catch (IOException e) {
        	e.printStackTrace();
        }
    }
    
    public Tank generateTank(String name) {
    	Tank tank = new Tank(rm.getAnimationForKey("green up"), 
    			rm.getAnimationForKey("green down"), 
                rm.getAnimationForKey("green right"), 
                rm.getAnimationForKey("green left"), 
                rm.getAnimationForKey("green fire up"), 
                rm.getAnimationForKey("green fire down"), 
                rm.getAnimationForKey("green fire right"), 
                rm.getAnimationForKey("green fire left"), 
                Tank.GREEN);
    	tank.setName(name);
    	tank.setPosition(100, 100);
    	tank = (Tank)World.tagSprite(tank);
    	return tank;
    }
    
    public synchronized void newCommer(SocketAction sock) {
    	clients.add(new Client(sock));
    }

    public synchronized void sendWorldTo(SocketAction socket) {
    	synchronized (socket) {
    		try {
    			for (Sprite s : world.getSprites()) {
    				socket.send(new Message("new", "", s));
    		
    			}
    		}
    			catch (IOException e) {
    			System.err.println(e);
    		}	
    	}
    }
    
    private class Client extends Thread {
        
        private Tank player;
        private SocketAction sock;
        private boolean running = true;
        
        public Client(SocketAction sock) {
        	this(sock, null);
        }
        
        public Client(SocketAction sock, Tank tank) {
            player = tank;
            this.sock = sock;
            start();
        }
        
        public SocketAction getSock() {
            return sock;
        }
        
        public void setTank(Tank tank) {
        	player = tank;
        }
        
        public Tank getTank() {
        	return player;
        }
        
        public void run() {
            try {
                while (running && sock.isConnected()) {
                    String in = sock.receive();
                    if (in != null) {
                        running = processMessage(this, in);
                    }
                }
            }
            catch (IOException e) {
            	e.printStackTrace();
            }
        }
    }
}

I would really recommend using a third-party solution than trying to go the old I/O approach.

I can biasedly recommend JGN: http://javagamenetworking.dev.java.net

It uses NIO and has a ton of examples in the source and is about as simple to use as anything you’ll find.

If you’re resist to a 3rd party solution, you may want to move away from serialization. It’s much less efficent then implementing your own object->binary and binary->object algorithms (not to mention it’s smaller, would help with the lag you’re seeing ;D).

Also, using binary data over strings would help too.

Look up java.io.DataOutputStream/java.io.DataInputStream.

I am resistant to a 3rd party “thingy” of any kind. I was in the process of removing all the serialization stuff, sending strings now. Hadn’t thought of binary data, i’ll look into that thanks.

You may also think of asynchronous processing, so instead of e.g.


  public synchronized void processObject(Object o) {
    	Message message = (Message)o;
    	if (message.getTitle().equals("state")) {
    		GameManager.INSTANCE.adjustWorld(message.getMessage());
    	}
    	else if (message.getTitle().equals("new")) {
    		GameManager.INSTANCE.getWorld().addSprite((Sprite)message.getData());
    	}
    	else if (message.getTitle().equals("remove")) {
    		GameManager.INSTANCE.getWorld().removeSpriteForID(Integer.parseInt(message.getMessage()));
    	}
    }

do something like


   public synchronized void processObject(Object o) {
    	final Message message = (Message)o;
        new Thread(){
            public void run()
            {
              if (message.getTitle().equals("state")) {
                GameManager.INSTANCE.adjustWorld(message.getMessage());
              }
              else if (message.getTitle().equals("new")) {
                GameManager.INSTANCE.getWorld().addSprite((Sprite)message.getData());
              }
              else if (message.getTitle().equals("remove")) {
                GameManager.INSTANCE.getWorld().removeSpriteForID(Integer.parseInt(message.getMessage()));
           }  
    	}.start();
    }

This is of course brute force threading :wink: To retain message order and reduce thread creation overhead, you night want to create one dedicated worker thread and insert the messages into a processing queue.

On a side-note: Don’t be too resistant to 3rd party libs - they could really save you a lot of hassle as long as they are licensed appropriately (BSD licensed stuff is great since it puts nearly no restriction on you)

Why resistant to third-party libraries? I can understand if you’re attempting to learn, but based on your original statement that NIO was too hard it would seem you’re only hurting yourself by refusing to use a third-party library.

I’m only resistant for now. When I finish this project, I’ll probably attempt one with a non-blocking approach. Then after that I’ll probably just adopt some 3rd party code.

I have removed all the serialization and am just sending a String now, havn’t tested it yet.

Will this be speed it up or do nothing?

DataOutputStream out = new DataOutputStream(myOtherStream);
out.writeUTF(myStringMessage);

and for reading

BufferedReader reader = new BufferedReader(new DataInputSream(myOtherStreams));
String message = reader.readLine();reader

Will those be faster than reading writing a stream or the same?

I’m not sure if you get the correct message with (ObjectOutputStream).writeUTF() because I experienced some errors on the beginning of serials with UTF and Strings. You better use writeByte() for Strings. And as well for reading readByte() should behave better, too.
One more word about bytes R/W’ing , try to init a byte array of size 512 “at the most” to get the faster results and to keep the system bus r/w channels sufficiently “fresh” ! There’s more on the web about the byte arrays serialization process.

Well, for starters: You’ll get garbled output using that setup.

DataOutputStream.writeUTF(String) writes 2 bytes for the string’s length, then string data.

BufferedReader.readLine() reads in string data until it reachs a newline (potentially never with the current implementation).

If you change the BufferedReader.readLine() to DataInputStream.readUTF(), you’ll be fine.

As for this, I think you were looking at the wrong stream :stuck_out_tongue:

But on a different note: Using writeByte() and readByte() instead of writeUTF() and readUTF() might not account for unicode characters that take 2 or more bytes (also, there’s a writeBytes() in ObjectOutputStream for writing string bytes, but no readBytes() in ObjectInputStream).

I can’t comment on the byte array size as I haven’t actually looked into it.

Oopsies. No errors though.