From 9ee7c606fe413f9e5329307a76d641c5d5e468f7 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 7 Jul 2022 21:18:45 +0200 Subject: [PATCH 1/4] Implementierung verschiedener Task-Typen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Tasks erfüllen verschiedene Aufgaben und können vom TaskRunner parallel ausgeführt werden. Ob ein so komplexes Task-Management notwendig ist, bleibt offen. --- src/schule/ngb/zm/tasks/DelayedTask.java | 65 +++++++++++++++++++ .../ngb/zm/tasks/FrameSynchronizedTask.java | 51 +++++++++++++++ .../ngb/zm/tasks/FramerateLimitedTask.java | 11 ++++ src/schule/ngb/zm/tasks/RateLimitedTask.java | 53 +++++++++++++++ src/schule/ngb/zm/tasks/Task.java | 24 +++++++ src/schule/ngb/zm/tasks/TaskRunner.java | 30 ++++++--- 6 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 src/schule/ngb/zm/tasks/DelayedTask.java create mode 100644 src/schule/ngb/zm/tasks/FrameSynchronizedTask.java create mode 100644 src/schule/ngb/zm/tasks/FramerateLimitedTask.java create mode 100644 src/schule/ngb/zm/tasks/RateLimitedTask.java create mode 100644 src/schule/ngb/zm/tasks/Task.java diff --git a/src/schule/ngb/zm/tasks/DelayedTask.java b/src/schule/ngb/zm/tasks/DelayedTask.java new file mode 100644 index 0000000..02f3ed5 --- /dev/null +++ b/src/schule/ngb/zm/tasks/DelayedTask.java @@ -0,0 +1,65 @@ +package schule.ngb.zm.tasks; + +import schule.ngb.zm.Zeichenmaschine; + +import java.util.concurrent.Delayed; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +public abstract class DelayedTask extends Task implements Delayed { + + protected long startTime = System.currentTimeMillis(); // in ms + + /** + * Gibt die absolute Verzögerung der Task zurück. Im Gegensatz zu + * {@link #getDelay(TimeUnit)} sollte das Ergebnis von {@code getDelay()} + * bei mehrmaligem Aufruf konstant bleiben. + * + * @return Die ursprüngliche Verzögerung in Millisekunden + */ + public abstract int getDelay(); + + public long getStartTime() { + return startTime + getDelay(); + } + + /** + * Gibt die verbleibende Verzögerung bis zur Ausführung der Task zurück. Im + * Gegensatz zu {@link #getDelay()} sollte für mehrere Aufrufe von + * {@code getDelay(TimeUnit)} gelten, dass der zeitlich spätere Aufruf einen + * kleineren Wert zurückgibt, als der Frühere (abhängig von der gewählten + * {@link TimeUnit}). + * + * @param unit Die Zeiteinheit für die Verzögerung. + * @return Die verbleibende Verzögerung in der angegebenen Zeiteinheit. + */ + @Override + public long getDelay( TimeUnit unit ) { + int diff = (int) (getStartTime() - System.currentTimeMillis()); + return unit.convert(diff, TimeUnit.MILLISECONDS); + } + + @Override + public int compareTo( Delayed o ) { + return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); + } + + @Override + public void run() { + long delay = getDelay(TimeUnit.MILLISECONDS); + while( delay > 0 ) { + try { + wait(delay); + } catch( InterruptedException e ) { + // Keep waiting + } + delay = getDelay(TimeUnit.MILLISECONDS); + } + + running = true; + this.update(0.0); + running = false; + done = true; + } + +} diff --git a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java new file mode 100644 index 0000000..73bda60 --- /dev/null +++ b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java @@ -0,0 +1,51 @@ +package schule.ngb.zm.tasks; + +import schule.ngb.zm.Constants; +import schule.ngb.zm.Zeichenmaschine; + +public abstract class FrameSynchronizedTask extends Task { + + private static Thread mainThread; + + private static final Thread getMainThread() { + if( mainThread == null ) { + mainThread = Thread.currentThread(); + if( !mainThread.getName().equals("Zeichenthread") ) { + // Need to search for main Zeichenthread ... + } + } + return mainThread; + } + + @Override + public void run() { + running = true; + int lastTick = 0; + Thread lock = getMainThread(); + + while( running ) { + lastTick = Constants.tick; + this.update(lastTick); + + synchronized( lock ) { + while( lastTick >= Constants.tick ) { + /*try { + lock.wait(); + } catch( InterruptedException e ) { + // We got interrupted ... + }*/ + Thread.yield(); + } + } + } + + running = false; + done = true; + } + + @Override + public boolean isActive() { + return false; + } + +} diff --git a/src/schule/ngb/zm/tasks/FramerateLimitedTask.java b/src/schule/ngb/zm/tasks/FramerateLimitedTask.java new file mode 100644 index 0000000..8fab4f6 --- /dev/null +++ b/src/schule/ngb/zm/tasks/FramerateLimitedTask.java @@ -0,0 +1,11 @@ +package schule.ngb.zm.tasks; + +import schule.ngb.zm.Constants; + +public abstract class FramerateLimitedTask extends RateLimitedTask { + + public int getRate() { + return Constants.framesPerSecond; + } + +} diff --git a/src/schule/ngb/zm/tasks/RateLimitedTask.java b/src/schule/ngb/zm/tasks/RateLimitedTask.java new file mode 100644 index 0000000..aadc06d --- /dev/null +++ b/src/schule/ngb/zm/tasks/RateLimitedTask.java @@ -0,0 +1,53 @@ +package schule.ngb.zm.tasks; + +public abstract class RateLimitedTask extends Task { + + public abstract int getRate(); + + @Override + public final void run() { + if( running || done ) { + return; + } + + // current time in ns + long beforeTime = System.nanoTime(); + // store for deltas + long overslept = 0L; + // delta in ms + double delta = 0; + + running = true; + while( running ) { + // delta in seconds + delta = (System.nanoTime() - beforeTime) / 1000000000.0; + beforeTime = System.nanoTime(); + + this.update(delta); + + // delta time in ns + long afterTime = System.nanoTime(); + long dt = afterTime - beforeTime; + long sleep = 0; + if( getRate() > 0 ) { + sleep = ((1000000000L / getRate()) - dt) - overslept; + } + + if( sleep > 0 ) { + try { + Thread.sleep(sleep / 1000000L, (int) (sleep % 1000000L)); + } catch( InterruptedException e ) { + // Interrupt not relevant + } + } else { + Thread.yield(); + } + // Did we sleep to long? + overslept = (System.nanoTime() - afterTime) - sleep; + } + + running = false; + done = true; + } + +} diff --git a/src/schule/ngb/zm/tasks/Task.java b/src/schule/ngb/zm/tasks/Task.java new file mode 100644 index 0000000..a8ae158 --- /dev/null +++ b/src/schule/ngb/zm/tasks/Task.java @@ -0,0 +1,24 @@ +package schule.ngb.zm.tasks; + +import schule.ngb.zm.Updatable; + +public abstract class Task implements Runnable, Updatable { + + protected boolean running = false; + + protected boolean done = false; + + @Override + public boolean isActive() { + return running; + } + + public boolean isDone() { + return !running & done; + } + + public void stop() { + running = false; + } + +} diff --git a/src/schule/ngb/zm/tasks/TaskRunner.java b/src/schule/ngb/zm/tasks/TaskRunner.java index 5bd20da..0173fca 100644 --- a/src/schule/ngb/zm/tasks/TaskRunner.java +++ b/src/schule/ngb/zm/tasks/TaskRunner.java @@ -25,6 +25,11 @@ public class TaskRunner { return runner; } + public static Future run( Task task ) { + TaskRunner r = getTaskRunner(); + return r.pool.submit(task); + } + public static Future run( Runnable task ) { TaskRunner r = getTaskRunner(); return r.pool.submit(task); @@ -35,18 +40,13 @@ public class TaskRunner { return r.pool.submit(task, result); } - public static Future schedule( Runnable task, int ms ) { - TaskRunner r = getTaskRunner(); - return r.pool.schedule(task, ms, TimeUnit.MILLISECONDS); - } - public static void invokeLater( Runnable task ) { SwingUtilities.invokeLater(task); } public static void shutdown() { if( runner != null ) { - runner.pool.shutdown(); + /*runner.pool.shutdown(); try { runner.pool.awaitTermination(SHUTDOWN_TIME, TimeUnit.MILLISECONDS); } catch( InterruptedException ex ) { @@ -55,15 +55,27 @@ public class TaskRunner { if( !runner.pool.isTerminated() ) { runner.pool.shutdownNow(); } - } + }*/ + runner.pool.shutdownNow(); } } - ScheduledExecutorService pool; + ExecutorService pool; private TaskRunner() { //pool = new ScheduledThreadPoolExecutor(4); - pool = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() { + /*pool = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() { + private final ThreadFactory threadFactory = Executors.defaultThreadFactory(); + + @Override + public Thread newThread( Runnable r ) { + Thread t = threadFactory.newThread(r); + t.setName("TaskRunner-" + t.getName()); + t.setDaemon(true); + return t; + } + });*/ + pool = Executors.newCachedThreadPool(new ThreadFactory() { private final ThreadFactory threadFactory = Executors.defaultThreadFactory(); @Override From ced0aa6842699f0466aed0a0b5144d1caa6165c2 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 07:44:49 +0200 Subject: [PATCH 2/4] =?UTF-8?q?Synchronisation=20=C3=BCber=20einen=20globa?= =?UTF-8?q?len=20Monitor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schule/ngb/zm/Zeichenmaschine.java | 35 +++++++++++++++++-- .../ngb/zm/tasks/FrameSynchronizedTask.java | 9 +++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/schule/ngb/zm/Zeichenmaschine.java b/src/schule/ngb/zm/Zeichenmaschine.java index 151de10..93ed677 100644 --- a/src/schule/ngb/zm/Zeichenmaschine.java +++ b/src/schule/ngb/zm/Zeichenmaschine.java @@ -1276,10 +1276,37 @@ public class Zeichenmaschine extends Constants { // Zeichenthread //// + /** + * Globaler Monitor, der einmal pro Frame vom Zeichenthread freigegeben + * wird. Andere Threads können {@link Object#wait()} auf dem Monitor + * aufrufen, um sich mit dem Zeichenthread zu synchronisieren. Der + * {@code wait()} Aufruf sollte sich zur Sicherheit in einer Schleife + * befinden, die prüft, ob sich der Aktuelle {@link #tick} erhöht hat. + *

+	 * int lastTick = Constants.tick;
+	 *
+	 * // Do some work
+	 *
+	 * while( lastTick >= Constants.tick ) {
+	 *     synchronized( Zeichenmaschine.globalSyncLock ) {
+	 *         try {
+	 *             Zeichenmaschine.globalSyncLock.wait();
+	 *         } catch( InterruptedException ex ) {}
+	 *     }
+	 * }
+	 * // Next frame has started
+	 * 
+ *

+ * Die {@link schule.ngb.zm.tasks.FrameSynchronizedTask} implementiert eine + * {@link schule.ngb.zm.tasks.Task}, die sich automatisch auf diese Wiese + * mit dem Zeichenthread synchronisiert. + */ + public static final Object globalSyncLock = new Object[0]; + class Zeichenthread extends Thread { public Zeichenthread() { - super(Zeichenthread.class.getSimpleName()); + super(APP_NAME); //super(APP_NAME + " " + APP_VERSION); } @@ -1328,6 +1355,9 @@ public class Zeichenmaschine extends Constants { } runTasks(); + synchronized( globalSyncLock ) { + globalSyncLock.notifyAll(); + } // delta time in ns long afterTime = System.nanoTime(); @@ -1413,7 +1443,8 @@ public class Zeichenmaschine extends Constants { } - class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener{ + class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener { + @Override public void mouseClicked( MouseEvent e ) { enqueueEvent(e); diff --git a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java index 73bda60..416b9db 100644 --- a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java +++ b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java @@ -10,7 +10,7 @@ public abstract class FrameSynchronizedTask extends Task { private static final Thread getMainThread() { if( mainThread == null ) { mainThread = Thread.currentThread(); - if( !mainThread.getName().equals("Zeichenthread") ) { + if( !mainThread.getName().equals(Constants.APP_NAME) ) { // Need to search for main Zeichenthread ... } } @@ -21,7 +21,7 @@ public abstract class FrameSynchronizedTask extends Task { public void run() { running = true; int lastTick = 0; - Thread lock = getMainThread(); + Object lock = Zeichenmaschine.globalSyncLock; while( running ) { lastTick = Constants.tick; @@ -29,12 +29,11 @@ public abstract class FrameSynchronizedTask extends Task { synchronized( lock ) { while( lastTick >= Constants.tick ) { - /*try { + try { lock.wait(); } catch( InterruptedException e ) { // We got interrupted ... - }*/ - Thread.yield(); + } } } } From a8bbce72a2a5109c1c42f4ca51deab6b966977a2 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 07:52:35 +0200 Subject: [PATCH 3/4] =?UTF-8?q?DelayedTask=20wartet=20selbstst=C3=A4ndig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schule/ngb/zm/tasks/DelayedTask.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/schule/ngb/zm/tasks/DelayedTask.java b/src/schule/ngb/zm/tasks/DelayedTask.java index 02f3ed5..7597ab3 100644 --- a/src/schule/ngb/zm/tasks/DelayedTask.java +++ b/src/schule/ngb/zm/tasks/DelayedTask.java @@ -46,14 +46,14 @@ public abstract class DelayedTask extends Task implements Delayed { @Override public void run() { - long delay = getDelay(TimeUnit.MILLISECONDS); - while( delay > 0 ) { + while( getDelay(TimeUnit.MILLISECONDS) > 0 ) { try { - wait(delay); + System.out.println("Waiting for " + getDelay(TimeUnit.MILLISECONDS) + " ms"); + Thread.sleep(getDelay(TimeUnit.MILLISECONDS)); + //wait(getDelay(TimeUnit.MILLISECONDS)); } catch( InterruptedException e ) { // Keep waiting } - delay = getDelay(TimeUnit.MILLISECONDS); } running = true; From 476545f721b3195a55537a1577034e560b05f6d4 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 07:52:39 +0200 Subject: [PATCH 4/4] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7ab68..69cb1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## Added - Interface `Audio` extrahiert, mit Basisfunktionen von `Sound` und `Music`. - Klasse `Mixer` steuert mehrere Audio-Objekte gleichzeitig. +- Klasse `tasks.RateLimitedTask`, `tasks.FramerateLimitedTask`, `tasks.FrameSynchronizedTask` und `tasks.DelayedTask`. ## Changed - Neue Package-Struktur: