Reasons why Java is not a good language for game development

WinXP/Java6

I had to flip two fullscreen rows to make fullscreen-mode run, setFullscreenWindow must be called before setDisplayMode method. Then I added commandlines to make testing easier.
c:\test> java -cp test.jar GameLoop “fullscreen=true”

I have LCD 60fps screen and was able to see a tearing on both modes. FPS counter actually reported 60-63 frames on both windowed and fullscreen modes. It’s strange to have tearing even this simple and low-cpu intensive animation.


package gameloop;

import java.util.*;

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferStrategy;

import java.awt.DisplayMode; // for full-screen mode

/**
 * A basic game loop.
 * 
 * @author Mario
 */
public class GameLoop implements KeyListener {
    /*
     * The display window
     */
    Frame mainFrame;
    
    /*
     * Saves the time in nanos at which the last frame was drawn
     */ 
    long lastFrameDrawn;
    
    /*
     * The frame rate cap. Set to 60 fps here as a default.
     */
    long fpsLimit = 60;
    
    /*
     * Stores the frames per second.
     */
    long fps;
    
    /*
     * Saves the last time we looked at the clock.
     */
    long lastTime;
    
    /*
     * Saves a count of the number of frames drawn per 1 sec interval.
     */
    long frameCounter;
    
    /*
     * Saves the elapsed time in nanos.
     */
    long elapsedTime;
    
    /*
     * A rectangle to use as a basic game object to move
     * around on screen.
     */
    Rectangle2D rect;
    
    /**
     * Create a new GameLoop that will use the specified GraphicsDevice.
     * 
     * @param device
     */
    public GameLoop(Map<String,String> args, GraphicsDevice device) {
        try {
            // Setup the frame
            GraphicsConfiguration gc = device.getDefaultConfiguration();
            
            mainFrame = new Frame(gc);
            mainFrame.setUndecorated(true);
            mainFrame.setIgnoreRepaint(true);
            mainFrame.setVisible(true);
            mainFrame.setSize(640, 480);
            mainFrame.setLocationRelativeTo(null);
            mainFrame.createBufferStrategy(2);
            mainFrame.addKeyListener(this);

            // Uncomment this code if you want to see the game loop run full
            // screen. Running in full screen will enable the buffer strategy's
            // vertical retrace lock which should result in smoother animation.
            if ("true".equalsIgnoreCase(args.get("fullscreen"))) {
              device.setFullScreenWindow(mainFrame);
              device.setDisplayMode(new DisplayMode(640, 480, 8, DisplayMode.REFRESH_RATE_UNKNOWN));
            }
          
            // Cache the buffer strategy and create a rectangle to move
            BufferStrategy bufferStrategy = mainFrame.getBufferStrategy();
            rect = new Rectangle2D.Float(0,100,64,64);
            
            // Main loop
            
            while(true) {
                long time = System.nanoTime();
                elapsedTime = System.nanoTime() - time;
                
                updateWorld(elapsedTime);
                
                // Draw
                Graphics g = bufferStrategy.getDrawGraphics();
                drawScreen(g);
                g.dispose();
                
                // Flip the buffer
                if( ! bufferStrategy.contentsLost() )
                    bufferStrategy.show();
                
                // Synchronise with the display hardware. Note that on
                // Windows Vista this method may cause your screen to flash.
                // If that bothers you, just comment it out.
                Toolkit.getDefaultToolkit().sync();
                
                yield();
                calculateFramesPerSecond();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            device.setFullScreenWindow(null);
        }
        
    }
    private void updateWorld(long elapsedTime) {
        
        //double xMov = 0.001 * elapsedTime;
        double xMov = 2.0;
        rect.setRect(rect.getX() + xMov, 100, 64, 64);
        
        if( rect.getX() > mainFrame.getWidth() )
            rect.setRect(-rect.getWidth(), 100, 64, 64);
        
    }
    
    private void drawScreen(Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, mainFrame.getWidth(), mainFrame.getHeight());
        g.setColor(Color.WHITE);
        g.drawString("FPS: "+ fps, 0, 17);
        
        g.setColor(Color.RED);
        g.fillRect((int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight());
    }

    protected void yield() throws InterruptedException {
		long delayTime = 1000000000L/fpsLimit  - (System.nanoTime() - lastFrameDrawn);
		if(delayTime > 0) Thread.sleep(delayTime/1000000);
		lastFrameDrawn = System.nanoTime();
	}
    
    private void calculateFramesPerSecond() {
        long time = System.nanoTime();
        if( time - lastTime >= 1000000000L ) {
            fps = frameCounter;
            lastTime = time; 
            frameCounter = 0;
        }
        frameCounter++;
    }
    public void keyPressed(KeyEvent e) {
        if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
            System.exit(0);
        }
    }
    public void keyReleased(KeyEvent e) {
        
    }
    public void keyTyped(KeyEvent e) {
        
    }

    public static void main(String[] args) {
        try {
	    Map<String,String> mapArgs = parseArguments(args);

            GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsDevice device = env.getDefaultScreenDevice();
            new GameLoop(mapArgs, device);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


	/**
	 * Parse commandline arguments, each parameter is a name-value pair.
	 * Example: java.exe MyApp "key1=value1" "key2=value2"
	 */
	private static Map<String,String> parseArguments(String[] args) {
		Map<String,String> mapArgs = new HashMap<String,String>();

		for(int idx=0; idx < args.length; idx++) {
			String val = args[idx];
			int delimIdx = val.indexOf('=');
			if (delimIdx < 0) {
				mapArgs.put(val, null);
			} else if (delimIdx == 0) {
				mapArgs.put("", val.substring(1));
			} else {
				mapArgs.put(
					val.substring(0, delimIdx).trim(),
					val.substring(delimIdx+1)
				);
			}
		}
		
		return mapArgs;
	}

}

(me still looking for a good mainloop for windowed mode gfx apps)

There is such an interface already in development on java.net somewhere (I forget its name). However you must understand that half of the calls we make to native code have to be adapted for Java use anyway - specifically the use of Buffers and arrays - so this simply wouldn’t work for many function calls. Of the remaining function calls, almost all are essentially trivial state changes, the overhead of which is lost in noise. The only real benefits we could really glean are if we had a mechanism to allow us to interface to native libraries that required a lot less tedious boilerplate code for these trivial functions.

[quote]2. Manual control over object allocation.
[/quote]
I agree that having different heaps would be handy, and each heap could be given a different garbage collection strategy depending on its usage. Stack allocation would be handy from escape analysis too, to simply avoid the whole bother of heap allocation in the first place.

One particular scenario I have is that I’d like to manage references to GL objects (textures, shaders, FBOs, etc) in a separate heap - the GC is typically only forcibly triggered when some arbitrary out-of-memory condition occurs in the main heap; however the memory taken up by GL objects is exists in a video card and would ideally be shadowed by some phantom heap that tracked the size of these objects and triggered separate collections independent of the main heap in order to destroy things no longer referenced.

[quote]3. More aggressive, and manual control over the optimization process.
[/quote]
This is one thing I don’t really agree with; it’d be better if the JIT simply got better and better. But having said that, in a closed environment (ie. locally installed) it should be possible to run the JVM with a -Dnoboundscheck parameter which would bring almost all Java speed bottlenecks vs. C on to an equal footing. A bit like turning off asserts.

[quote]4. Structs for Java. OpenMP for Java. Now!
[/quote]
How long have we waited? And I bet they do it wrong.

Cas :slight_smile:

TGT is actually broken on some 15-20 year old motherboards. Fortunately that’s completely irrelevant. :wink:

Huh? The complexity of the animation doesn’t change anything. If vsync is disabled there will be tearing. Yes, it’s really that simple.

[quote=“whome,post:101,topic:32504”]
Without vsync you’ll always have some tearing no matter how you programm it or in what ever language. -> oi oNyx beat me to it.

Since the day we realized that there is no such thing as exactly 10ms in real life, so you always have to define a certain precision. I think I learned that in my 1st physics class.
Like I said, Thread.sleep has a precision of 1ms. So with that precision, 99987757 nanoseconds is 10 milliseconds. So your test does not really demonstrate any failure of Thread.sleep

In any case, small imprecisions like this are not going to cause jerky movement anyway.

Trivial functions would benefit even more from inlining (inserting the machine code between JIT compiled code), those are the functions that are potentially called a lot of times, so in case of inlining the JNI call overhead wouldn’t add up.

I think a “@NoBoundsChecks” infront of the method where the programmer is sure no problem can arise would be the best.

http://maxine.dev.java.net - I need more spare time :slight_smile:

[quote]VM code can be inlined into app code and optimized together
[/quote]

[quote]Optional runtime code features can be turned on and off
by dynamic re-compilation
• no overhead while off
[/quote]

nooooooo, and all those methods are going to do what? throw security exceptions? or just create huge security problems? :o

the most interesting part of this project is this:

[quote]…to develop a research Virtual Machine that is written in the Java™ Programming Language
[/quote]
aka, what was first, egg or chicken? :wink:

That reaction sums it up, how people react to the idea of a game-friendly JVM.

haha, you are right… java virtual machine should be programmed in C or C++? because you need one JVM for windows, one for Linux, one for solaris… etc

I’m afraid that has little to do with game friendliness, I have nothing against a jvm flag. I also have little against memory mapped objects. But what your suggesting, in light of all the other efforts in the jvm, would just make everything a half baked, poor, engineering job.

[quote]a -Dnoboundscheck parameter which would bring almost all Java speed bottlenecks vs. C on to an equal footing. A bit like turning off asserts.
[/quote]
More a bit like disabling bytecode verification, but who cares.

I don’t agree, forced bounds checking are one of core Java features that warrants you don’t get corrupted memory/state and you’re informed about the error very precisely. And no, I don’t believe in debug builds for testing, you get bugs all the time in production, and it can be triggered even after some time of usage.

But aren’t there type of applications(games?) that aren’t so much focused on stability as much as user experience?

I’m suggesting it in addition to the JVM flag. I’f you have code that you think is worth to risk, and think that it does not pose a security risk to omit bounds checking, then you would mark it as such. Old libraries unaware of the pragma, would still run with bounds checking with JVM flag enabled or disabled. During development and testing the JVM flag should be disabled, so exceptions come to light. If you think this is half-baked, then think about that C++ does not have bounds checking at all if the programmer explicitly does not write it, yet it is widely accepted for games (and other kind of) applications. Enterprise applications concerned in safety above all can just disregard both the flag and the pragma, make the IDE/compiler throw a warning if it encounters the pragma, tie its use to security manager, don’t allow dynamic loading of classes with the pragma or force it to still run with bounds checking, or just don’t use the custom JVM in the first place.

It is easily possible to write OpenGL code that breaks the JVM (the graphics driver to be precise, but no Java stacktrace, just a JVM crash), but we are still managing to make working applications. Everything should be done with proper consideration, so don’t add the pragma if you are not sure. And don’t rely on exceptions too much, they are faster than in C++, but are still costly.

Sorry I though you meant best as in replacement:

[quote=“VeaR,post:106,topic:32504”]
But yeah in unison, complimenting each other, sounds pretty sweet.

Yes, with native code it’s normal to have crashing programs. Unfortunatelly we have to use OpenGL for 3D acceleration, so some native code interaction is needed. But it can be pretty well isolated in small area of code (wrapped calls to OpenGL routines) and checked (for example LWJGL tracks OpenGL state and does bounds checks for direct buffers to prevent corruption). Also OpenGL is good API secure-wise because it doesn’t work with pointers but handles. After all JRE does the exact same thing with native OS calls (wrapped and checked).

Speed of exception generation is irrelevant, because it’s generated when there is problem. And there is no visible (probably not any) penalty for try/catch blocks.

Good user experience requires stability.

I can’t see any disadvantage of having a flag for disabling bounds checking, as long as it is a runtime and not a compiletime flag. Make some security restrictions on setting this flag (valid certificate) and forbid it’s usage for applets, so you basically have this ability for locally installed applications (that users trust anyhow). Provide two icons for your game (“unchecked” and “checked”), so your support can advice the user to start the “checked” one after a crash and you have the best of both worlds…

This would create grand effect of crippling Java as many (standalone) apps will disable it and value of Java platform will decrease rapidly. Try look to things at big picture… Luckily this won’t happen, Sun won’t allow this. You’re perfectly OK to modify OpenJDK code if you wish, but it won’t be Java.

I don’t know what’s wrong, either you like Java strengths and use Java for them or use native languages instead.

HotSpot already has range-check elimination:
http://www.forthgo.com/blog/range-check-elimination-optimization/