package greenfoot.core;
import greenfoot.Actor;
import greenfoot.ActorVisitor;
import greenfoot.World;
import greenfoot.WorldVisitor;
import greenfoot.event.SimulationListener;
import greenfoot.event.SimulationListener.AsyncEvent;
import greenfoot.event.SimulationListener.SyncEvent;
import greenfoot.event.WorldEvent;
import greenfoot.event.WorldListener;
import greenfoot.util.HDTimer;
import threadchecker.OnThread;
import threadchecker.Tag;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.swing.event.EventListenerList;
| The main class of the simulation. It drives the simulation and calls act()
| on the objects in the world and then paints them.
|
| @author Poul Henriksen
|
@OnThread(Tag.Simulation)
public class Simulation
extends Thread
implements WorldListener{
@OnThread(Tag.Any)
private WorldHandler worldHandler;
| Whether the simulation is (to be) paused
|
@OnThread(value = Tag.Any, requireSynchronized = true)
private boolean paused;
| Whether the simulation is enabled (world installed)
|
private volatile boolean enabled;
| Whether to run one loop when paused
|
@OnThread(value = Tag.Any, requireSynchronized = true)
private boolean runOnce;
| Tasks that are queued to run on the simulation thread
|
@OnThread(value = Tag.Any, requireSynchronized = true)
private Queue<SimulationRunnable> queuedTasks = new LinkedList<>();
@OnThread(Tag.Any)
private final List<SimulationListener> listenerList = new ArrayList<>();
@OnThread(value = Tag.Any, requireSynchronized = true)
private static Simulation instance;
| for timing the animation
|
public static final int MAX_SIMULATION_SPEED = 100;
@OnThread(value = Tag.Any, requireSynchronized = true)
private int speed;
private long lastDelayTime;
private long delay;
| Lock to synchronize access to the two fields: delaying and interruptDelay
|
@OnThread(Tag.Any)
private Object interruptLock = new Object();
| Whether we are currently delaying between act-loops.
|
@OnThread(Tag.Any)
private boolean delaying;
| Whether a delay between act-loops should be interrupted.
|
@OnThread(Tag.Any)
private boolean interruptDelay;
| Used to figure out when we are transitioning from running to paused state and vice versa.
| Only modify this from the simulation thread.
|
private boolean isRunning = false;
| flag to indicate that we want to abort the simulation and never start it again.
|
private volatile boolean abort;
| Create new simulation. Leaves the simulation in paused state
|
@OnThread(Tag.Any)
private Simulation()
{
this.setName("SimulationThread");
setPriority(Thread.MIN_PRIORITY);
paused = true;
speed = 50;
delay = calculateDelay(speed);
HDTimer.init();
}
| Initialize the (singleton) simulation instance.
| The simulation thread will not actually be started until the WorldHandler
| is attached.
|
@OnThread(Tag.Any)
public static synchronized void initialize()
{
instance = new Simulation();
}
| Returns the simulation if it is initialised. If not, it will return null.
|
@OnThread(Tag.Any)
public static synchronized Simulation getInstance()
{
return instance;
}
| Attach this simulation to the world handler (and vice versa).
|
@OnThread(Tag.Any)
public void attachWorldHandler(WorldHandler worldHandler)
{
this.worldHandler = worldHandler;
worldHandler.addWorldListener(this);
addSimulationListener(worldHandler);
start();
}
| Runs the simulation from the current state.
|
@Override
@OnThread(value = Tag.Simulation, ignoreParent = true)
public void run()
{
| It is important this redirects to another method.
| The debugger sets a breakpoint on the first line of this method, and if
| that is a loop (as is the case for the first line of runContent at the time of writing)
| then it hits the breakpoint every time. By putting it all in a separate
| method, we avoid that happening:
|
runContent();
}
private void runContent()
{
while (!abort){
try {
maybePause();
if (worldHandler.hasWorld()) {
runOneLoop(worldHandler.getWorld());
}
boolean currentlyPaused;
synchronized (this)
{
currentlyPaused = paused;
}
if (!currentlyPaused)
{
delay();
}
}
catch (ActInterruptedException e) {
}
catch (InterruptedException e) {
}
catch (Throwable t) {
synchronized (this) {
paused = true;
}
t.printStackTrace();
WorldHandler.getInstance().notifyStoppedWithError();
paintRemote(true);
}
}
synchronized (this) {
if (isRunning) {
World world = worldHandler.getWorld();
if (world != null) {
worldStopped(world);
}
isRunning = false;
}
}
}
public static interface SimulationRunnable
{
@OnThread(Tag.Simulation)
public void run();
}
| Schedule some task to run on the simulation thread. The task will be run with the
| world write lock held.
|
@OnThread(Tag.Any)
public synchronized void runLater(SimulationRunnable r)
{
queuedTasks.add(r);
if (paused || ! enabled) {
notify();
}
}
public final static String PAUSED = "simulationWait";
| A special method recognised by the debugger as indicating that the simulation
| is pausing.
|
private void simulationWait() throws InterruptedException
{
paintRemote(true);
this.wait();
}
public final static String WORLD_STARTED = "worldStarted";
private static void worldStarted(World world)
{
world.started();
}
public final static String WORLD_STOPPED = "worldStopped";
private static void worldStopped(World world)
{
world.stopped();
}
| Block if the simulation is paused. This will block until the simulation
| is resumed (is both enabled and unpaused). It should only be called on the
| simulation thread.
|
| @throws InterruptedException If it couldn't acquire the world lock when
| signalling started()/stopped() to the world.
|
private void maybePause()
throws InterruptedException
{
while (!abort){
runQueuedTasks();
World world;
boolean checkStop;
synchronized (this) {
checkStop = (paused || !enabled) && isRunning;
world = worldHandler.getWorld();
if (checkStop) {
isRunning = false;
synchronized (interruptLock) {
interruptDelay = false;
}
}
else if (isRunning) {
return;
}
}
if (checkStop) {
try {
signalStopping(world);
}
catch (InterruptedException ie) {
continue;
}
synchronized (this) {
runOnce = false;
if (! paused) {
isRunning = enabled;
}
}
}
boolean doResumeRunning;
synchronized (this) {
doResumeRunning = !paused && enabled && !abort && !isRunning;
if (! isRunning && ! doResumeRunning && ! runOnce) {
if (enabled) {
fireSimulationEventAsync(AsyncEvent.STOPPED);
}
if (worldHandler != null) {
worldHandler.repaint();
}
if (! queuedTasks.isEmpty()) {
continue;
}
System.gc();
try {
simulationWait();
lastDelayTime = System.nanoTime();
}
catch (InterruptedException e1) {
}
continue;
}
}
if (doResumeRunning) {
resumeRunning();
}
synchronized (this) {
if (runOnce || isRunning) {
runOnce = false;
return;
}
}
}
runQueuedTasks();
}
| Send a started event and notify the world that it is now running.
|
| @throws InterruptedException
|
private void resumeRunning() throws InterruptedException
{
isRunning = true;
lastDelayTime = System.nanoTime();
fireSimulationEventSync(SyncEvent.STARTED);
World world = worldHandler.getWorld();
if (world != null) {
try {
worldStarted(world);
}
catch (Throwable t) {
isRunning = false;
synchronized (interruptLock) {
Thread.interrupted();
interruptDelay = false;
}
setPaused(true);
t.printStackTrace();
return;
}
}
}
| Tell the world that the simulation is stopping. The world might resume
| the simulation when this happens.
|
private void signalStopping(World world) throws InterruptedException
{
if (world != null) {
try {
worldStopped(world);
}
catch (ActInterruptedException aie) {
synchronized (this) {
paused = true;
}
throw aie;
}
catch (Throwable t) {
synchronized (this) {
paused = true;
}
t.printStackTrace();
}
}
}
| This must match the method name below!
|
public static String RUN_QUEUED_TASKS = "runQueuedTasks";
| Run all tasks that have been schedule to run on the simulation thread.
| Of course, this should only be called from the simulation thread...
| (and from an unsynchronized context).
|
private void runQueuedTasks()
{
SimulationRunnable r;
synchronized (this) {
r = queuedTasks.poll();
}
while (r != null){
try {
fireSimulationEventSync(SyncEvent.QUEUED_TASK_BEGIN);
try {
r.run();
}
catch (Throwable t) {
t.printStackTrace();
}
fireSimulationEventSync(SyncEvent.QUEUED_TASK_END);
}
finally {
}
synchronized (this) {
r = queuedTasks.poll();
}
}
}
| Performs one step in the simulation. Calls act() on all actors.
| May propagate a runtime exception or error from user code.
|
| @throws ActInterruptedException if an act() call was interrupted.
|
private void runOneLoop(World world)
{
fireSimulationEventSync(SyncEvent.NEW_ACT_ROUND);
ActInterruptedException interruptedException = null;
List<? extends Actor> objects = null;
try
{
actWorld(world);
if (world != worldHandler.getWorld())
{
paintRemote(false);
return;
}
}
catch (ActInterruptedException e)
{
interruptedException = e;
}
objects = new ArrayList<Actor>(WorldVisitor.getObjectsListInActOrder(world));
for (Actor actor : objects)
{
if (!enabled)
{
return;
}
if (ActorVisitor.getWorld(actor) != null)
{
try
{
actActor(actor);
if (world != worldHandler.getWorld())
{
return;
}
}
catch (ActInterruptedException e)
{
if (interruptedException == null)
{
interruptedException = e;
}
}
}
}
worldHandler.getKeyboardManager().clearLatchedKeys();
if (interruptedException != null) {
throw interruptedException;
}
repaintIfNeeded();
fireSimulationEventSync(SyncEvent.END_ACT_ROUND);
}
public static final String ACT_ACTOR = "actActor";
private static void actActor(Actor actor)
{
actor.act();
}
public static final String ACT_WORLD = "actWorld";
private static void actWorld(World world)
{
world.act();
}
public static final String NEW_INSTANCE = "newInstance";
public static Object newInstance(Constructor<?> constructor)
throws InvocationTargetException, IllegalArgumentException, InstantiationException, IllegalAccessException
{
return constructor.newInstance((Object[])null);
}
| Repaints the world if needed to obtain the desired frame rate.
|
private void repaintIfNeeded()
{
paintRemote(false);
}
protected void paintRemote(boolean forcePaint)
{
WorldHandler.getInstance().paint(forcePaint);
}
| Debug output to print the rate at which updates are performed
| (acts/second).
|
|
|
|private void printUpdateRate(long currentTime)
|
|{} //updates++;
|
|long timeSinceUpdate = currentTime - lastUpdate;
|
|if (timeSinceUpdate > 3000000000L) {} lastUpdate = currentTime;
|
|//updates = 0;
|
|}
|
|}
| Run one step of the simulation. Each actor in the world acts once.
|
@OnThread(Tag.Any)
public synchronized void runOnce()
{
if (enabled) {
synchronized (interruptLock) {
interruptDelay = false;
}
}
runOnce = true;
notifyAll();
}
| Toggles the running/paused state of the simulation.
|
@OnThread(Tag.Any)
public synchronized void togglePaused()
{
setPaused(!paused);
}
| Pauses and unpauses the simulation.
|
@OnThread(Tag.Any)
public synchronized void setPaused(boolean b)
{
if (paused == b)
{
return;
}
paused = b;
if (enabled)
{
if (!paused)
{
synchronized (interruptLock)
{
interruptDelay = false;
}
}
notifyAll();
if (paused)
{
interruptDelay();
}
}
}
| Interrupt if we are currently delaying between act-loops or the user is
| using the Greenfoot.delay() method. This will basically jump to the next
| act-loop as fast as possible while still executing the rest of actors in
| the current loop. Used by setPaused() and setSpeed() to interrupt current
| delays.
|
@OnThread(Tag.Any)
private void interruptDelay()
{
synchronized (interruptLock) {
if (delaying) {
interrupt();
}
else {
interruptDelay = true;
}
}
}
| Enable or disable the simulation.
|
@OnThread(Tag.Any)
public synchronized void setEnabled(boolean b)
{
if (b == enabled) {
return;
}
enabled = b;
if (b) {
notifyAll();
if (paused) {
fireSimulationEventAsync(AsyncEvent.STOPPED);
}
}
else {
interruptDelay();
if (! paused) {
paused = true;
}
else {
synchronized (interruptLock) {
interruptDelay = false;
}
}
fireSimulationEventAsync(AsyncEvent.DISABLED);
}
}
@OnThread(Tag.Simulation)
private void fireSimulationEventSync(SyncEvent event)
{
synchronized (listenerList) {
for (SimulationListener listener : listenerList)
{
listener.simulationChangedSync(event);
}
}
}
@OnThread(Tag.Any)
private void fireSimulationEventAsync(AsyncEvent event)
{
synchronized (listenerList) {
for (SimulationListener listener : listenerList)
{
listener.simulationChangedAsync(event);
}
}
}
| Add a simulationListener to listen for changes.
|
| @param l
| Listener to add
|
@OnThread(Tag.Any)
public void addSimulationListener(SimulationListener l)
{
synchronized (listenerList) {
listenerList.add(0, l);
}
}
| Set the speed of the simulation.
|
| @param newSpeed
| The speed in the range (0..100)
|
@OnThread(Tag.Any)
public void setSpeed(int newSpeed)
{
if (newSpeed < 0)
{
newSpeed = 0;
}
else if (newSpeed > MAX_SIMULATION_SPEED)
{
newSpeed = MAX_SIMULATION_SPEED;
}
boolean speedChanged;
synchronized (this)
{
speedChanged = this.speed != newSpeed;
if (speedChanged)
{
this.speed = newSpeed;
this.delay = calculateDelay(newSpeed);
if (!paused)
{
synchronized (interruptLock)
{
if (delaying)
{
interrupt();
}
}
}
}
}
if (speedChanged)
{
fireSimulationEventAsync(AsyncEvent.CHANGED_SPEED);
}
}
| Returns the delay as a function of the speed.
|
| @return The delay in nanoseconds.
|
@OnThread(Tag.Any)
private static long calculateDelay(int curSpeed)
{
long rawDelay = MAX_SIMULATION_SPEED - curSpeed;
long min = 30 * 1000L;
long max = 10000 * 1000L * 1000L;
double a = Math.pow(max / (double) min, 1D / (MAX_SIMULATION_SPEED - 1));
long calcDelay = 0;
if (rawDelay > 0) {
calcDelay = (long) (Math.pow(a, rawDelay - 1) * min);
}
return calcDelay;
}
| Get the current simulation speed.
|
| @return The speed in the range (1..100)
|
@OnThread(Tag.Any)
public synchronized int getSpeed()
{
return speed;
}
| Sleep an amount of time according to the current speed setting for this
| simulation. This will wait without considering previous waits, as opposed
| to delay(). It should be called only from the simulation thread, in an
| unsynchronized context.
|
@OnThread(Tag.Simulation)
public void sleep(int numCycles)
{
synchronized (this)
{
if (paused && isRunning && !runOnce)
{
return;
}
if (! enabled)
{
return;
}
synchronized (interruptLock)
{
if (interruptDelay)
{
return;
}
delaying = true;
}
}
fireSimulationEventSync(SyncEvent.DELAY_LOOP_ENTERED);
try
{
worldHandler.repaint();
for (int i = 0; i < numCycles; i++)
{
HDTimer.sleep(delay);
}
}
catch (InterruptedException e)
{
}
finally
{
synchronized (interruptLock)
{
Thread.interrupted();
interruptDelay = false;
delaying = false;
}
fireSimulationEventSync(SyncEvent.DELAY_LOOP_COMPLETED);
}
}
| Cause a delay (wait) according to the current speed setting for this
| simulation. It will take the time spend in this simulation loop into
| consideration and only pause the remaining time.
|
| <p>This method is used for controlling the speed of the animation.
|
| <p>The world lock should not be held when this method is called, so
| that repaints can occur.
|
private void delay()
{
long currentTime = System.nanoTime();
long timeElapsed = currentTime - lastDelayTime;
long actualDelay = Math.max(delay - timeElapsed, 0L);
synchronized (this)
{
synchronized (interruptLock)
{
if (interruptDelay)
{
interruptDelay = false;
if (paused || abort)
{
lastDelayTime = currentTime;
return;
}
}
delaying = true;
}
}
fireSimulationEventSync(SyncEvent.DELAY_LOOP_ENTERED);
while (actualDelay > 0)
{
try
{
HDTimer.sleep(actualDelay);
}
catch (InterruptedException ie)
{
synchronized (this)
{
if (!enabled || paused || abort)
{
break;
}
}
}
currentTime = System.nanoTime();
timeElapsed = currentTime - lastDelayTime;
actualDelay = delay - timeElapsed;
}
lastDelayTime = currentTime;
synchronized (interruptLock)
{
Thread.interrupted();
interruptDelay = false;
delaying = false;
}
fireSimulationEventSync(SyncEvent.DELAY_LOOP_COMPLETED);
}
| Abort the simulation. It abruptly stops what is running and ends the
| simulation thread, and it is not possible to start it again.
|
@OnThread(Tag.Any)
public void abort()
{
abort = true;
setEnabled(false);
}
| A new world was created - we're ready to go. Enable the simulation
| functions.
|
@Override
public void worldCreated(WorldEvent e)
{
setEnabled(true);
}
| The world was removed - disable the simulation functions.
|
@Override
public void worldRemoved(WorldEvent e)
{
setEnabled(false);
}
}
top,
use,
map,
class Simulation
. Simulation
. initialize
. getInstance
. attachWorldHandler
. run
. runContent
top,
use,
map,
interface SimulationRunnable
. run
. runLater
. simulationWait
. worldStarted
. worldStopped
. maybePause
. resumeRunning
. signalStopping
. runQueuedTasks
. runOneLoop
. actActor
. actWorld
. newInstance
. repaintIfNeeded
. paintRemote
. runOnce
. togglePaused
. setPaused
. interruptDelay
. setEnabled
. fireSimulationEventSync
. fireSimulationEventAsync
. addSimulationListener
. setSpeed
. calculateDelay
. getSpeed
. sleep
. delay
. abort
. worldCreated
. worldRemoved
1159 neLoCode
+ 91 LoComm