J2ME Game Optimization Secrets (part 5)

Other Techniques


One technique I was unable to include in my example code was the optimal use of a switch() statement. Switches are very commonly used to implement Finite State Machines, which are used in game Artificial Intelligence code to control the behavior of non-player actors. When you use a switch, it is good programming practice to write code like this:


  public static final int STATE_RUNNING = 1000;
  public static final int STATE_JUMPING = 2000;
  public static final int STATE_SHOOTING = 3000;
  switch ( n ) {
    case STATE_RUNNING:
      doRun();
    case STATE_JUMPING:
      doJump();
    case STATE_SHOOTING:
      doShoot();
  }


There’s nothing wrong with this, and the int constants are nice and far apart, in case we might want to stick another constant in between RUNNING and JUMPING, like STATE_DUCKING = 2500. But apparently switch statements can be compiled into one of two byte codes, and the faster of the two is used if the ints used are close together, so this would be better:


  public static final int STATE_RUNNING = 1;
  public static final int STATE_JUMPING = 2;
  public static final int STATE_SHOOTING = 3;


There are also some optimizations you can perform when using a Fixed Point math library. First, if you’re doing a lot of division by a single number, you should instead work out the inverse of that number and perform a multiplication. Multiplication is slightly quicker than division. So instead of…


  int fpP = FP.Div( fpX, fpD );
  int fpQ = FP.Div( fpY, fpD );
  int fpR = FP.Div( fpZ, fpD );


…you should rewrite it like this:


  int fpID = FP.Div( 1, fpD );
  int fpP = FP.Mul( fpX, fpID );
  int fpQ = FP.Mul( fpY, fpID );
  int fpR = FP.Mul( fpZ, fpID );


If you’re performing hundreds of divisions every frame, this will help. Secondly, don’t take your FP math library for granted. If you have source for it, open it up and take a look at what’s going on in there. Make sure all the methods are declared final static and look for other opportunities to improve the code. For example, you may find that the multiplication method has to cast both ints to longs and then back to an int:


public static final int Mul (int x, int y) {
  long z = (long) x * (long) y;
  return ((int) (z >> 16));
}


Those casts take time. Collision detection using bounding circles or spheres involves adding the squares of ints together. That can generate some big numbers that might overflow the upper bound of your int Fixed Point data type. To avoid this, you could write your own square function that returns a long:


    public static final long Sqr (int x) {
      long z = (long) x;
      z *= z;
      return (z >> 16);
    }


This optimized method avoids a couple of casts. If you’re doing a great deal of Fixed Point math, you might consider replacing all of the library calls in the main game loop with the long-hand math. That will save a lot of method calls and parameter passing. You may also find that when the math is written out manually you can reduce the number of casts that are required. This is especially true if you are nesting several calls to your library, e.g.


  int fpA = FP.Mul( FP.toInt(5),
                    FP.Mul( FP.Div( 1 / fpB ),
                    FP.Mul( FP.Div( fpC, fpD ),
                    FP.toInt( 13 ) ) ) );


Take the time to unravel nested calls like this and see if you can reduce the amount of casting. Another way to avoid casting to longs is if you know that the numbers involved are small enough that they definitely won’t cause an overflow.


To help with high-level optimization, you should look for articles on game programming. A lot of the problems presented by game programming such as fast 3D geometry and collision detection have already been solved very elegantly and efficiently. If you can’t find Java source, you will almost certainly find C source or pseudo-code to convert. Bounds checking, for example, is a common technique that we could have used inside our paint() method. Instead of clearing the entire screen every time, we really only need to clear the section of the screen that changes from frame to frame. Because graphics routines are relatively slow you will find that the extra housekeeping required to keep track of which parts of the screen need to be cleared is well worth the effort.


Some phone manufacturers offer proprietary APIs that help programmers get around some of the limitations J2ME presents, such as lack of sound, lack of Image transparency, etc. Motorola, for example, offers a floating point math library that uses floating point math instructions on the chip. This library is much faster than the fastest Fixed Point math library, and a lot more accurate. Using these libraries completely destroys the portability of your code, of course, but they may be an option to consider if deployment on many different handsets is not a concern.


Conclusions
Only optimize code if you need to
Only optimize where it counts
Use the profiler to see where to optimize
The profiler won’t help you on the device, so use the System timer on the hardware
Always study your code and try to improve the algorithms before using low-level techniques
Drawing is slow, so use the Graphics calls as sparingly as possible
Use setClip() where possible to minimize the drawing area
Keep as much stuff as possible out of loops
Pre-calculate and cache like crazy
Strings create garbage and garbage is bad so use StringBuffers instead
Assume nothing
Use static final methods where possible and avoid the synchronized modifier
Pass as few parameters as possible into frequently-called methods
Where possible, remove method calls altogether
Unroll loops
Use bit shift operators instead of division or multiplication by a power of two
You can use bit operators to implement circular loops instead of modulo
Try to compare to zero instead of any other number
Array access is slower than C, so cache array elements
Eliminate common sub-expressions
Local variables are faster than instance variables
Don’t wait() if you can callSerially()
Use small, close constants in switch() statements
Look inside your Fixed Point math library and optimize it
Unravel nested FP calls to reduce casting
Division is slower than multiplication, so multiply by the inverse instead of dividing
Use tried and tested algorithms
Use proprietary high-performance APIs with care to preserve portability
Where to next?
Optimization is a black art. At the heart of any computer lies the CPU and at the heart of Java lies a virtual CPU, the JVM. To squeeze the last ounce of performance from the JVM, you need to know a lot about how it functions beneath the hood. Specifically, you need to know what things the JVM can do fast, and what it does slowly. Look for sites with solid information on the inner workings of Java. You don’t necessarily have to learn how to program in byte code, but the more you know, the easier it will be to come up with new ways to optimize your applications for performance.


There’s no substitute for experience. In time you will discover your own secrets about the performance characteristics of J2ME and of the handsets you are developing for. Even if you can’t code around certain idiosynchrasies, you could design your next game around them. While developing my game I found that calling drawImage() five times to draw five images of 25 pixels each is much slower than calling it once to draw an image five times the size. That knowledge will definitely help shape my next game.


Good luck, and have fun.


Resources:



  1. J2ME’s official web site contains the latest on what’s happening on this front.

  2. Like wireless games? Read the Wireless Gaming Review.

  3. Discuss J2ME Game Development at j2me.org

  4. A great site on many aspects of Java Optimization

  5. Another great site on Optimization

  6. Many articles on J2ME performance tuning

  7. The amazing Graphics Programming Black Book by Michael Abrash
  8. The Art of Computer Game Design by Chris Crawford

Developing Applications with the Java APIs for Bluetooth (JSR-82)

Developing Applications with the Java APIs for Bluetooth (JSR-82)



This paper covers the Java API for Bluetooth (JSR-82) with respect to Sony Ericsson devices. It starts by introducing the Bluetooth technology, followed by the Java APIs for Bluetooth, and how to use them.


Currently, these APIs are currently available in the Sony Ericsson P900/P908 handsets.


Click here to view “Developing Applications with the Java APIs for Bluetooth (JSR-82)”



Click here to view the Training Materials (.zip) associated with this article.


j2me:Socket connection

/*
J2ME: The Complete Reference


James Keogh

Publisher: McGraw-Hill

ISBN 0072227109

*/
// jad file (Please verify the jar size first)
/*
MIDlet-Name: socketconnection
MIDlet-Version: 1.0
MIDlet-Vendor: MyCompany
MIDlet-Jar-URL: socketconnection.jar
MIDlet-1: socketconnection, , socketconnection
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0
MIDlet-JAR-SIZE: 100

*/
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import javax.microedition.io.*;
public class socketconnection extends MIDlet implements CommandListener {
  private Command exit, start;
  private Display display;
  private Form form;
  public socketconnection () 
  {
    display = Display.getDisplay(this);
    exit = new Command(“Exit”, Command.EXIT, 1);
    start = new Command(“Start”, Command.EXIT, 1);
    form new Form(“Read Write Socket”);
    form.addCommand(exit);
    form.addCommand(start);
    form.setCommandListener(this);
  }
  public void startApp() throws MIDletStateChangeException 
  {
    display.setCurrent(form);
  }
  public void pauseApp() 
  {
  }
  public void destroyApp(boolean unconditional
  {
  }
  public void commandAction(Command command, Displayable displayable
  {
    if (command == exit
    {
      destroyApp(false);
      notifyDestroyed();
    }
    else if (command == start
    {
      try 
      {
       StreamConnection connection = (StreamConnectionConnector.open(“socket://www.myserver.com:80”);
       PrintStream output = 
         new PrintStream(connection.openOutputStream() );
       output.println“GET /my.html HTTP/0.9\n\n” );
       output.flush();
       InputStream in = connection.openInputStream();
       int ch;
       while( ( ch = in.read() ) != –)
      {
         System.out.print( (charch );
       }
       in.close();
       output.close();
       connection.close();
     }
      catchConnectionNotFoundException error )
       {
         Alert alert = new Alert(
            “Error”“Cannot access socket.”, null, null);
         alert.setTimeout(Alert.FOREVER);
         alert.setType(AlertType.ERROR);
         display.setCurrent(alert);      
        }
        catchIOException error )
        {
         Alert alert = new Alert(“Error”, error.toString(), null, null);
         alert.setTimeout(Alert.FOREVER);
         alert.setType(AlertType.ERROR);
         display.setCurrent(alert);
        }
    }
  }
}


Demonstrates the functionality of DatagramConnection framework.

/*** Chapter 5 Sample Code for Datagram functionality ***/

import 
javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.util.*;

public class DatagramTest extends MIDlet {

  // Port 9001 is used for datagram communication
    static final int receiveport  = 9001;
  Receive receiveThread = new Receive();

    public DatagramTest() {
  }

    public void startApp() {
        // Start the listening thread
        receiveThread.start();
        // Send message Hello World!
        sendMessage(“Hello World!”);
    }

    public void pauseApp() {
    }

    public void destroyApp(boolean unconditional)  {
    }

  // This function sends a datagram message on port 9001.
    void sendMessage(String msg)  {
    String destAddr = “datagram://localhost:” + receiveport;
        DatagramConnection dc = null;
    Datagram dgram;
    byte[] bMsg = msg.getBytes();

    try {
           dc = (DatagramConnection)Connector.open(destAddr);
      // Create a datagram socket and send
            dgram= dc.newDatagram(bMsg,bMsg.length,destAddr);
            dc.send(dgram);
      System.out.println(“Sending Packet:” +  msg);
            dc.close();
        }
        catch (Exception e)  {
            System.out.println(“Exception Connecting: ” + e.getMessage());
    }
    finally {
            if (dc != null) {
                try {
                    dc.close();
                }
                catch (Exception e) {
                   System.out.println(“Exception Closing: ” + e.getMessage());
                }
            }
        }
    }

  // This function is a listener. It waits to receive datagram packets on 9001 port
    class Receive extends Thread  {
        public void run() {
        doReceive();
        }

        void doReceive() {
            DatagramConnection dc = null;
            Datagram dgram;
      try {
        // Open Server side datagram connection
                dc = (DatagramConnection)Connector.open(“datagram://:”+receiveport);
          String receivedMsg;
          while (true) {
                    dgram = dc.newDatagram(dc.getMaximumLength());
                try {
             dc.receive(dgram);
          catch (Exception e) {
            System.out.println(“Exception in receiving message:” + e.getMessage());
          }
                    receivedMsg = new String(dgram.getData()0,dgram.getLength());
                    System.out.println(“Received Message: ” + receivedMsg);
                    try {
                       Thread.sleep(500);
                    }
                    catch (Exception e) {
                      System.out.println(“Exception doReceive(): ” + e.getMessage());
                    }
                }

            }
          catch (Exception e) {
          System.out.println(“Exception doReceive(): ” + e.getMessage());
       }
       finally {
          if (dc != null) {
          try {
                       dc.close();
                   }
                   catch (Exception e) {
                      System.out.println(“Exception Closing: ” + e.getMessage());
                   }
               }
          }

        }
     }
 }


Derby JDBC Connection

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import java.util.Properties;

public class MainClass {
   public static void main(String[] args)
   {
      try {
         String driver = “org.apache.derby.jdbc.EmbeddedDriver”;

         Class.forName(driver).newInstance();
         Connection conn = null;
         conn = DriverManager.getConnection(“jdbc:derby:DerbyTestDB”);
         Statement s = conn.createStatement();
         ResultSet rs = s.executeQuery(“SELECT city, state, zipcode FROM zipcodes”);

         while(rs.next()) {
            System.out.println(“City   : “+ rs.getString(1));
            System.out.println(“State  : “+ rs.getString(2));
            System.out.println(“Zipcode: “+ rs.getString(3));
            System.out.println();
         }

         rs.close();
         s.close();
         conn.close();
      catch(Exception e) {
         System.out.println(“Exception: “+ e);
         e.printStackTrace();
      }
   }
}


Connect to Java DB (Derby) with org.apache.derby.jdbc.EmbeddedDriver

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

public class JavaDBDemo {
  static Connection conn;

  public static void main(String[] args) {
    String driver = “org.apache.derby.jdbc.EmbeddedDriver”;
    String connectionURL = “jdbc:derby:myDatabase;create=true”;
    String createString = “CREATE TABLE Employee (NAME VARCHAR(32) NOT NULL, ADDRESS VARCHAR(50) NOT NULL)”;
    try {
      Class.forName(driver);
    catch (java.lang.ClassNotFoundException e) {
      e.printStackTrace();
    }
    try {
      conn = DriverManager.getConnection(connectionURL);
      Statement stmt = conn.createStatement();
      stmt.executeUpdate(createString);

      PreparedStatement psInsert = conn.prepareStatement(“insert into Employee values (?,?)”);

      psInsert.setString(1, args[0]);
      psInsert.setString(2, args[1]);

      psInsert.executeUpdate();

      Statement stmt2 = conn.createStatement();
      ResultSet rs = stmt2.executeQuery(“select * from Employee”);
      int num = 0;
      while (rs.next()) {
        System.out.println(++num + “: Name: ” + rs.getString(1“\n Address” + rs.getString(2));
      }
      rs.close();
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}


分享一下我的Eclipse啓動參數

eclipse.exe -nl en_US -clean -vmargs -Xverify:none -XX:+UseParallelGC -XX:PermSize=20M -XX:MaxNewSize=32M -XX:NewSize=32M -Xmx256m -Xms128m


-nl 後面跟的是語言


-clean 是當啓動Eclipse IDE時清空緩衝,一般來説在没有更新插件的情况下,去掉這個參數啓動速度更快。


-vmargs 使用JRE的參數,後面就是JRE的參數了:


-Xverify:none 去掉JAR包數據驗證,一般來説只有在網絡環境下才需要驗證JAR包數據的有效性。本地的話可以不用驗證。


-XX:+UseParallelGC 使用并行垃圾收集機制,據説這個GC算法比較快。具體不清楚。


-XX:PermSize=20M -XX:MaxNewSize=32M -XX:NewSize=32M 這三個就是設置詳細的緩衝數據了。詳情看Java官方網站的介紹吧。


-Xmx256m Java虚擬機最大使用内存容量,根據你所使用機器的内容大小設置,只要不超過最大内存容量就好。


-Xms128m Java虚擬機初始化内存容量。


在偶Athlon64 3000+,1GB DDR400的機器第一次啓動速度不到20秒,第二次啓動速度10秒左右。希望對大家有用。