@@ -78,7 +78,7 @@ int max = numbers.stream().reduce(Integer.MIN_VALUE, Math::max);</code></pre>
...
@@ -78,7 +78,7 @@ int max = numbers.stream().reduce(Integer.MIN_VALUE, Math::max);</code></pre>
<pre><codeclass="language-java line-numbers">int max = numbers.stream().parallel().reduce(Integer.MIN_VALUE, Math::max);</code></pre>
<pre><codeclass="language-java line-numbers">int max = numbers.stream().parallel().reduce(Integer.MIN_VALUE, Math::max);</code></pre>
<p>Die grundsätzliche Erwartung ist, dass durch die Nutzung mehrerer parallel arbeitender Threads der parallelisierte Stream schneller fertig wird als der sequentielle Stream. Das lässt sich mit einem ausreichend großen Berechnungsaufwand für den Stream und entsprechender Multicore-Hardware auch demonstrieren (<ahref="/blob/master/concurrency/src/main/java/streams/ParallelStreamProcessingTime.java"class="repo-link">siehe hier</a>).</p>
<p>Die grundsätzliche Erwartung ist, dass durch die Nutzung mehrerer parallel arbeitender Threads der parallelisierte Stream schneller fertig wird als der sequentielle Stream. Das lässt sich mit einem ausreichend großen Berechnungsaufwand für den Stream und entsprechender Multicore-Hardware auch demonstrieren (<ahref="/concurrency/src/main/java/streams/ParallelStreamProcessingTime.java"class="repo-link">siehe hier</a>).</p>
<p>Grundsätzlich ist die Annahme, dass ein sequentieller und ein paralleler Stream stets zum gleichen Ergebnis führen. Es gibt aber Ausnahmefälle, die entsprechend dokumentiert sind, z.B. die häufig genutzte Methode <ahref="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#forEach(java.util.function.Consumer)"><code>forEach</code></a>, mit der über die Elemente eines Streams iteriert werden kann. Die Methode <code>forEach</code> ist wie folgt dokumentiert: <i>"The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization."</i> Die folgende Methode <code>log</code> gibt die Elemente eines parallelen Streams daher nicht unbedingt in der richtigen Reihenfolge aus.</p>
<p>Grundsätzlich ist die Annahme, dass ein sequentieller und ein paralleler Stream stets zum gleichen Ergebnis führen. Es gibt aber Ausnahmefälle, die entsprechend dokumentiert sind, z.B. die häufig genutzte Methode <ahref="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#forEach(java.util.function.Consumer)"><code>forEach</code></a>, mit der über die Elemente eines Streams iteriert werden kann. Die Methode <code>forEach</code> ist wie folgt dokumentiert: <i>"The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization."</i> Die folgende Methode <code>log</code> gibt die Elemente eines parallelen Streams daher nicht unbedingt in der richtigen Reihenfolge aus.</p>
...
@@ -102,7 +102,7 @@ Das Fork-Join-Framework agiert nach dem Prinzip des Divide & Conquer-Algorithmus
...
@@ -102,7 +102,7 @@ Das Fork-Join-Framework agiert nach dem Prinzip des Divide & Conquer-Algorithmus
<p>Beim ersten Fork wird der Indexbereich in die beiden Teilbereiche 0..31 und 32..63 aufgeteilt. Damit sind 2 Teilaufgaben entstanden: (a) Finden der größten Zahl im Indexbereich 0..31 sowie (b) finden der größten Zahl im Indexbereich 32..63.
<p>Beim ersten Fork wird der Indexbereich in die beiden Teilbereiche 0..31 und 32..63 aufgeteilt. Damit sind 2 Teilaufgaben entstanden: (a) Finden der größten Zahl im Indexbereich 0..31 sowie (b) finden der größten Zahl im Indexbereich 32..63.
Diese Teilaufgaben können in der Fork-Phase rekursiv in noch kleinere Teilaufgaben zerlegt werden: Finden der größten Zahl in den Indexbereichen 0..15, 16..31, 32..47 und 48..63. Entscheidend ist, dass jede Teilaufgabe unabhängig von den anderen in einem nebenläufigen Thread gelöst werden kann. Die Methode <ahref="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#reduce(T,java.util.function.BinaryOperator)"><code>reduce</code></a> (s. oben) führt dazu die Elemente eines Streams paarweise zusammen. In diesem Fall wird der Methode <code>reduce</code> dazu die Methode <code>Math.max</code> als <code>BinaryOperator</code> übergeben, um jeweils die größere von zwei Zahlen zurückzugeben. Angenommen es liegt eine Quadcore-Hardware zugrunde, würden in der Fork-Phase 4 Threads genutzt, die unabhängig voneinander die Teilaufgaben bearbeiten. In der anschließenden Join-Phase werden die Ergebnisse der Teilaufgaben aggregiert, indem sie paarweise als Argumente dem <code>BinaryOperator</code> (hier <code>Math:max</code>) übergeben werden, der in der Methode <code>reduce</code> ausgeführt wird.</p>
Diese Teilaufgaben können in der Fork-Phase rekursiv in noch kleinere Teilaufgaben zerlegt werden: Finden der größten Zahl in den Indexbereichen 0..15, 16..31, 32..47 und 48..63. Entscheidend ist, dass jede Teilaufgabe unabhängig von den anderen in einem nebenläufigen Thread gelöst werden kann. Die Methode <ahref="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#reduce(T,java.util.function.BinaryOperator)"><code>reduce</code></a> (s. oben) führt dazu die Elemente eines Streams paarweise zusammen. In diesem Fall wird der Methode <code>reduce</code> dazu die Methode <code>Math.max</code> als <code>BinaryOperator</code> übergeben, um jeweils die größere von zwei Zahlen zurückzugeben. Angenommen es liegt eine Quadcore-Hardware zugrunde, würden in der Fork-Phase 4 Threads genutzt, die unabhängig voneinander die Teilaufgaben bearbeiten. In der anschließenden Join-Phase werden die Ergebnisse der Teilaufgaben aggregiert, indem sie paarweise als Argumente dem <code>BinaryOperator</code> (hier <code>Math:max</code>) übergeben werden, der in der Methode <code>reduce</code> ausgeführt wird.</p>
<p>Wenn N die Anzahl der verfügbaren Prozessorkerne ist, wird das Ausgangsproblem tatsächlich nicht nur in N Teilaufgaben zerlegt sondern in etwa 4·N. Die Anzahl der erzeugten Teilaufgaben entspricht der Zweierpotenz, die größer als 4·(N-1) ist. Auf einer Quadcore-Hardware werden also 16 Teilaufgaben erzeugt, die nacheinander von 4 Threads bearbeitet werden. Dieses Verhalten lässt sich auch beobachten (<ahref="/blob/master/concurrency/src/main/java/streams/ParallelStreamCountForks.java"class="repo-link">siehe hier</a>). Der per Default genutzte Thread-Pool namens <i>ForkJoinPool.common</i> lässt sich bei Bedarf verändern oder austauschen. Über die folgende Anweisung wird z.B. die Anzahl der Threads in diesem Thread-Pool auf 8 erhöht.</p>
<p>Wenn N die Anzahl der verfügbaren Prozessorkerne ist, wird das Ausgangsproblem tatsächlich nicht nur in N Teilaufgaben zerlegt sondern in etwa 4·N. Die Anzahl der erzeugten Teilaufgaben entspricht der Zweierpotenz, die größer als 4·(N-1) ist. Auf einer Quadcore-Hardware werden also 16 Teilaufgaben erzeugt, die nacheinander von 4 Threads bearbeitet werden. Dieses Verhalten lässt sich auch beobachten (<ahref="/concurrency/src/main/java/streams/ParallelStreamCountForks.java"class="repo-link">siehe hier</a>). Der per Default genutzte Thread-Pool namens <i>ForkJoinPool.common</i> lässt sich bei Bedarf verändern oder austauschen. Über die folgende Anweisung wird z.B. die Anzahl der Threads in diesem Thread-Pool auf 8 erhöht.</p>
<p>Es soll eine Anwendung erstellt werden, die mind. in Client- und Server-Komponente unterteilt ist. Das Projekt kann im Team von 2-4 Studierenden bearbeitet werden.</p>
<p>Im Fokus des Moduls steht das Kennenlernen und praktische Erproben von bewährten Entwurfsmustern und Frameworks, die auch in professionellen Sofwareprojekten (im Java-Umfeld) eingesetzt werden. Es soll als Prüfungsleistung daher eine Anwendung konstruiert werden, die mind. in Client- und Server-Komponente unterteilt ist. Das Projekt kann im Team von 2-4 Studierenden bearbeitet werden.</p>
<ul>
<ul>
<li><b>Funktionale Anforderungen</b>: Es soll ein einfaches Multiplayer-Spiel für mind. 2 Spieler realisiert werden. Als Projektidee wird die Implementierung eines bekannten Arcade-Klassikers vorgeschlagen, z.B. <ahref="https://de.wikipedia.org/wiki/Tetris">Tetris</a>, <ahref="https://de.wikipedia.org/wiki/Tank_(Computerspiel)">Tank</a>, <ahref="https://de.wikipedia.org/wiki/Snake_(Computerspiel)">Snake</a>, <ahref="https://de.wikipedia.org/wiki/Pac-Man">Pac-Man</a>, <ahref="https://de.wikipedia.org/wiki/Dig_Dug">Dig Dug</a> oder <ahref="https://de.wikipedia.org/wiki/Blobby_Volley">Blobby Volley</a>. Es kann auch ein Brettspiel oder ein Quizspiel umgesetzt werden. Die Spielregeln des Spiels können gerne individuell angepasst werden. Bezüglich der angestrebten Funktionalität der Anwendung wird dementsprechend Freiraum für kreative Ideen gelassen, die mit dem jeweiligen Modulbetreuer abzustimmen sind. Unabhängig von der gewählten Projektidee sollten folgende Anforderungen berücksichtigt werden:
<li><b>Funktionale Anforderungen</b>: Es soll ein einfaches Multiplayer-Spiel für mind. 2 Spieler realisiert werden. Als Projektidee wird die Implementierung eines bekannten Arcade-Klassikers vorgeschlagen, z.B. <ahref="https://de.wikipedia.org/wiki/Tetris">Tetris</a>, <ahref="https://de.wikipedia.org/wiki/Tank_(Computerspiel)">Tank</a>, <ahref="https://de.wikipedia.org/wiki/Snake_(Computerspiel)">Snake</a>, <ahref="https://de.wikipedia.org/wiki/Pac-Man">Pac-Man</a>, <ahref="https://de.wikipedia.org/wiki/Dig_Dug">Dig Dug</a> oder <ahref="https://de.wikipedia.org/wiki/Blobby_Volley">Blobby Volley</a>. Es kann auch ein Brettspiel oder ein Quizspiel umgesetzt werden. Die Spielregeln des Spiels können gerne individuell angepasst werden. Bezüglich der angestrebten Funktionalität der Anwendung wird dementsprechend Freiraum für kreative Ideen gelassen, die mit dem jeweiligen Modulbetreuer abzustimmen sind. Tatsächlich ist Java keine besonders übliche Sprache für die Implementierung eines Spiels, aber gerade dadurch entsteht Raum für Kreativität im Entwurf. Es darf mit Freude über verschiedene Lösungsansätze diskutiert werden. Zusätzlich kann der Humor, der i.d.R. mit der Entwicklung eines Spiels verbunden ist, die Motivation steigern. Unabhängig von der gewählten Projektidee sollten folgende Anforderungen berücksichtigt werden:
<ul>
<ul>
<li>Ein Anwender soll sich registrieren, einloggen und ausloggen können.</li>
<li>Ein Anwender soll sich registrieren, einloggen und ausloggen können.</li>
<li>Für jeden Anwender wird eine Historie seiner gespielten Spiele erfasst und mittels einfacher Auswertungen dargestellt (gewonnene und verlorene Spiele, mittlere Punktzahl, usw.).</li>
<li>Für jeden Anwender wird eine Historie seiner gespielten Spiele erfasst und mittels einfacher Auswertungen dargestellt (gewonnene und verlorene Spiele, mittlere Punktzahl, usw.).</li>
@@ -352,4 +352,140 @@ public class PostLoginTask extends Task<User> {
...
@@ -352,4 +352,140 @@ public class PostLoginTask extends Task<User> {
<li>Die Klasse <code>Task<V></code> ist eine generische Klasse, für die ein bestimmter Rückgabetyp <code>V</code> für die Methode <code>call</code> spezifiziert werden kann. Für die Klasse <code>PostLoginTask</code> ist dies im vorliegenden Beispiel die Datenstruktur-Klasse <code>User</code> (vgl. Zeilen 6 und 14).</li>
<li>Die Klasse <code>Task<V></code> ist eine generische Klasse, für die ein bestimmter Rückgabetyp <code>V</code> für die Methode <code>call</code> spezifiziert werden kann. Für die Klasse <code>PostLoginTask</code> ist dies im vorliegenden Beispiel die Datenstruktur-Klasse <code>User</code> (vgl. Zeilen 6 und 14).</li>
<li>In Zeile 19 des Task wird ein blockierender HTTP-Request gesendet, der ggf. eine Exception wirft, wenn z.B. der Server nicht zu erreichen ist. Insbesondere wegen dieser Zeile wird überhaupt ein asynchroner Task angelegt. In den Zeilen 20-23 wird die HTTP-Response verarbeitet.</li>
<li>In Zeile 19 des Task wird ein blockierender HTTP-Request gesendet, der ggf. eine Exception wirft, wenn z.B. der Server nicht zu erreichen ist. Insbesondere wegen dieser Zeile wird überhaupt ein asynchroner Task angelegt. In den Zeilen 20-23 wird die HTTP-Response verarbeitet.</li>
<li>Zum Senden des HTTP-Request wird hier exemplarisch die Bibliothek <ahref="http://unirest.io/java.html">Unirest</a> verwendet. Diese würde alternativ auch das Senden eines nicht-blockierenden HTTP-Request ermöglichen, wenn am Ende von Zeile 19 anstatt der Methode <code>asString</code> die Methode <code>asStringAsync</code> aufgerufen wird. Die Rückgabe wäre dann vom Typ <code>Future<HttpResponse<String>></code>. Dadurch würde sich das Auslagern des blockierenden Aufrufs aus dem eigenen Code in den internen Code der Bibliothek verlagern. Ein Task wäre nicht mehr unbedingt erforderlich, falls die Vorbereitung des HTTP-Request und die Verarbeitung der HTTP-Response als nicht sonderlich aufwändig eingestuft werden und es tolerierbar ist, diese im <i>JavaFX Application Thread</i> auszuführen.</li>
<li>Zum Senden des HTTP-Request wird hier exemplarisch die Bibliothek <ahref="http://unirest.io/java.html">Unirest</a> verwendet. Diese würde alternativ auch das Senden eines nicht-blockierenden HTTP-Request ermöglichen, wenn am Ende von Zeile 19 anstatt der Methode <code>asString</code> die Methode <code>asStringAsync</code> aufgerufen wird. Die Rückgabe wäre dann vom Typ <code>Future<HttpResponse<String>></code>. Dadurch würde sich das Auslagern des blockierenden Aufrufs aus dem eigenen Code in den internen Code der Bibliothek verlagern. Ein Task wäre nicht mehr unbedingt erforderlich, falls die Vorbereitung des HTTP-Request und die Verarbeitung der HTTP-Response als nicht sonderlich aufwändig eingestuft werden und es tolerierbar ist, diese im <i>JavaFX Application Thread</i> auszuführen.</li>
</ul>
</ul>
\ No newline at end of file
<h4>Animationen in JavaFX</h4>
<p>In einem Spiel bewegen sich häufig unterschiedliche Objekte über den Bildschirm, die miteinander im Rahmen der Spielregeln interagieren. In JavaFX wird zur Animation von Objekten die abstrakte Klasse <code><ahref="https://openjfx.io/javadoc/11/javafx.graphics/javafx/animation/AnimationTimer.html">AnimationTimer</a></code> bereitgestellt. Mit dieser Klasse kann ein Timer erstellt werden, der in jedem Frame aufgerufen wird, während er aktiv ist. Die Frame-Rate ist variabel und hängt von der zugrundeliegenden Ausführungsumgebung und Hardware ab. Grundsätzlich ist es zu empfehlen, die Frame-Rate, in der das UI aktualisiert wird, und die Spielphysik voneinander zu entkoppeln (siehe Diskussion <ahref="https://gamedev.stackexchange.com/questions/97933/framerate-is-affecting-speed-of-object">hier</a>). Zur Verwendung eines konkreten <code>AnimationTimer</code> muss die Methode <code>handle</code> sinnvoll überschrieben werden. Die Methoden <code>start</code> und <code>stop</code> erlauben das Starten bzw. Stoppen des Timers. Das folgende Code-Beispiel erzeugt eine Anwendung, in der ein animierter Ball mit fixer Geschwindigkeit an den Kanten des Anwendungsfensters abprallt (siehe <ahref="/ui/javafx-animation/src/main/java/animation/SingleBouncingBall.java"class="repo-link">SingleBouncingBall.java</a>).</p>
<p>Die Klasse <code>Ball</code> erweitert die Klasse <ahref="https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/shape/Circle.html"><code>javafx.scene.shape.Circle</code></a>. Ein <code>Circle</code> ist ein spezieller <ahref="https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/shape/Shape.html"><code>Shape</code></a>, der als <code>Node</code> dem <i>Scene Graph</i> hinzugefügt werden kann. Weitere Shapes sind <code>Rectangle</code>, <code>Polygon</code>, <code>Text</code>, <code>SVGPath</code>, uvm.
Alternativ zur Verwendung von Shapes können animierte Objekte in JavaFX auch auf einer <ahref="https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/canvas/Canvas.html">Canvas</a> über deren <ahref="https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/canvas/GraphicsContext.html"><code>GraphicsContext</code></a> gezeichnet werden. Die beiden folgenden Code-Beispiele visualisieren jeweils den Text "Hello World!" und einen roten Kreis – im ersten Code-Beispiel werden dazu Shapes angelegt, im zweiten Code-Beispiel wird auf eine Canvas gezeichnet.</p>
<pre><codeclass="language-java line-numbers">// display text and circle as shapes
Pane shapesContainer = new Pane();
Shape text = new Text(20, 20, "Hello World!"); // x = 20, y = 20
shapesContainer.getChildren().add(text);
Shape circle = new Circle(100, 100, 30, Color.RED); // centerX = 100, centerY = 100, radius = 30
<p>Die alternativen Implementierungen in den Klassen <ahref="/ui/javafx-animation/src/main/java/animation/BouncingBallsShapes.java"class="repo-link">BouncingBallsShapes.java</a> und <ahref="/ui/javafx-animation/src/main/java/animation/BouncingBallsCanvas.java"class="repo-link">BouncingBallsCanvas.java</a> lassen erkennen, dass im Vergleich von Shapes- und Canvas-Animationen sich keine signifikanten Unterschiede hinsichtlich der Frame-Rate und des Hauptspeicherbedarfs ergeben, aber die Shapes-Animationen eine etwas höhere CPU-Auslastung verursachen. Das folgende Video demonstriert diese Beobachtungen.</p>
<label>Animationen in JavaFX mittels Shapes und Canvas</label>
<h4>Simulation der Spielphysik</h4>
<p>Die Spielphysik kann – wie in den obigen Beispielen – durch stark vereinfachte Annahmen bei der <ahref="https://de.wikipedia.org/wiki/Sto%C3%9F_(Physik)">Kollision von Objekten</a> eigenständig implementiert werden. Wenn in der Spielphysik allerdings auch spezielle Effekte und Kräfte wie Reibung, Dichte oder Rotationsgeschwindigkeit berücksichtigt werden sollen, bietet sich die Verwendung einer Physik-Engine wie z.B. <ahref="http://www.dyn4j.org/">dyn4j</a> an. Die Physik-Engine ist unabhängig von JavaFX und kann gleichermaßen mit jedem anderen UI-Framework verwendet werden.</p>
<p>In dyn4j wird eine 2D-Welt simuliert. Die Welt (Klasse <ahref="http://docs.dyn4j.org/v3.3.0/org/dyn4j/dynamics/World.html"><code>World</code></a>) kann diverse Körper (Klasse <ahref="http://docs.dyn4j.org/v3.3.0/org/dyn4j/dynamics/Body.html"><code>Body</code></a>) enthalten, die ihrerseits aus mehreren miteinander verbundenen Körperteilen (Klasse <code>BodyFixture</code>) bestehen (s. <ahref="http://www.dyn4j.org/documentation/getting-started/">Getting Started zu dyn4j</a>). Auf die Körper können Kräfte und Impulse wirken. Das folgende Code-Beispiel zeigt wie einer dyn4j-Welt ein rechteckiger und ein runder Körper hinzugefügt werden.</p>
<pre><codeclass="language-java line-numbers">World world = new World();
Body rect = new Body();
rect.addFixture(Geometry.createRectangle(width, height)); // 1st body fixture is a rectangle
rect.setMass(MassType.INFINITY);
body.translate(0, 0); // sets the rectangle's center (x, y)
circle.translate(0, 10); // sets the circle's center (x, y)
world.addBody(circle);
world.setGravity(World.EARTH_GRAVITY); // the circle will fall down, the rectangle is fixed due to an infinite mass
circle.applyImpulse(new Vector2(x, y)); // applies an impulse on the circle in the direction of (x, y)</code></pre>
<p>Mittels der Methode <code>update</code> auf der Klasse <code>World</code> kann die Simulation der Welt in flexiblen zeitlichen Intervallen fortgeführt werden.</p>
<pre><codeclass="language-java line-numbers">ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
scheduler.scheduleAtFixedRate(() -> {
world.update(interval); // simulation of the world model is updated here
<p>Das folgende Video zeigt eine Beispielanwendung, in der dyn4j zur Kollisionserkennung der animierten Objekte eingesetzt wird. Der Ball kann über die Pfeiltasten gesteuert werden. Durch Mausklicks können herabfallende Rechtecke erzeugt werden. Der vollständige Code der Beispielanwendung findet sich in der Klasse <ahref="/ui/javafx-animation/src/main/java/animation/BouncingBallDyn4j.java"class="repo-link">BouncingBallDyn4j</a> im Verzeichnis <ahref="/ui/javafx-animation"class="repo-link">/ui/javafx-animation</a> des Modul-Repository.</p>
finaldoubleu1=xVelocity1*unitContactX+yVelocity1*unitContactY;// velocity of ball 1 parallel to contact vector
finaldoubleu2=xVelocity2*unitContactX+yVelocity2*unitContactY;// same for ball 2
finaldoublemassSum=b1.mass+b2.mass;
finaldoublemassDiff=b1.mass-b2.mass;
// These equations are derived for one-dimensional collision by solving equations for conservation of momentum and conservation of energy.
finaldoublev1=(2*b2.mass*u2+u1*massDiff)/massSum;
finaldoublev2=(2*b1.mass*u1-u2*massDiff)/massSum;
finaldoubleu1PerpX=xVelocity1-u1*unitContactX;// Components of ball 1 velocity in direction perpendicular to contact vector. This doesn't change with collision.
finaldoubleu1PerpY=yVelocity1-u1*unitContactY;
finaldoubleu2PerpX=xVelocity2-u2*unitContactX;// same for ball 2