Refactoring des Beendens der ZM

This commit is contained in:
ngb
2022-07-26 18:14:23 +02:00
parent a228b21c84
commit f0e4cd6c80

View File

@@ -130,6 +130,8 @@ public class Zeichenmaschine extends Constants {
*/ */
private boolean running = false; private boolean running = false;
private boolean terminateImediately = false;
/** /**
* Ob die ZM nach dem nächsten Frame pausiert werden soll. * Ob die ZM nach dem nächsten Frame pausiert werden soll.
*/ */
@@ -165,7 +167,7 @@ public class Zeichenmaschine extends Constants {
* Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll, * Gibt an, ob nach Ende des Hauptthreads das Programm beendet werden soll,
* oder das Zeichenfenster weiter geöffnet bleibt. * oder das Zeichenfenster weiter geöffnet bleibt.
*/ */
private boolean quitAfterTeardown = false; private boolean quitAfterShutdown = false;
// Mauszeiger // Mauszeiger
/** /**
@@ -311,29 +313,20 @@ public class Zeichenmaschine extends Constants {
canvas.addKeyListener(inputListener); canvas.addKeyListener(inputListener);
// Programm beenden, wenn Fenster geschlossen wird // Programm beenden, wenn Fenster geschlossen wird
// TODO: (ngb) Der Listener hat zu viel FUnktionalität -> nach quit() / exit() auslagern
frame.addWindowListener(new WindowAdapter() { frame.addWindowListener(new WindowAdapter() {
@Override @Override
public void windowClosing( WindowEvent e ) { public void windowClosing( WindowEvent e ) {
if( running ) { if( isTerminated() ) {
running = false; quit(true);
mainThread.interrupt(); } else {
//teardown(); exitNow();
//cleanup();
} }
// Give the app a minimum amount of time to shut down
// then kill it.
while( state != Options.AppState.TERMINATED ) {
Thread.yield();
if( Thread.interrupted() ) {
break;
}
}
// Quit
quit(true);
} }
}); });
// Fenster anzeigen // Fenster anzeigen
frame.centerFrame();
frame.setVisible(true); frame.setVisible(true);
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden. // Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
@@ -493,7 +486,11 @@ public class Zeichenmaschine extends Constants {
return state == Options.AppState.PAUSED; return state == Options.AppState.PAUSED;
} }
public final boolean isStopped() { public final boolean isTerminated() {
return state == Options.AppState.TERMINATED;
}
public final boolean isTerminating() {
return state == Options.AppState.STOPPED || state == Options.AppState.TERMINATED; return state == Options.AppState.STOPPED || state == Options.AppState.TERMINATED;
} }
@@ -502,9 +499,13 @@ public class Zeichenmaschine extends Constants {
* <p> * <p>
* Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine * Nachdem der aktuelle Frame gezeichnet wurde wechselt die Zeichenmaschine
* in den Zustand {@link Options.AppState#STOPPED} und ruft * in den Zustand {@link Options.AppState#STOPPED} und ruft
* {@link #teardown()} auf. Nachdem {@code teardown()} ausgeführt wurde * {@link #shutdown()} auf. Nachdem {@code teardown()} ausgeführt wurde
* wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das * wechselt der Zustand zu {@link Options.AppState#TERMINATED}. Das
* Zeichenfenster bleibt weiter geöffnet. * Zeichenfenster bleibt weiter geöffnet.
* <p>
* Die Zeichenmaschine reagiert in diesem Zustand weiter auf Eingaben,
* allerdings muss die Zeichnung nun manuell mit {@link #redraw()}
* aktualisiert werden.
*/ */
public final void stop() { public final void stop() {
running = false; running = false;
@@ -515,7 +516,7 @@ public class Zeichenmaschine extends Constants {
* <p> * <p>
* Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und * Wird nach dem {@link #stop() Stopp} der Zeichenmaschine aufgerufen und
* verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die * verbleibende Threads, Tasks, etc. zu stoppen und aufzuräumen. Die
* Äquivalente Methode für Unterklassen ist {@link #teardown()}, die direkt * Äquivalente Methode für Unterklassen ist {@link #shutdown()}, die direkt
* vor {@code cleanup()} aufgerufen wird. * vor {@code cleanup()} aufgerufen wird.
*/ */
private void cleanup() { private void cleanup() {
@@ -538,7 +539,18 @@ public class Zeichenmaschine extends Constants {
public final void exit() { public final void exit() {
if( running ) { if( running ) {
running = false; running = false;
this.quitAfterTeardown = true; quitAfterShutdown = true;
} else {
quit(true);
}
}
public final void exitNow() {
if( running ) {
running = false;
terminateImediately = true;
quitAfterShutdown = true;
mainThread.interrupt();
} else { } else {
quit(true); quit(true);
} }
@@ -546,6 +558,10 @@ public class Zeichenmaschine extends Constants {
/** /**
* Beendet das Programm vollständig. * Beendet das Programm vollständig.
* <p>
* Enspricht dem Aufruf {@code quit(true)}.
*
* @see #quit(boolean)
*/ */
public final void quit() { public final void quit() {
//quit(!IN_BLUEJ); //quit(!IN_BLUEJ);
@@ -555,6 +571,14 @@ public class Zeichenmaschine extends Constants {
/** /**
* Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird * Beendet das Programm. Falls {@code exit} gleich {@code true} ist, wird
* die komplette VM beendet. * die komplette VM beendet.
* <p>
* Die Methode sorgt nicht für ein ordnungsgemäßes herunterfahren und
* freigeben aller Ressourcen, da die Zeichenmaschine gegebenenfalls
* geöffnet bleiben und weitere Aufgaben erfüllen soll. Aufrufende Methoden
* sollten dies berücksichtigen.
* <p>
* Soll das Programm vollständig beendet werden, ist es ratsamer
* {@link #exit()} zu verwenden.
* *
* @param exit Ob die VM beendet werden soll. * @param exit Ob die VM beendet werden soll.
* @see System#exit(int) * @see System#exit(int)
@@ -846,8 +870,8 @@ public class Zeichenmaschine extends Constants {
} }
long timer = 0L; long timer = 0L;
/* if( /*updateState == Options.AppState.DRAWING*/
if( updateState == Options.AppState.DRAWING ) { isTerminating() ) {
// Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen // Falls gerade draw() ausgeführt wird, zeigen wir den aktuellen
// Stand der Zeichnung auf der Leinwand an. Die Zeit für das // Stand der Zeichnung auf der Leinwand an. Die Zeit für das
// Rendern wird gemessen und von der Wartezeit abgezogen. // Rendern wird gemessen und von der Wartezeit abgezogen.
@@ -855,7 +879,6 @@ public class Zeichenmaschine extends Constants {
canvas.render(); canvas.render();
timer = System.nanoTime() - timer; timer = System.nanoTime() - timer;
} }
*/
Options.AppState oldState = updateState; Options.AppState oldState = updateState;
try { try {
@@ -1034,7 +1057,7 @@ public class Zeichenmaschine extends Constants {
* Spiels oder der Abspann einer Animation angezeigt werden, oder mit * Spiels oder der Abspann einer Animation angezeigt werden, oder mit
* {@link #saveImage()} die erstellte Zeichnung abgespeichert werden. * {@link #saveImage()} die erstellte Zeichnung abgespeichert werden.
*/ */
public void teardown() { public void shutdown() {
// Intentionally left blank // Intentionally left blank
} }
@@ -1076,33 +1099,33 @@ public class Zeichenmaschine extends Constants {
eventQueue.add(evt); eventQueue.add(evt);
} }
if( isPaused() || isStopped() ) { if( isPaused() || isTerminated() ) {
dispatchEvents(); dispatchEvents();
} }
} }
private void dispatchEvents() { private void dispatchEvents() {
//synchronized( eventQueue ) { //synchronized( eventQueue ) {
while( !eventQueue.isEmpty() ) { while( !eventQueue.isEmpty() ) {
InputEvent evt = eventQueue.poll(); InputEvent evt = eventQueue.poll();
switch( evt.getID() ) { switch( evt.getID() ) {
case KeyEvent.KEY_TYPED: case KeyEvent.KEY_TYPED:
case KeyEvent.KEY_PRESSED: case KeyEvent.KEY_PRESSED:
case KeyEvent.KEY_RELEASED: case KeyEvent.KEY_RELEASED:
handleKeyEvent((KeyEvent) evt); handleKeyEvent((KeyEvent) evt);
break; break;
case MouseEvent.MOUSE_CLICKED: case MouseEvent.MOUSE_CLICKED:
case MouseEvent.MOUSE_PRESSED: case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED: case MouseEvent.MOUSE_RELEASED:
case MouseEvent.MOUSE_MOVED: case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_DRAGGED: case MouseEvent.MOUSE_DRAGGED:
case MouseEvent.MOUSE_WHEEL: case MouseEvent.MOUSE_WHEEL:
handleMouseEvent((MouseEvent) evt); handleMouseEvent((MouseEvent) evt);
break; break;
}
} }
}
//} //}
} }
@@ -1289,48 +1312,51 @@ public class Zeichenmaschine extends Constants {
public final void run() { public final void run() {
// Wait for full initialization before start // Wait for full initialization before start
while( state != Options.AppState.INITIALIZED ) { while( state != Options.AppState.INITIALIZED ) {
delay(1); Thread.yield();
} }
// ThreadExecutor for the update/draw Thread // ThreadExecutor for the update/draw Thread
final UpdateThreadExecutor updateThreadExecutor = new UpdateThreadExecutor(); final UpdateThreadExecutor updateThreadExecutor = new UpdateThreadExecutor();
// start of thread in ms // Start des Thread in ms
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
// current time in ns // Aktuelle Zeit in ns
long beforeTime = System.nanoTime(); long beforeTime;
long updateBeforeTime = System.nanoTime(); long updateBeforeTime = System.nanoTime();
// store for deltas // Speicher für Änderung
long overslept = 0L; long overslept = 0L;
// internal counters for tick and runtime // Interne Zähler für tick und runtime
int _tick = 0; int _tick = 0;
long _runtime = 0; long _runtime = 0;
// public counters for access by subclasses // Öffentliche Zähler für Unterklassen
tick = 0; tick = 0;
runtime = 0; runtime = 0;
// call setup of subclass and wait // setup() der Unterklasse aufrufen
setup(); setup();
// Alles startklar ...
state = Options.AppState.RUNNING; state = Options.AppState.RUNNING;
while( running ) { while( running ) {
// delta in seconds // Aktuelle Zeit in ns merken
beforeTime = System.nanoTime(); beforeTime = System.nanoTime();
// Mausposition einmal pro Frame merken
saveMousePosition(mouseEvent); saveMousePosition(mouseEvent);
if( state != Options.AppState.PAUSED ) { if( state != Options.AppState.PAUSED ) {
//handleUpdate(delta); // update() und draw() der Unterklasse werden in einem
//handleDraw(); // eigenen Thread ausgeführt, aber der Zeichenthread
// wartet, bis der Thread fertig ist. Außer die Unterklasse
// Update and draw are executed in a new thread, // ruft delay() auf und lässt den Thread eine länger Zeit
// but we wait for them to finish unless the user // schlafen. Dann wird der nächst Frame vorzeitig gerendert,
// did call any blocking method, that would also block // bis der update-Thread wieder bereit ist. Dadurch können
// rendering of new frames. // nebenläufige Aufgaben (z.B. Animationen) weiterlaufen.
if( !updateThreadExecutor.isRunning() ) { if( !updateThreadExecutor.isRunning() ) {
delta = (System.nanoTime() - updateBeforeTime) / 1000000000.0; delta = (System.nanoTime() - updateBeforeTime) / 1000000000.0;
updateBeforeTime = System.nanoTime(); updateBeforeTime = System.nanoTime();
// uddate()/draw() ausführen
updateThreadExecutor.execute(() -> { updateThreadExecutor.execute(() -> {
if( state == Options.AppState.RUNNING if( state == Options.AppState.RUNNING
&& updateState == Options.AppState.IDLE ) { && updateState == Options.AppState.IDLE ) {
@@ -1358,6 +1384,7 @@ public class Zeichenmaschine extends Constants {
if( Thread.interrupted() ) { if( Thread.interrupted() ) {
running = false; running = false;
terminateImediately = true;
break; break;
} }
} }
@@ -1369,7 +1396,6 @@ public class Zeichenmaschine extends Constants {
// frame.repaint(); // frame.repaint();
} }
// dispatchEvents(); // dispatchEvents();
} }
@@ -1412,37 +1438,23 @@ public class Zeichenmaschine extends Constants {
pause_pending = false; pause_pending = false;
} }
} }
// Shutdown the updateThreads
updateThreadExecutor.shutdownNow();
state = Options.AppState.STOPPED; state = Options.AppState.STOPPED;
// Shutdown the updateThread
while( !terminateImediately && updateThreadExecutor.isRunning() ) {
Thread.yield();
}
updateThreadExecutor.shutdownNow();
// Cleanup // Cleanup
teardown(); shutdown();
cleanup(); cleanup();
state = Options.AppState.TERMINATED; state = Options.AppState.TERMINATED;
if( quitAfterTeardown ) { if( quitAfterShutdown ) {
quit(); quit();
} }
} }
public void handleUpdate( double delta ) {
if( state == Options.AppState.RUNNING ) {
state = Options.AppState.UPDATING;
update(delta);
canvas.updateLayers(delta);
state = Options.AppState.RUNNING;
}
}
public void handleDraw() {
if( state == Options.AppState.RUNNING ) {
state = Options.AppState.DRAWING;
draw();
state = Options.AppState.RUNNING;
}
}
} }
// TODO: Remove // TODO: Remove