Merge branch 'tasks' into anim

This commit is contained in:
ngb
2022-07-08 07:53:01 +02:00
8 changed files with 258 additions and 11 deletions

View File

@@ -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:

View File

@@ -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.
* <pre><code>
* 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
* </code></pre>
* <p>
* 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();
@@ -1414,6 +1444,7 @@ public class Zeichenmaschine extends Constants {
}
class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener {
@Override
public void mouseClicked( MouseEvent e ) {
enqueueEvent(e);

View File

@@ -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() {
while( getDelay(TimeUnit.MILLISECONDS) > 0 ) {
try {
System.out.println("Waiting for " + getDelay(TimeUnit.MILLISECONDS) + " ms");
Thread.sleep(getDelay(TimeUnit.MILLISECONDS));
//wait(getDelay(TimeUnit.MILLISECONDS));
} catch( InterruptedException e ) {
// Keep waiting
}
}
running = true;
this.update(0.0);
running = false;
done = true;
}
}

View File

@@ -0,0 +1,50 @@
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(Constants.APP_NAME) ) {
// Need to search for main Zeichenthread ...
}
}
return mainThread;
}
@Override
public void run() {
running = true;
int lastTick = 0;
Object lock = Zeichenmaschine.globalSyncLock;
while( running ) {
lastTick = Constants.tick;
this.update(lastTick);
synchronized( lock ) {
while( lastTick >= Constants.tick ) {
try {
lock.wait();
} catch( InterruptedException e ) {
// We got interrupted ...
}
}
}
}
running = false;
done = true;
}
@Override
public boolean isActive() {
return false;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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