/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Pavel Savara
* - Initial implementation
*******************************************************************************/
package net.sf.robocode.ui.battle;
import net.sf.robocode.battle.IBattleManager;
import net.sf.robocode.battle.events.BattleEventDispatcher;
import net.sf.robocode.battle.snapshot.RobotSnapshot;
import net.sf.robocode.io.Logger;
import robocode.control.events.*;
import robocode.control.snapshot.IRobotSnapshot;
import robocode.control.snapshot.ITurnSnapshot;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Pavel Savara (original)
*/
public final class AwtBattleAdaptor {
private final IBattleManager battleManager;
private final BattleEventDispatcher battleEventDispatcher = new BattleEventDispatcher();
private final BattleObserver observer;
private final Timer timerTask;
private final AtomicReference<ITurnSnapshot> snapshot;
private final AtomicBoolean isRunning;
private final AtomicBoolean isPaused;
private final AtomicInteger majorEvent;
private final AtomicInteger lastMajorEvent;
private ITurnSnapshot lastSnapshot;
private StringBuilder[] outCache;
public AwtBattleAdaptor(IBattleManager battleManager, int maxFps, boolean skipSameFrames) {
this.battleManager = battleManager;
snapshot = new AtomicReference<ITurnSnapshot>(null);
this.skipSameFrames = skipSameFrames;
timerTask = new Timer(1000 / maxFps, new TimerTask());
isRunning = new AtomicBoolean(false);
isPaused = new AtomicBoolean(false);
majorEvent = new AtomicInteger(0);
lastMajorEvent = new AtomicInteger(0);
observer = new BattleObserver();
battleManager.addListener(observer);
}
protected void finalize() throws Throwable {
try {
timerTask.stop();
battleManager.removeListener(observer);
} finally {
super.finalize();
}
}
public synchronized void addListener(IBattleListener listener) {
battleEventDispatcher.addListener(listener);
}
public synchronized void removeListener(IBattleListener listener) {
battleEventDispatcher.removeListener(listener);
}
public ITurnSnapshot getLastSnapshot() {
return lastSnapshot;
}
// this is always dispatched on AWT thread
private void awtOnTurnEnded(boolean forceRepaint, boolean readoutText) {
try {
ITurnSnapshot current = snapshot.get();
if (current == null) { // !isRunning.get() ||
// paint logo
lastSnapshot = null;
battleEventDispatcher.onTurnEnded(new TurnEndedEvent(null));
} else {
if (lastSnapshot != current || !skipSameFrames || forceRepaint) {
lastSnapshot = current;
IRobotSnapshot[] robots = null;
if (readoutText) {
synchronized (snapshot) {
robots = lastSnapshot.getRobots();
for (int i = 0; i < robots.length; i++) {
RobotSnapshot robot = (RobotSnapshot) robots[i];
final StringBuilder cache = outCache[i];
if (cache.length() > 0) {
robot.setOutputStreamSnapshot(cache.toString());
outCache[i].setLength(0);
}
}
}
}
battleEventDispatcher.onTurnEnded(new TurnEndedEvent(lastSnapshot));
if (readoutText) {
for (IRobotSnapshot robot : robots) {
((RobotSnapshot) robot).setOutputStreamSnapshot(null);
}
}
calculateFPS();
}
}
} catch (Throwable t) {
Logger.logError(t);
}
}
public int getFPS() {
return fps;
}
// FPS (frames per second) calculation
private int fps;
private long measuredFrameCounter;
private long measuredFrameStartTime;
private final boolean skipSameFrames;
private void calculateFPS() {
// Calculate the current frames per second (FPS)
if (measuredFrameCounter++ == 0) {
measuredFrameStartTime = System.nanoTime();
}
long deltaTime = System.nanoTime() - measuredFrameStartTime;
if (deltaTime / 1000000000 >= 1) {
fps = (int) (measuredFrameCounter * 1000000000L / deltaTime);
measuredFrameCounter = 0;
}
}
private class TimerTask implements ActionListener {
public void actionPerformed(ActionEvent e) {
awtOnTurnEnded(false, true);
}
}
// BattleObserver methods are always called by battle thread
// but everything inside invokeLater {} block in on AWT thread
private class BattleObserver extends BattleAdaptor {
@Override
public void onTurnEnded(final TurnEndedEvent event) {
if (lastMajorEvent.get() == majorEvent.get()) {
// snapshot is updated out of order, but always within the same major event
snapshot.set(event.getTurnSnapshot());
}
final IRobotSnapshot[] robots = event.getTurnSnapshot().getRobots();
for (int i = 0; i < robots.length; i++) {
RobotSnapshot robot = (RobotSnapshot) robots[i];
final int r = i;
final String text = robot.getOutputStreamSnapshot();
if (text != null && text.length() != 0) {
robot.setOutputStreamSnapshot(null);
EventQueue.invokeLater(new Runnable() {
public void run() {
synchronized (snapshot) {
outCache[r].append(text);
}
}
});
}
}
if (isPaused.get()) {
EventQueue.invokeLater(new Runnable() {
public void run() {
awtOnTurnEnded(false, true);
}
});
}
}
@Override
public void onRoundStarted(final RoundStartedEvent event) {
if (lastMajorEvent.get() == majorEvent.get()) {
snapshot.set(event.getStartSnapshot());
}
majorEvent.incrementAndGet();
EventQueue.invokeLater(new Runnable() {
public void run() {
awtOnTurnEnded(true, false);
battleEventDispatcher.onRoundStarted(event);
lastMajorEvent.incrementAndGet();
}
});
}
@Override
public void onBattleStarted(final BattleStartedEvent event) {
majorEvent.incrementAndGet();
EventQueue.invokeLater(new Runnable() {
public void run() {
isRunning.set(true);
isPaused.set(false);
synchronized (snapshot) {
outCache = new StringBuilder[event.getRobotsCount()];
for (int i = 0; i < event.getRobotsCount(); i++) {
outCache[i] = new StringBuilder(1024);
}
}
snapshot.set(null);
battleEventDispatcher.onBattleStarted(event);
lastMajorEvent.incrementAndGet();
awtOnTurnEnded(true, false);
timerTask.start();
}
});
}
@Override
public void onBattleFinished(final BattleFinishedEvent event) {
majorEvent.incrementAndGet();
EventQueue.invokeLater(new Runnable() {
public void run() {
isRunning.set(false);
isPaused.set(false);
timerTask.stop();
// flush text cache
awtOnTurnEnded(true, true);
battleEventDispatcher.onBattleFinished(event);
lastMajorEvent.incrementAndGet();
snapshot.set(null);
// paint logo
awtOnTurnEnded(true, true);
}
});
}
@Override
public void onBattleCompleted(final BattleCompletedEvent event) {
majorEvent.incrementAndGet();
EventQueue.invokeLater(new Runnable() {
public void run() {
battleEventDispatcher.onBattleCompleted(event);
lastMajorEvent.incrementAndGet();
awtOnTurnEnded(true, true);
}
});
}
@Override
public void onRoundEnded(final RoundEndedEvent event) {
majorEvent.incrementAndGet();
EventQueue.invokeLater(new Runnable() {
public void run() {
battleEventDispatcher.onRoundEnded(event);
lastMajorEvent.incrementAndGet();
awtOnTurnEnded(true, true);
}
});
}
@Override
public void onBattlePaused(final BattlePausedEvent event) {
EventQueue.invokeLater(new Runnable() {
public void run() {
timerTask.stop();
battleEventDispatcher.onBattlePaused(event);
awtOnTurnEnded(true, true);
isPaused.set(true);
}
});
}
@Override
public void onBattleResumed(final BattleResumedEvent event) {
EventQueue.invokeLater(new Runnable() {
public void run() {
battleEventDispatcher.onBattleResumed(event);
if (isRunning.get()) {
timerTask.start();
isPaused.set(false);
}
}
});
}
@Override
public void onBattleMessage(final BattleMessageEvent event) {
EventQueue.invokeLater(new Runnable() {
public void run() {
battleEventDispatcher.onBattleMessage(event);
}
});
}
@Override
public void onBattleError(final BattleErrorEvent event) {
EventQueue.invokeLater(new Runnable() {
public void run() {
battleEventDispatcher.onBattleError(event);
}
});
}
}
}