If you are wondering how you can easily schedule time for AI, collision detection, animation etc. with threads, here is a copy-paste from my games engine thread code.
This is the main thread loop for the engine (not the renderer)
while (!stopped && !this.isInterrupted()) {
newTime = System.currentTimeMillis();
// Integrate mouse and keybaord effects on player position
engine.player_camera.update();
// Update active entity AI
updateEntityAI();
// Execute geometry updates for next frame
// (executeNext obtains geomety lock if renderer has finished buffer swap)
executeNext();
// Did we have left over time to give back to the renderer?
// TODO: for J15, use nano
long delta = newTime - System.currentTimeMillis();
if (delta < desired_time) {
// Give unused time back to renderer or awt threads
while (System.currentTimeMillis() < newTime + (desired_time - delta)) {
Thread.yield();
}
} else {
// Renderer thread or other CPU process is starving us
// (Share the CPU as best we can)
Thread.yield();
}
// Figure out how long this all took
// TODO: use nana for J15
long elapsed = System.currentTimeMillis() - newTime;
currentFPS = (int) (1000 / elapsed);
// Target elapsed time should be 15 ms (aprx 62 FPS)
// Adjust lagMultipler accordingly for the next
// engine frame
engine.setLagMultiplier(currentFPS / optimal_rate);
}
The call to executeNext() is important because that is where a synchronize block happens to pass reference geometry to the renderer, after all collision detection, animation, etc has happend. excecuteNext is a command pattern executer that determines which objects are invalid based on how often they wanted to be woke up. Each active entity can operate at upto 62 game frames per second (they get called back if it is time for them to work on their movement or their geometry…or whatever they do).
This is my main rendering method (not the loop…see below for that). The syncrhonzation block ensures that the engine can not pass a new reference to geometry (inside engine.world) while actual drawing is taking place.
public void display(GLDrawable drawable) {
if (!engine.engine_running)
return;
// Let the engine know we are about to draw a new frame.
engine.newFrame();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
// Per frame things that are non-visual and non-thread dependent can be done within this call
nonVisualPrep();
// Prevent geometry from being updated while we are doing
// the actual rendering
synchronized (engine.frame_sync) {
// Take ownership
engine.setActiveThread(VoraxEngine.RENDERER);
// Clear the draw later que every frame
clearDrawLaterQue();
// Need orientation help?
if (VoraxEngine.DEBUG)
drawGrid(gl);
// apply the camera
engine.player_camera.apply(gl);
// Render the world
engine.world.render(gl, matStack);
// Draw items in the draw later que
for (int i = 0; i < drawLaterQue.size(); i++) {
((GeoObject) drawLaterQue.elementAt(i)).drawObject(gl);
}
// Show fps?
if (engine.show_fps)
drawFPSText(gl);
// Show engine fps?
if (engine.show_fps)
drawEngineFPSText(gl);
// Geometry stats?
if (engine.tris_stats)
drawTrisCountText(gl);
}
}
This is the main loop that calls display() above:
while (engine_running) {
// Render...
renderer.display(drawable);
// swap the buffers
drawable.swapBuffers();
// Don't overdraw and waste CPU, give it to the
// engine or awt threads
if (gl_fps > config.refresh_rate || engineThread.currentFPS < engineThread.optimal_refresh)
Thread.yield();
}
Two things to note here are that the buffer swap is not within a synchronization block. This is safe because only the renderer writes to the context. By having it unsynchronized, the engine can start working on the next frame as soon as the renderer has finished the last gl call.
The other is the yield within this loop. If the renderer thread has already reached the refresh rate, it will not draw the next frame without yielding some cpu time to the engine or awt threads. There is no point as it only wastes cpu time wich would be better spent on preparing the data for the next frame.
That’s the high lights. The engine thread runs at a constant 62 FPS and the renderer thread goes from worst case of 54 fps (with 28K geometry being rendered) upto 1800 fps when looking at nothing (I said 400 before… I found some bad code that was causing a chained recalc of bounding boxes down the SG path…big difference with it removed)
That code isn’t really complicated. As you can see I can test to see if the engine thread is actually benefiting anything at all by simply calling executeNext() inside the main display() method. When I do, I get this:
(unknown engine FPS)
Worse case scenario is now 42 FPS for renderer. So as it stands, the engine thread is gaining me 12 FPS under the most highest geometry conditions. 12 FPS is a pretty substantial gain to me because I am already well below the target FPS of 85 for the renderer thread.
As I add more geometry, this will get even worse. I willl balance it out by using a stripifier on the data and vertex lists were possible (some for static geometry now). So, if I double my geometry rendering speed with a stripifier, I will hit around 100 fps. Without the engine thread I would only be hitting around 84 (best case), and I wouldn’t be able to add any more geometry. The AI is also going to get alot more complicated, but will have an almost unoticable effect on rendering FPS becuase it happens in the engine thread. With the performance gains from the threading I will be able to add approx, 15% more geometry to the worst case scenario and still achieve my desired FPS.
edit: I almost forgot the other point of relevance to this thread…the engine FPS is constant and seperate from the rendering, so all players of the game (despite hardware, or network if I add it) will have the same experience.
…are the advantages becoming clearer yet?