/**
* Copyright 2011 The ForPlay Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package forplay.android;
import java.util.concurrent.atomic.AtomicBoolean;
import android.graphics.Canvas;
import android.util.Log;
import android.view.View;
public abstract class GameLoop implements Runnable {
private static final int MAX_DELTA = 100;
private AtomicBoolean running = new AtomicBoolean();
private long timeOffset = System.currentTimeMillis();
private int updateRate;
private int accum;
private int lastTime;
private final View view;
private int lastStats;
private int updateCount;
private int updateTime;
private int paintCount;
private int paintQueueStart;
private int runQueueStart;
private int paintTime, paintLagTime;
private int runCount;
private int runQueueTime;
private float paintAlpha;
public GameLoop(View view) {
this.view = view;
}
public void start() {
lastStats = time();
if (!running.get()) {
Log.i("forplay", "Starting game loop");
this.updateRate = AndroidPlatform.instance.game.updateRate();
running.set(true);
runQueueStart = lastTime = time();
view.post(this);
}
}
public void end() {
Log.i("forplay", "Halting game loop");
running.set(false);
}
public void run() {
// The thread can be stopped between runs.
if (!running.get())
return;
runCount++;
int now = time();
float delta = now - lastTime;
if (delta > MAX_DELTA)
delta = MAX_DELTA;
lastTime = now;
runQueueTime += now - runQueueStart;
if (now - lastStats > 10000) {
if (paintCount > 0)
Log.i("forplay", "Stats: paints = " + paintCount + " " + ((float) paintTime / paintCount)
+ "ms (" + ((float) paintCount / (now - lastStats) * 1000) + "/s) lag = "
+ ((float) paintLagTime / paintCount) + "ms");
if (updateCount > 0)
Log.i("forplay", "Stats: updates = " + updateCount + " "
+ ((float) updateTime / updateCount) + "ms ("
+ ((float) updateCount / (now - lastStats) * 1000) + "/s)");
Log.i("forplay", "Stats: runs = " + runCount + " run lag = "
+ ((float) runQueueTime / runCount) + "ms (" + runCount / ((float) now - lastStats)
* 1000 + "/s)");
paintCount = updateCount = runCount = 0;
paintTime = paintLagTime = updateTime = 0;
lastStats = now;
}
boolean isPaintDirty = false;
if (updateRate == 0) {
AndroidPlatform.instance.update(delta);
accum = 0;
isPaintDirty = true;
} else {
accum += delta;
while (accum >= updateRate) {
updateCount++;
double start = time();
AndroidPlatform.instance.update(updateRate);
updateTime += time() - start;
accum -= updateRate;
isPaintDirty = true;
}
}
if (isPaintDirty) {
paintAlpha = (updateRate == 0) ? 0 : accum / updateRate;
paintQueueStart = time();
paint();
}
// A stop request can occur during this run, in which case we must not
// try to schedule a new loop.
if (running.get()) {
runQueueStart = time();
// Did we spend so much time in this method that we're due for another
// update?
if (updateRate > 0 && runQueueStart - now > updateRate)
view.post(this);
else
view.postDelayed(this, 1);
}
}
private int time() {
// System.nanoTime() would be better here, but it's busted on the HTC EVO
// 2.3 update. Instead we use an offset from a known time to keep it within
// int range.
return (int) (System.currentTimeMillis() - timeOffset);
}
protected abstract void paint();
void paint(Canvas c) {
double start = time();
paintLagTime += start - paintQueueStart;
AndroidPlatform.instance.draw(c, paintAlpha);
paintTime += time() - start;
paintCount++;
}
}