Discussion:
JUnit Test von JButton: Action wird nicht erkannt
(zu alt für eine Antwort)
Christian H. Kuhn
2016-07-13 23:09:27 UTC
Permalink
Hallo Gemeinde,

Ich verzweifle gerade.

Vollständiger Code auf https://www.qno.de/gitweb/, Branch v0.8.4. Die
wichtigsten Ausschnitte:

public class QChessClockJavaAV extends JFrame implements
QChessClockObserver, ActionListener {

(...)

private final transient JButton resetButton;

(...)

public QChessClockJavaAV(final QChessClock _clock) {
super("QChessClock – The JAVA Chess Clock from your FIDE Arbiter");
final JPanel contentPanel = new JPanel();
contentPanel.setLayout(new GridBagLayout());
final GridBagConstraints constraint = new GridBagConstraints();
constraint.fill = GridBagConstraints.BOTH;
constraint.weightx = 1;
(...)
resetButton = new JButton("R");
resetButton.setActionCommand("reset");
resetButton.addActionListener(this);
resetButton.setMinimumSize(QChessClockJavaAV.BUTTON_DIMENSION);
resetButton.setName("ResetButton");
constraint.gridx = 1;
constraint.gridy = 1;
contentPanel.add(resetButton, constraint);
}

(...)

public final void actionPerformed(final ActionEvent _event) {
final String myActionCommand = _event.getActionCommand();

switch (myActionCommand) {
case "reset":
clock.resetPressed();
break;
(...)
default:
}
}
(...)
}

public class QChessClockJavaAVTest implements QChessClockObserver {

private transient QChessClockJavaAV sut;
private transient QChessClock clock;
private transient JButton resetButton;
private transient QChessClockState status;

@Before
public final void setUp() throws Exception {
clock = new QChessClock();
clock.addObserver(this);
sut = new QChessClockJavaAV(clock);
resetButton = (JButton) TestUtils.getChildNamed(sut, "ResetButton");
}

public final void update(final QChessClockState _state) {
status = _state;
}

@Test
public final void pressResetButtonTest() throws Throwable {
leftButton.doClick();
stopButton.doClick();
Thread.sleep(TWO_CYCLES);
resetButton.doClick();
Thread.sleep(TWO_CYCLES);
assertSame(QChessClockState.NOT_STARTED, status);
}
}

TestUtils von
http://www.javaworld.com/article/2073056/swing-gui-programming/automate-gui-tests-for-swing-applications.html

Der Test fails. status ist STOPPED, was es nach stopButton.doClick sein
sollte, aber nicht mehr nach resetButton.doClick.

resetButton wurde korrekt aufgefunden, wie durch einen Test mit
setBackground(Color) herausgefunden wurde.

Wird QChessClockJavaAV normal gestartet und mit der Maus bedient,
funktioniert der Button wie geplant.

Alle anderen Knöpfe reagieren wie gewollt auf doClick().

resetButton.doClick() löst keine Aktion aus, oder zumindest wird sie
nicht von actionPerformed() in QChessClockJavaAV aufgefangen. Ein
Breakpoint zu Beginn von actionPerformed() hält im Debugger bei allen
anderen Knöpfen an, wird aber bei resetButton.onClick() nicht getroffen.
Stattdessen wird die nächste Zeile im Test, Thread.sleep(), ausgeführt.

Ich habe auch versucht, den Knopf mit doClick(1000) länger zu drücken.
Gleicher Fehler.

Mir fehlen die Ideen, wo ich noch suchen soll. Alle mir vorstellbaren
Fehlermöglichkeiten sind ausgeschlossen. Und am Ende ist es dann wie
üblich irgendwas ganz primitives ...

TIA
QNo
Christian H. Kuhn
2016-07-14 16:26:23 UTC
Permalink
Am 14.07.2016 um 01:09 schrieb Christian „Ingrid“ Kuhn:

Die Tests waren natürlich extrem umständlich. Also aus QChessClock ein
Interface extrahiert. Die Testklasse implementiert jetet dieses
Interface und schleicht sich als Mock-Uhr in die zu testende GUI ein.
Damit können die Methodenaufrufe wesentlich leichter getestet werden.
Der Reset-Button reagiert allerding immer noch nicht.

Weiteres Herumspielen mit Farbmarkierungen funktionierte bei diesem
Knopf auch nicht. Allerdings kam dadurch der entscheidende Hinweis: Der
Button war schlicht und ergreifend nicht aktiviert. Nach
setEnabled(true) ist dann alles gelaufen.

lg
QNo
Patrick Roemer
2016-07-14 17:47:54 UTC
Permalink
Post by Christian H. Kuhn
Vollständiger Code auf https://www.qno.de/gitweb/, Branch v0.8.4.
Vielleicht bin ich nur zu doof, aber ich sehe in diesem Interface keinen
Weg, das ganze Projekt einfach auszuchecken. Falls es das nicht gibt,
wäre ein Link auf ein Zip oder, noch besser, ein clone des Repos auf
github o.ä., sinnvoller.

Was auf den ersten Blick auffällt: "Echtzeit" (System.nanoTime(),
java.util.Timer, etc.) in den Kernklassen hart zu verdrahten, ist schon
wegen der Tests nicht so der Knüller - die brauchen ja jetzt schon ewig...

Ich würde das in irgendeine TimeSource-Abstraktion auslagern, die man in
den Tests mit gescripteten Zeitwerten/-events mocken kann. Das mag die
Sache durchaus etwas komplexer machen, ist aber IMHO "essential
complexity" - Zeit ist nun mal nicht einfach. :) Ich würde erwarten,
dass eine solche Umstellung das Design - unabhängig von den Tests -
letztendlich verbessert, weil die Abhängigkeiten vom Zeitverlauf dadurch
klarer ersichtlich werden sollten und man Threading (s.u.) besser
wegkapseln kann.
Post by Christian H. Kuhn
public class QChessClockJavaAV extends JFrame implements
QChessClockObserver, ActionListener {
Statt die komplette Eventlogik in einem einzigen ActionListener zu
dispatchen, würde ich jedem Widget (bzw. deren Models) einen eigenen
(ggfs. anonymen) ActionListener spendieren, bevorzugt in Form einer
javax.swing.Action.
Post by Christian H. Kuhn
resetButton.doClick() löst keine Aktion aus, oder zumindest wird sie
nicht von actionPerformed() in QChessClockJavaAV aufgefangen.
Dass das an resetButton.isEnabled() == false liegt, hast Du ja schon
selber herausgefunden. Sowas ist übrigens genau die Art von Logik, die
man in einer State Machine in einem Presentation Model kapseln und
unabhängig vom UI-Framework testen kann. :) Die Widgets (bzw. deren
Models) würden dann einfach mit den Events aus dem PM verdrahtet.

Ansonsten hast Du auf jeden Fall noch ein Threading-Problem. Sowohl der
Test (main-Thread) als auch das Kernmodell (Timer-Threads) werkeln
direkt auf dem UI-Zustand rum. Das muss alles über den Swing-EDT laufen.

Das ist kein rein akademisches Problem! Wenn ich im Debugger etwas mit
Breakpoints und Timings rumkaspere, schaffe ich es z.B., diesen Testfall
(natürlich nichtdeterministisch und nur selten) grün laufen zu lassen -
weil das Deaktivieren des Reset-Buttons noch nicht im EDT "angekommen"
ist und auch die stop-Action "untergeht". Das sieht dann etwa so aus:

<snip>
new state: NOT_STARTED
thread: main
from: QChessClock.addObserver(QChessClock.java:196)

new state: NOT_STARTED
thread: Timer-2
from: QChessClock$1.run(QChessClock.java:48)

new state: RUNNING
thread: main
from: QChessClock.leftPressed(QChessClock.java:78)

new state: NOT_STARTED
thread: main
from: QChessClock.resetPressed(QChessClock.java:136)
</snip>

Viele Grüße,
Patrick
Christian H. Kuhn
2016-07-14 22:08:10 UTC
Permalink
Post by Patrick Roemer
Post by Christian H. Kuhn
Vollständiger Code auf https://www.qno.de/gitweb/, Branch v0.8.4.
Vielleicht bin ich nur zu doof, aber ich sehe in diesem Interface keinen
Weg, das ganze Projekt einfach auszuchecken.
Ich hab auch gebraucht ... auschecken geht nicht, stattdessen kann man
einen Snapshot runterladen, das ist ein Zip-File. Aufs gewünschte
Projekt (oder dahinter auf shortlog) clicken, beim gewünschten Commit
auf Snapshot.
Post by Patrick Roemer
wäre ein Link auf ein Zip oder, noch besser, ein clone des Repos auf
github o.ä., sinnvoller.
GitHub kommt später. Ich habe versucht, apache und git mit „dummem“ http
aufzusetzen. Bislang ohne Erfolg, das schaue ich mir später nochmal an.
Wenn das läuft, ist ein einfaches Clonen über http möglich.
Post by Patrick Roemer
Was auf den ersten Blick auffällt: "Echtzeit" (System.nanoTime(),
java.util.Timer, etc.) in den Kernklassen hart zu verdrahten, ist schon
wegen der Tests nicht so der Knüller - die brauchen ja jetzt schon ewig...
Ich würde das in irgendeine TimeSource-Abstraktion auslagern, die man in
den Tests mit gescripteten Zeitwerten/-events mocken kann.
Ich hab ein wenig gebraucht, um zu begreifen, was du von mir willst :-)
Ich glaube verstanden zu haben: Wenn ich nicht System.nanoTime() nehme,
sondern eine AbstractTime.getTime(), dann kann ich „in echt“ den 50
ms-Takt zum Aktualisieren nehmen. Bei den Tests brauche ich keine
Rücksicht auf Zeitabläufe zu nehmen, sondern setze die Zeiten so, wie
ich sie brauche?
Post by Patrick Roemer
Presentation Model
Einer der nächsten Schritte. Autonomous view klappt jetzt, quick and
dirty. Da dort jedes Widget eine eigene Repräsentation seines Zustands
hat, bekommt auch jedes seinen eigenen ActionListener.
Post by Patrick Roemer
Ansonsten hast Du auf jeden Fall noch ein Threading-Problem. Sowohl der
Test (main-Thread) als auch das Kernmodell (Timer-Threads) werkeln
direkt auf dem UI-Zustand rum. Das muss alles über den Swing-EDT laufen.
Das ist kein rein akademisches Problem! Wenn ich im Debugger etwas mit
Breakpoints und Timings rumkaspere, schaffe ich es z.B., diesen Testfall
(natürlich nichtdeterministisch und nur selten) grün laufen zu lassen -
weil das Deaktivieren des Reset-Buttons noch nicht im EDT "angekommen"
Um mich da rauszuwinden, habe ich in den Tests mit hinreichend langen
Thread.sleep() gearbeitet. Und in der Tat gibt es Probleme. Die
Wartezeit muss durch Ausprobieren so gewählt werden, dass der Test
sowohl bei normaler Ausführung als auch bei der langsameren
Coverage-Ausführung (und natürlich auch mit Breakpoint) grün bleibt.
Unschön.

Im Studium hatte ich Multi-Threading nur im ProPra, und der einzige
wirkliche Lerneffekt war, dass der Datenfluss von einem Thread in den
anderen EWIG dauert.

Threadsicherheit, Transaktionen etc. hab ich mal gehört, im Prinzip
verstanden, aber noch keinen praktischen Kontakt gehabt. Schien mir hier
auch nicht nötig. In der gegebenen Situation (Schach) werden auch geübte
Spieler nicht mehr als 2 Züge pro Sekunde machen. Bei einem 50ms-Takt
passiert da nichts gravierendes. notifyObserver() überträgt immer nur
die aktuellen Änderungen, und aufeinanderfolgende Aufrufe werden da
keine vom Spieler wahrnehmbaren Störeffekte erzeugen. JUnit nimmt da
etwas filigraner wahr. Und das überspiele ich zur Zeit noch mit
Thread.sleep(). Mal schauen, was ich da wie in den EDT bekomme.

Wobei ich denke, dass der TimerTask in QChessTimer unproblematisch ist.
Zur Not wird auf flagFallen synchronisiert. Den TimerTask in QChessClock
kann man womöglich ganz wegbekommen, wenn QChessTimer in seinem Takt auf
Veränderungen in den Sekunden testet und dann QChessClock benachrichtigt.

Wie das externe Setzen von GUI-Bestandteilen in den EDT rein soll,
erschließt sich mir noch nicht. Im Moment sehe ich eigentlich nur, für
ein Update von Eigenschaften entweder ein spezielles (unsichtbares) noch
einzufügendes Element der GUI mit doClick() auszulösen oder einem
ActionListener ein Event zuzuschicken. Beides wäre auf Swing
spezialisiert, und für eine andere Oberfläche müsste ich das Modell
umschreiben. Unschön. Ja, inzwischen weiss ich. Presentation Model hat
das Problem nicht :-)

Ich glaube, der nächste Schritt wird die Umstellung auf Presentation
Model. Da unterhält sich nur das PM mit der GUI.

lg
QNo
Patrick Roemer
2016-07-15 20:04:35 UTC
Permalink
Post by Christian H. Kuhn
Ich glaube verstanden zu haben: Wenn ich nicht System.nanoTime() nehme,
sondern eine AbstractTime.getTime(), dann kann ich „in echt“ den 50
ms-Takt zum Aktualisieren nehmen. Bei den Tests brauche ich keine
Rücksicht auf Zeitabläufe zu nehmen, sondern setze die Zeiten so, wie
ich sie brauche?
Genau. Naiv erst mal:

interface TimeSource {
long getTimeInNanoSecs();
}

class ChessTimer {
public ChessTimer(TimeSource timeSource) {
// ...
}
// ...
}

class SystemTimeSource implements TimeSource {
@override public long getTimeInNanoSecs() {
return System.nanoSecs();
}
}

...und für Tests dann sowas:

class ScriptedTimeSource implements TimeSource {
private long time;

@override public long getTimeInNanoSecs() {
return time;
}

public void set(long time) {
this.time = time;
}
}

...oder ähnlich - kann man je nach Bedarf komplexer gestalten, mit
EasyMock "implementieren", was immer für den konkreten Test am
günstigsten ist.

Das ist aber nur die halbe Miete. Man würde dann auch den konkreten
j.u.Timer in der Kernklasse wegabstrahieren wollen. Dafür könnte man ein
eigenes Interface für die Funktionalität von j.u.Timer erstellen, usw.
Alternativ/zusätzlich könnte man das Design so ändern, dass der
ChessTimer nur eine Methode #checkFlag() hat, die von einem *externen*
Timer aufgerufen wird. Dann braucht man "weiter oben" natürlich wieder
eine Entität, die externen Timer und ChessTimer zusammenführt und muss
die separat testen - aber man hat die Verantwortlichkeiten getrennt und
die "Problemfläche" für Threading reduziert.
Post by Christian H. Kuhn
Im Studium hatte ich Multi-Threading nur im ProPra, und der einzige
wirkliche Lerneffekt war, dass der Datenfluss von einem Thread in den
anderen EWIG dauert.
Dann war das ProPra für den Fuß. :)
Post by Christian H. Kuhn
Threadsicherheit, Transaktionen etc. hab ich mal gehört, im Prinzip
verstanden, aber noch keinen praktischen Kontakt gehabt. Schien mir hier
auch nicht nötig. In der gegebenen Situation (Schach) werden auch geübte
Spieler nicht mehr als 2 Züge pro Sekunde machen. Bei einem 50ms-Takt
passiert da nichts gravierendes. notifyObserver() überträgt immer nur
die aktuellen Änderungen, und aufeinanderfolgende Aufrufe werden da
keine vom Spieler wahrnehmbaren Störeffekte erzeugen.
Es ist völlig egal, welche Frequenz die Aufrufe in den einzelnen Threads
haben. Solange es keine "happens-before"-Beziehung zwischen Aktion A in
Thread 1 und Aktion B in Thread 2 gibt, hast Du keinerlei Garantie, dass
Thread 2 die Resultate von Aktion A je sehen wird, egal, wieviel
Realzeit zwischen den Aktionen liegt.
Post by Christian H. Kuhn
Wobei ich denke, dass der TimerTask in QChessTimer unproblematisch ist.
Ist er nicht. #flagFallen müsste mindestens volatile sein, da von
mehreren Threads drauf zugegriffen wird.
Post by Christian H. Kuhn
Wie das externe Setzen von GUI-Bestandteilen in den EDT rein soll,
erschließt sich mir noch nicht.
Über SwingUtilities#invokeLater().

Viele Grüße,
Patrick
Christian H. Kuhn
2016-07-15 20:53:25 UTC
Permalink
Post by Patrick Roemer
Dann war das ProPra für den Fuß. :)
Naja. Nicht ganz. Das erste größere Projekt, das ich mehr oder weniger
alleine bearbeitet habe. Eclipse und Android 2.3.3 halbwegs
kennengelernt. Halt Anfängerpraktikum. Die Betreuer waren engagiert, die
Aufgabenstellung kreativ und motivierend. Es gibt aber effektiveres Lernen.

lg
QNo
Christian H. Kuhn
2016-07-15 21:09:27 UTC
Permalink
Post by Patrick Roemer
interface TimeSource {
long getTimeInNanoSecs();
}
Alternativ/zusätzlich könnte man das Design so ändern, dass der
ChessTimer nur eine Methode #checkFlag() hat, die von einem *externen*
Timer aufgerufen wird. Dann braucht man "weiter oben" natürlich wieder
eine Entität, die externen Timer und ChessTimer zusammenführt und muss
die separat testen - aber man hat die Verantwortlichkeiten getrennt und
die "Problemfläche" für Threading reduziert.
Der Gedanke kam mir auch schon. Die Kernfunktionalität von QChessTimer
ist einfach das unterbrechbare Runterzählen und Ausliefern der Zeit. Ob
die Null erreicht ist, kann auch QChessClock prüfen. Man kann auch
argumentieren, dass das semantisch besser ist.
Post by Patrick Roemer
Es ist völlig egal, welche Frequenz die Aufrufe in den einzelnen Threads
haben. Solange es keine "happens-before"-Beziehung zwischen Aktion A in
Thread 1 und Aktion B in Thread 2 gibt, hast Du keinerlei Garantie, dass
Thread 2 die Resultate von Aktion A je sehen wird, egal, wieviel
Realzeit zwischen den Aktionen liegt.
Ja. Das ist die Theorie. Schreib-Lese-Konflikte. In dem Fall allerdings
nicht praktisch relevant. Das Blockadepotential der Anwendung ist eine
Größenordnung kleiner als die Wahrnehmung der Spieler. Und wenn externe
Blockaden auftreten, bleibt sowieso irgendwas unzumutbar stehen.

Weil es mir aber weniger darum geht, eine funktionierende Schachuhr zu
schreiben, als das Problem theoretisch zu verstehen und gewissermaßen
optimal zu lösen, kümmere ich mich natürlich auch um solche Konflikte.

lg
QNo
Patrick Roemer
2016-07-15 21:44:25 UTC
Permalink
Post by Christian H. Kuhn
Post by Patrick Roemer
Alternativ/zusätzlich könnte man das Design so ändern, dass der
ChessTimer nur eine Methode #checkFlag() hat, die von einem *externen*
Timer aufgerufen wird. Dann braucht man "weiter oben" natürlich wieder
eine Entität, die externen Timer und ChessTimer zusammenführt und muss
die separat testen - aber man hat die Verantwortlichkeiten getrennt und
die "Problemfläche" für Threading reduziert.
Der Gedanke kam mir auch schon. Die Kernfunktionalität von QChessTimer
ist einfach das unterbrechbare Runterzählen und Ausliefern der Zeit. Ob
die Null erreicht ist, kann auch QChessClock prüfen. Man kann auch
argumentieren, dass das semantisch besser ist.
Das wäre noch eine andere Variante.

Ich meinte eher, dass QChessTimer zunächst mal eine State Machine ist,
die anhand von eingehenden Events zwischen Idle, Stopped, Started und
Flagged o.s.ä. wechselt. Momentan verwaltet die Klasse zusätzlich noch
eine der Eventquellen (den Timer). Die könnte man da komplett
rausziehen. Dann hätte man eine (quasi "rein funktionale") Klasse mit
der Logik für die Zustandsübergänge. Und die könnte man in einer
"übergeordneten" Klasse (die dem jetzigen QChessTimer entspricht) mit
einer Timerabstraktion zusammenschalten.
Post by Christian H. Kuhn
Post by Patrick Roemer
Es ist völlig egal, welche Frequenz die Aufrufe in den einzelnen Threads
haben. Solange es keine "happens-before"-Beziehung zwischen Aktion A in
Thread 1 und Aktion B in Thread 2 gibt, hast Du keinerlei Garantie, dass
Thread 2 die Resultate von Aktion A je sehen wird, egal, wieviel
Realzeit zwischen den Aktionen liegt.
Ja. Das ist die Theorie. Schreib-Lese-Konflikte. In dem Fall allerdings
nicht praktisch relevant. Das Blockadepotential der Anwendung ist eine
Größenordnung kleiner als die Wahrnehmung der Spieler. Und wenn externe
Blockaden auftreten, bleibt sowieso irgendwas unzumutbar stehen.
Ich dachte mir schon, dass Schachspieler eher so die geduldigen und
sanguinischen Typen sind, aber dass sie es entspannt hinnehmen, wenn im
Turnierfinale irgendwas (außer ihrer Deckung, natürlich) unzumutbar
stehenbleibt, erstaunt mich dann doch. :D

Abgesehen davon: Das ist nicht nur Theorie, das passiert. In echt. Und
das konkret angesprochene Problem hat (erst mal) nix mit Blockaden und
Konflikten zu tun. Es kann schlicht vorkommen, dass der Timer flagFallen
auf true setzt, und der Main-Thread da nie etwas von mitbekommt. Und
somit die Uhr unzumutbar *nicht* stehenbleibt...

Viele Grüße,
Patrick
Christian H. Kuhn
2016-07-16 21:44:45 UTC
Permalink
Post by Patrick Roemer
Post by Christian H. Kuhn
Der Gedanke kam mir auch schon. Die Kernfunktionalität von QChessTimer
ist einfach das unterbrechbare Runterzählen und Ausliefern der Zeit. Ob
die Null erreicht ist, kann auch QChessClock prüfen. Man kann auch
argumentieren, dass das semantisch besser ist.
Das wäre noch eine andere Variante.
Ich meinte eher, dass QChessTimer zunächst mal eine State Machine ist,
die anhand von eingehenden Events zwischen Idle, Stopped, Started und
Flagged o.s.ä. wechselt. Momentan verwaltet die Klasse zusätzlich noch
eine der Eventquellen (den Timer). Die könnte man da komplett
rausziehen. Dann hätte man eine (quasi "rein funktionale") Klasse mit
der Logik für die Zustandsübergänge. Und die könnte man in einer
"übergeordneten" Klasse (die dem jetzigen QChessTimer entspricht) mit
einer Timerabstraktion zusammenschalten.
Ok. Konkret: Zwei Zustände, started und stopped. idle === stopped.
flagged ist für QChessTimer kein eigener Zustand mehr, sondern wird
höheren Ortes angesiedelt. Da wird einfach überprüft, ob die
verbleibende Zeit noch > 0 ist, ansonsten Blättchen. Damit entfällt der
TimerTask.

Zustandsübergang durch start() und stop(). Eigentlich unparametrisiert,
weil ja auf die Systemzeit zurückgegriffen wurde. Dann auch mit
Parameter, damit der zu stoppende QChessTimer „zur gleichen Zeit“
angehalten werden kann wie der zu startende gestartet wird. Wenn einfach
start() und stop() hintereinander ausgeführt werden, kann da schon mal
ne ms oder vier dazwischen liegen. Verlängert die Partie nur um
Sekundenbruchteile, aber es soll ja sauber sein. Also wird die
unparametrisierte Variante gestrichen. Auf eine Zeitquelle muss nur noch
zur Zeitabfrage bei laufender Uhr zurückgegriffen werden.

Den Vorteil einer Abstraktion der Zeitquelle habe ich noch nicht
erkannt. Im praktischen Einsatz wird dies die Systemuhr sein, eine
Alternative kann ich mir gerade nicht vorstellen. Und dann würde ich
überflüssige Funktionalität entwickeln, nur damit ich die testen kann ...

Die Zeitquelle im QChessTimer könnte übrigens komplett entfallen, wenn
das Auslesen des Zustands nicht die Zeit, sondern ein Objekt übergibt,
das verbleibende Zeit und Zustandsname enthält. Bei laufender Uhr wird
remainingTime + startTime zurückgegeben; wenn der Zustand running ist,
muss der Aufrufer sehen, woher er die aktuelle Zeit nimmt, zieht die ab
und hat die verbleibende Zeit; bei stopped bekommt er schon den
korrekten Wert. QChessTimer würde also endgültig nichts mehr mit
laufender Zeit zu tun haben, sondern nur noch von außen mitgeteilte
Marken verwalten.

Ob man tatsächlich intern die Zeit in ns führt, aber nach außen s
ausgibt ... da ist ein Faktor von einer Million dazwischen. Es könnte
reichen, intern in ms zu rechnen. Problem: Beim Test, ob die Uhr läuft,
könnten zwei Abfragen hintereinander so schnell aufeinanderfolgen, dass
die ms sich nicht verändert haben und ein Test falsch fehlschlägt.

Damit sind die Problempunkte aus QChessTimer herausgelöst. Zugriff
erfolgt von QChessClock, aber bis zum Beweis des Gegenteils aus mehreren
Threads: Einem main-Thread, der die Timer erzeugt (reset erfolgt über
neuen Konstruktor), Knopfdruckaktionen können in einem eigenen Thread,
z.B. Swing-EDT, ausgelöst werden, und ein möglicher eigener Thread zur
Aktualisierung der Restzeit und der Fallblättchenüberprüfung.
QChessTimer muss also thread safe geschrieben werden. Zugriffe auf int,
long und boolean sind atomar. Die vier verbleibenden paketöffentlichen
Funktionen müssten synchronized werden.

Aber jetzt erstmal schlafen. Threadsicher und ohne interrupts ;-)

lg
QNo
Stefan Ram
2016-07-17 01:01:50 UTC
Permalink
Post by Christian H. Kuhn
Zustandsübergang durch start() und stop(). Eigentlich unparametrisiert,
Ich würde das vielleicht so machen (habe aber im Moment keine Zeit,
es wirklich zu schreiben):

Zunächst einmal das Uhrwerk für jede der beiden einzelnen Uhren,
ungetestet:

final class Clock
{ long duration;
long startTime;
long endTime;
boolean isRunning;

public void start( final long point )
{ if( this.isRunning )throw new java.lang.IllegalStateException();
if( point < endTime )
throw new java.lang.IllegalArgumentException();
this.startTime = point;
this.isRunning = true; }

public void end( final long point )
{ if( !this.isRunning )throw new java.lang.IllegalStateException();
if( point < startTime )
throw new java.lang.IllegalArgumentException();
this.endTime = point;
this.duration += point - startTime;
this.isRunning = false; }

public long duration( final long point )
{ return this.duration +
( this.isRunning ? point - this.startTime : 0 ); }}

. Dabei stelle ich mir vor, daß /alles/ auf dem EDT (Ereignisverlauf)
läuft, es also nur einen Thread gibt.

Es gibt folgende Ereignisse (vereinfacht):

S - Das Programm startet
A - Spieler startet Uhr
O - Spieler stoppt Uhr
R - Die Anzeige soll aktualisiert werden

Beim Aufbauen der Swing-Oberfläche wird ohnehin
invokeAndWait verwendet (S), alle anderen Ereignisse sollten
von Swing kommen und damit schon auf dem EDT laufen.

S: Es wird mit »clock = new Clock()« eine neue Uhr erzeugt
(ich nehme zur Vereinfachung nur eine Uhr an).

A: Es wird »clock.start( now() )« aufgerufen, wobei »now()«
die aktuelle Zeit in (beispielsweise) Nanosekunden ist.

O: Es wird »clock.end( now() )« aufgerufen.

R: Ich nehme an, daß Android und Swing es erlauben, sich
regelmäßig Timer-Ereignisse schicken zu lassen, am besten
synchronisiert mit der Bildschirmauffrischung und diese auch
gleich auf dem EDT laufen. Dann wird mit »clock.duration( now() )«
die aktuelle Zeit der Uhr abgefragt und angezeigt.

Unter Android läuft ohnehin normalerweise alles auf dem UI thread.

Ich würde allerdings gerade bei einem »Lernprojekt« dann
auch JavaFX verwenden, da man ja für die Zukunft lernen will
und nicht für die Vergangenheit.
Stefan Ram
2016-07-17 10:35:03 UTC
Permalink
Post by Stefan Ram
Ich würde das vielleicht so machen (habe aber im Moment keine Zeit,
Die Modellklassen wurden nun um eine Klasse »Clock«
erweitert, die zwei Uhrwerke »Unit« enthält.
Damit kann sie eine vereinfachte Art von Schachuhr
imitieren.

Mit »toggle« kann man der Schachuhr sagen, daß jetzt
der andere Spieler zu denken beginnt, und mit »out«
einen Timeout für beide Spieler initiieren.

Dann gibt es einen kleinen Beispielklienten »Main«.

final class Unit
{ long duration;
long startTime;
long endTime;
boolean isRunning;

public void start( final long point )
{ if( this.isRunning )throw new java.lang.IllegalStateException();
if( point < endTime )
throw new java.lang.IllegalArgumentException();
this.startTime = point;
this.isRunning = true; }

public void end( final long point )
{ if( this.isRunning )
{ if( point < startTime )
throw new java.lang.IllegalArgumentException();
this.endTime = point;
this.duration += point - startTime;
this.isRunning = false; }}

public long duration( final long point )
{ return this.duration +
( this.isRunning ? point - this.startTime : 0 ); }}

final class Clock
{ final Unit unit0 = new Unit();
final Unit unit1 = new Unit();
boolean isRunning;

public void start0( final long point )
{ this.unit0.start( point );
this.unit1.end( point );
this.isRunning = true; }

public void start1( final long point )
{ this.unit0.end( point );
this.unit1.start( point );
this.isRunning = false; }

public void toggle( final long point )
{ if( this.isRunning )start1( point );
else start0( point ); }

public void pause( final long point )
{ this.unit0.end( point );
this.unit1.end( point ); }

public void cont( final long point )
{ if( this.isRunning )start0( point );
else start1( point ); }

public java.awt.geom.Point2D.Double duration( final long point )
{ return
new java.awt.geom.Point2D.Double
( this.unit0.duration( point ), this.unit1.duration( point )); }

public java.lang.String toString( final long point )
{ return java.lang.String.format( "time: %3d [S0: %3d, S1: %3d]", point,
this.unit0.duration( point ), this.unit1.duration( point )); }}

public final class Main
{
private static final void print( final java.lang.String arg )
{ java.lang.System.out.print( arg + " " ); }

private static final void println( final java.lang.String arg )
{ java.lang.System.out.println( arg ); }

public static void main( final java.lang.String[] args )
{ final Clock clock = new Clock();
java.lang.System.out.println( "chess clock" );
long time;
time = 0;
java.lang.System.out.printf( "%59s%n", "Spieler S0 beginnt zu denken." );
print( clock.toString( time )); clock.start0( time ); time += 10;
println( "Nach 10 Sekunden beginnt der andere Spieler S1 zu denken." );
print( clock.toString( time )); clock.toggle( time ); time += 20;
println( "Nach 20 Sekunden beginnt der erste Spieler S0 zu denken." );
print( clock.toString( time )); clock.toggle( time ); time += 30;
println( "Nach 30 Sekunden beginnt der andere Spieler S1 zu denken." );
print( clock.toString( time )); clock.toggle( time ); time += 40;
println( "Nach 40 Sekunden beginnt ein Timeout." );
print( clock.toString( time )); clock.pause( time ); time += 50;
println( "Nach 50 Sekunden endet das Timeout." );
print( clock.toString( time )); clock.cont( time ); time += 60;
println( "Nach 60 Sekunden hört der andere Spieler S1 auf zu denken." );
print( clock.toString( time )); }}

Und die Ausgabe lautet (gekuerzt fuer die Zeilenbreite):

chess clock
Spieler S0 beginnt zu denken.
0 [0: 0, 1: 0] Nach 10 Sekunden beginnt S1 zu denken.
10 [0: 10, 1: 0] Nach 20 Sekunden beginnt S0 zu denken.
30 [0: 10, 1: 20] Nach 30 Sekunden beginnt S1 zu denken.
60 [0: 40, 1: 20] Nach 40 Sekunden beginnt ein Timeout.
100 [0: 40, 1: 60] Nach 50 Sekunden endet das Timeout.
150 [0: 40, 1: 60] Nach 60 Sekunden hört S1 auf zu denken.
210 [0: 40, 1: 120]
Christian H. Kuhn
2016-07-17 13:49:19 UTC
Permalink
Post by Stefan Ram
Die Modellklassen wurden nun um eine Klasse »Clock«
erweitert, die zwei Uhrwerke »Unit« enthält.
Damit kann sie eine vereinfachte Art von Schachuhr
imitieren.
Warum sieht das bei dir soviel aufgeräumter auf als bei mir? :-)

Ok, du hast die Zeitquelle jetzt auch noch aus Clock herausgezogen und
in die Verantwortung des Benutzers, also des UI, gegeben. Kann man so
machen. Es gibt ein Argument, das dagegen spricht.

Ich bin der Auffassung, dass die Feststellung einer Zeitüberschreitung
entweder in Unit oder in Clock gehört. Unit kann ein allgemeiner Timer
sein, der über Zeitüberschreitung nichts zu wissen braucht, dann gehört
der Überschreitungsalarm nach Clock. Deine Unit zählt die Zeit hoch, ich
brauche eigentlich einen Countdown, das ist dann eine Zeile mehr, und
dann kann man auch argumentieren, dass das Erreichen der 0 von Unit
festgestellt werden muss. Geht beides, ich neige im Moment mehr dazu,
das in Clock zu tun.

Wenn ich aber die Zeitüberschreitung in Clock feststelle, kann ich das
eigentlich nur machen, indem ich regelmäßig duration von beiden Unit
abfrage. Nicht nur von der laufenden, weil ja die andere Uhr zwischen
letzter Abfrage und letztem toggle die Zeit überschritten haben kann.
Damit muss Clock eine eigene Vorstellung von now haben.

Die Alternative wäre, dass das UI getaktet Clock.isFlagFallen(now())
aufrufen müsste. Hm. Bauch sagt nein ...

Das wird eine grundsätzliche Design-Frage. Ich folge bislang dem
Observer-Muster: Clock als Observable verwaltet die eigenen Zustände und
benachrichtigt seine Observer über Änderungen. Das (G)UI ist dumm. Es
trifft keine Feststellungen über Zustände und Übergänge, es stellt
einfach dar, was es mitgeteilt bekommt.

Indem du Clock derart vereinfacht hast, wird mehr Logik in das (G)UI
verfrachtet. Die Frage, wie oft auf welche Zustandsänderungen überprüft
wird, ist sehr abhängig von der Implementation von Clock. Im Moment mag
das kein Problem sein. Sobald der volle Funktionsumfang einer
Turnierschachuhr (mehrere verschiedene Bedenkzeitperioden, Zugzähler,
Bronstein-Bedenkzeit, Editierfunktionen für den Schiedsrichter)
implementiert wird, befürchte ich eine Verletzung der Kapselung.

lg
QNo
Stefan Ram
2016-07-17 14:09:35 UTC
Permalink
Post by Christian H. Kuhn
der Überschreitungsalarm nach Clock. Deine Unit zählt die Zeit hoch, ich
brauche eigentlich einen Countdown, das ist dann eine Zeile mehr, und
dann kann man auch argumentieren, dass das Erreichen der 0 von Unit
festgestellt werden muss. Geht beides, ich neige im Moment mehr dazu,
das in Clock zu tun.
Ich wußte nicht, daß Schachuhren abwärts zählen, aber werde
das dann zukünftig berücksichtigen.
Post by Christian H. Kuhn
Die Alternative wäre, dass das UI getaktet Clock.isFlagFallen(now())
aufrufen müsste. Hm. Bauch sagt nein ...
Ich stelle mir vor, daß die Oberfläche ohnehin dafür sorgen
muß, die Anzeige regelmäßig zu aktualisieren. Dabei kann sie
dann auch gleich die Prüfung des Fallblättchens auslösen.

Bei einer reinen Standard-Java-Konsolenoberfläche, die nur
einfache Methoden in der Art wie »println« vorsieht, müßte
man dann in einer Schleife immer wieder die neuen Zeiten
ausgeben. Außerdem benötigt man noch die Möglichkeit
/nichtblockierend/ zu testen, ob der Bediener eine Taste
gedrückt hat. Ich weiß nicht, ob das mit Standard-Java
möglich ist. »java.io.Console« hat es wohl nicht. Oder man
wartet blockierend auf eine Eingabe, die mit der Eingabe-
taste abgeschlossen werden muß, aber kann dann nicht ständig
die aktuellen Zeiten ausgeben. Das wäre dann mehr etwas
für Entwickler, aber nicht für Schachspieler.
Post by Christian H. Kuhn
das kein Problem sein. Sobald der volle Funktionsumfang einer
Turnierschachuhr (mehrere verschiedene Bedenkzeitperioden, Zugzähler,
Bronstein-Bedenkzeit, Editierfunktionen für den Schiedsrichter)
implementiert wird, befürchte ich eine Verletzung der Kapselung.
Da kenne ich mich eben nicht aus, also kann ich so etwas
auch nicht implementieren. Ich habe lediglich eine Pause
beider Uhrenwerke vorgesehen, falls beide Spiele mit dem
Schiedsrichter diskutieren wollen, da ich davon im Web
gelesen hatte.
Christian H. Kuhn
2016-07-17 15:03:28 UTC
Permalink
Post by Stefan Ram
Ich wußte nicht, daß Schachuhren abwärts zählen, aber werde
das dann zukünftig berücksichtigen.
Früher taten sie es nicht. In der analogen Welt waren das zwei ganz
normale Uhrenwerke. Die Uhr lief vorwärts. Ein paar Minuten vor der
vollen Stunde begann der Minutenzeiger (bei ein paar speziellen Modellen
auch ein anderer Mechanismus), das Fallblättchen anzuheben. Bei
Erreichen der vollen Stunde verlor das Blättchen seinen Halt auf dem
Zeiger und fiel wieder in die Ruhestellung. Eine Konvention, die mäßig
eingehalten wurde, verlangte, dass die Uhr so zu stellen sei, dass um
06:00:00 Uhr Partieende ist. Also wird für Blitzschach (5min pro Spieler
und Partie) die Uhr auf 05:55:00 gestellt. Für die übliche Bedenkzeit
bei Amateur-Turnierpartien (2h für die ersten 40 Züge, 1h für den Rest)
dann auf 03:00:00).

Interessant war aber nie die Uhrenstellung, sondern die verbleibende
Zeit. Dem Blitzspieler war es egal, ob da jetzt 5:58:32 oder 10:58:32
angezeigt wird. Wichtig war die Info: 1:28 min verbleiben. Mit dem
Aufkommen digitaler Schachuhren (die auch erst moderne Bedenkzeiten mit
Zeitzuschlägen pro Zug ermöglichten) ging man dann dazu über, die
verbleibende Bedenkzeit anzuzeigen und folglich rückwärts zu zählen.

Da Displays offensichtlich teuer sind, ist es zur Zeit bei den
handelsüblichen Modellen üblich, dass die Zeit zunächst in hh:mm
angezeigt wird. Sobald eine Restzeit von (je nach Modell) 10 oder 20
Minuten unterschritten wird, wechselt die Anzeige auf mm.ss. So spart
man zwei Stellen ein.

Das Fallblättchen wird durch ein Sonderzeichen, z.B. durch einen
führenden Unterstrich oder einen Apostroph, angezeigt. Bei
Bedenkzeitregelungen mit mehreren Zeitperioden kann es für jede Periode
ein eigenes Blättchen geben. Es kann aber auch kurz vor der nächsten
Zeitkontrolle das alte gelöscht und wiederverwertet werden.

Manche Uhren zeigen an, in der wievielten Bedenkzeitperiode man sich
befindet. Manche Uhren zeigen an, auf welcher Seite Weiß sitzt. Alle
Uhren zeigen an, welche Seite am Zug befindlich ist; entweder durch das
Blinken des Trennzeichens zwischen den hh und mm bzw. zwischen mm und
ss, das dann auch das Laufen der Uhr anzeigt, oder durch ein
zusätzliches Zeichen im Display, z.B. einen Pfeil, oder auch durch eine
Leuchte am Gehäuse.
Post by Stefan Ram
Da kenne ich mich eben nicht aus, also kann ich so etwas
auch nicht implementieren. Ich habe lediglich eine Pause
beider Uhrenwerke vorgesehen, falls beide Spiele mit dem
Schiedsrichter diskutieren wollen, da ich davon im Web
gelesen hatte.
Ein durchaus realistisches Szenario. Und damit weisst du in dem Punkt
mehr als 80% der Schachspieler, die die Uhr für Diskussionen nicht
anhalten und sich danach über die abgelaufene Bedenkzeit beschweren :-)

Du hast also den durchaus richtigen Ansatz verfolgt, dass man erstmal
die aktuellen Anforderungen implementiert und sein Design nicht an
möglichen zukünftigen Anforderungen orientiert. Ich habe es hier nicht
kommuniziert, weiss aber, dass da noch mehr kommt. Und bevor ich schon
wieder alles umschreibe, behalte ich das von Anfang an im Kopf.
Post by Stefan Ram
Bei einer reinen Standard-Java-Konsolenoberfläche, die nur
einfache Methoden in der Art wie »println« vorsieht, müßte
man dann in einer Schleife immer wieder die neuen Zeiten
ausgeben. Außerdem benötigt man noch die Möglichkeit
/nichtblockierend/ zu testen, ob der Bediener eine Taste
gedrückt hat.
Geht wohl nicht in einem Thread. Scheint aber kein Problem zu sein, wenn
ein Thread ausgibt und ein Thread blockierend wartet:

public class Threadspiel {
public static void main(final String[] _args) {

Thread thread1 = new Thread(() -> {
while (true) {
System.out.print("a");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
});

Thread thread2 = new Thread(() -> {
while (true) {
try {
if (0 < System.in.available()) {
System.out.println(System.in.read());
}
} catch (IOException e) {
}
}
});

thread1.start();
thread2.start();
}
}

lg
QNo
Stefan Ram
2016-07-17 22:22:55 UTC
Permalink
Post by Christian H. Kuhn
Geht wohl nicht in einem Thread. Scheint aber kein Problem zu sein, wenn
Sehr interessant! Diese Möglichkeit kannte ich noch nicht.

Inzwischen habe ich mein Model mal mit einer einfachen
JavaFX-GUI verbunden. Das sollte mit dem JDK 8 direkt
kompiliert und gestartet werden können, wenn es in eine
Datei »Main.java« kopiert wird.

Zum Spielstart muß Schwarz auf [black] klicken. Dadurch
beginnt die Zeit für Weiß zu laufen. Wenn Weiß fertig ist,
sollte er auf [white] klicken, wodurch die Zeit für Schwarz zu
laufen beginnt. [pause] und [continue] sind selbsterklärend.

Beide Spieler haben zwei Stunden Zeit. Die verbleibende Zeit
wird für jeden Spieler in Nanosekunden angezeigt. Ich habe
es noch nicht so lange laufen lassen, aber stelle mir vor,
daß nach Überschreitung der Zeit negative Zahlen erscheinen,
wodurch das Minuszeichen als eine Art von Fallblättchen dient.

Die Model-Klassen wissen absolut nichts von Swing, JavaFX,
Timers, dem EDT oder ähnlichen, so daß ich hoffe, daß sie
auch mit anderen Oberflächen als JavaFX verwendet werden
könnten.

final class Unit
{ long duration;
long startTime;
long endTime;
boolean isRunning;

public void start( final long point )
{ if( !this.isRunning )
{ if( point < endTime )
throw new java.lang.IllegalArgumentException();
this.startTime = point;
this.isRunning = true; }}

public void end( final long point )
{ if( this.isRunning )
{ if( point < startTime )
throw new java.lang.IllegalArgumentException();
this.endTime = point;
this.duration += point - startTime;
this.isRunning = false; }}

public long duration( final long point )
{ return this.duration +
( this.isRunning ? point - this.startTime : 0 ); }}

class Clock
{ final Unit unit0 = new Unit();
final Unit unit1 = new Unit();
boolean unit0IsRunning;

public void start0( final long point )
{ this.unit0.start( point );
this.unit1.end( point );
this.unit0IsRunning = true; }

public void start1( final long point )
{ this.unit0.end( point );
this.unit1.start( point );
this.unit0IsRunning = false; }

public void pause( final long point )
{ this.unit0.end( point );
this.unit1.end( point ); }

public void cont( final long point )
{ if( this.unit0IsRunning )this.start0( point );
else this.start1( point ); }

public long duration0( final long point )
{ return this.unit0.duration( point ); }

public long duration1( final long point )
{ return this.unit1.duration( point ); }

public java.lang.String toString( final long point )
{ return java.lang.String.format( "time: %3d [S0: %3d, S1: %3d]", point,
this.duration0( point ), this.duration1( point )); }}

/* Diese Klasse ist eine Art von Adapterklasse, welche die aufwaertszaehlenden
Uhren "Clock" in abwaertszaehlende Uhren "Credit" wandelt. */

final class Credit extends Clock
{
long credit0;
long credit1;

public void setCredit0( final long credit0 )
{ this.credit0 = credit0; }

public void setCredit1( final long credit1 )
{ this.credit1 = credit1; }

public long getCredit0()
{ return this.credit0; }

public long getCredit1()
{ return this.credit1; }

public long duration0( final long point )
{ return this.credit0 - super.duration0( point ); }

public long duration1( final long point )
{ return this.credit1 - super.duration1( point ); }}

/* --- the model ends here --- the application begins here --- */

public final class Main extends javafx.application.Application
{
class Timer extends javafx.animation.AnimationTimer
{ public void handle( final long time )
{ Main.this.handle( time ); }}

javafx.scene.control.TextField text0;
javafx.scene.control.TextField text1;

Credit clock;

private final void handle( final long time )
{ text0.setText( java.lang.String.valueOf( clock.duration0( time ) ));
text1.setText( java.lang.String.valueOf( clock.duration1( time ) )); }

public void start( final javafx.stage.Stage primaryStage )
{ text0 = new javafx.scene.control.TextField();
text1 = new javafx.scene.control.TextField();

final javafx.scene.layout.HBox clocks
= new javafx.scene.layout.HBox();

clocks.getChildren().add( text0 );
clocks.getChildren().add( text1 );

final javafx.scene.control.Button button0
= new javafx.scene.control.Button( "white" );

final javafx.scene.control.Button button1
= new javafx.scene.control.Button( "black" );

final javafx.scene.control.Button buttonPause
= new javafx.scene.control.Button( "pause" );

final javafx.scene.control.Button buttonCont
= new javafx.scene.control.Button( "continue" );

button0.setOnAction
( block ->
{ clock.start1( java.lang.System.nanoTime() ); });

button1.setOnAction
( block ->
{ clock.start0( java.lang.System.nanoTime() ); });

buttonPause.setOnAction
( block ->
{ clock.pause( java.lang.System.nanoTime() ); });

buttonCont.setOnAction
( block ->
{ clock.cont( java.lang.System.nanoTime() ); });

final javafx.scene.layout.HBox buttons
= new javafx.scene.layout.HBox();

buttons.getChildren().add( button0 );
buttons.getChildren().add( button1 );
buttons.getChildren().add( buttonPause );
buttons.getChildren().add( buttonCont );

final javafx.scene.layout.VBox all
= new javafx.scene.layout.VBox();

all.getChildren().add( clocks );
all.getChildren().add( buttons );

final javafx.scene.Scene scene = new javafx.scene.Scene( all );
primaryStage.setScene( scene );

clock = new Credit();
clock.setCredit0( 120L * 60L * 1_000_000_000L );
clock.setCredit1( 120L * 60L * 1_000_000_000L );

primaryStage.show();
this.new Timer().start(); }}
Stefan Ram
2016-07-17 23:04:34 UTC
Permalink
Post by Stefan Ram
public long duration( final long point )
{ return this.duration +
( this.isRunning ? point - this.startTime : 0 ); }}
Ich könnte den Aufruf von java.lang.System.nanoTime()
natürlich in solche Funktionen wie die obige einbauen,
um den Wert von »point« festzulegen.

Allerdings könnte es sein, daß nanoTime() unter einigen
Platformen schlechter unterstützt wird, und man auf eine
andere Zeitfunktion ausweichen will.
Post by Stefan Ram
public void start0( final long point )
{ this.unit0.start( point );
this.unit1.end( point );
this.unit0IsRunning = true; }
Und dann ist es so, daß durch die Übergabe der Zeit als
Argumentwert die beiden Methoden »start« und »end« oben
genau dieselbe Zeit sehen.

Wenn »start« und »end« jeweils intern »nanoTime« aufrufen
würden, dann könnten sie zwei verschiedene Zeiten sehen,
so daß dann der Startzeitpunkt des einen Uhrwerks von dem
Endzeitpunkt des anderen abweichen könnte.

Außerdem sind die Klassen auch leichter zu testen,
wenn die Tests ihnen einfach die Uhrzeiten geben können,
mit denen sie getestet werden sollen.
Stefan Ram
2016-07-18 18:38:29 UTC
Permalink
Post by Stefan Ram
Die Model-Klassen wissen absolut nichts von Swing, JavaFX,
Timers, dem EDT oder ähnlichen, so daß ich hoffe, daß sie
auch mit anderen Oberflächen als JavaFX verwendet werden
könnten.
Hier ist mal eine App für Android. Die Modellklassen dazu
habe ich nicht noch einmal gepostet, weil sie unverändert
von meinem vorherigen Posting (auf das ich hier antworte)
kopiert werden können.

...

/* the model ends here, the application begins here */

public final class Main extends android.app.Activity
implements android.animation.TimeAnimator.TimeListener
{
final android.app.Activity activity = this;
final android.content.Context context = this;

android.widget.TextView text0;
android.widget.TextView text1;

Credit clock;

public void onTimeUpdate
( android.animation.TimeAnimator animation,
long elapsed, long dt_ms )
{ long time = java.lang.System.nanoTime();
/* Eventuell kann hier ein Parameter an Stelle
von "time" verwendet werden. */
text0.setText( java.lang.String.valueOf
( clock.duration0( time ) ));
text1.setText( java.lang.String.valueOf
( clock.duration1( time ) )); }

@java.lang.Override protected final void onCreate
( final android.os.Bundle bundle )
{ super.onCreate( bundle );

text0 = new android.widget.TextView( Main.this.context );
text1 = new android.widget.TextView( Main.this.context );

android.widget.LinearLayout clocks
= new android.widget.LinearLayout( this );

clocks.addView( text0 );
clocks.addView( text1 );

final android.widget.Button button0
= new android.widget.Button( Main.this.context );
button0.setText( "white" );

final android.widget.Button button1
= new android.widget.Button( Main.this.context );
button1.setText( "black" );

final android.widget.Button buttonPause
= new android.widget.Button( Main.this.context );
buttonPause.setText( "pause" );

final android.widget.Button buttonCont
= new android.widget.Button( Main.this.context );
buttonCont.setText( "continue" );

button0.setOnClickListener
( new android.view.View.OnClickListener()
{ @java.lang.Override public void onClick
( final android.view.View view )
{ clock.start1( java.lang.System.nanoTime() ); }} );

button1.setOnClickListener
( new android.view.View.OnClickListener()
{ @java.lang.Override public void onClick
( final android.view.View view )
{ clock.start0( java.lang.System.nanoTime() ); }} );

buttonPause.setOnClickListener
( new android.view.View.OnClickListener()
{ @java.lang.Override public void onClick
( final android.view.View view )
{ clock.pause( java.lang.System.nanoTime() ); }} );

buttonCont.setOnClickListener
( new android.view.View.OnClickListener()
{ @java.lang.Override public void onClick
( final android.view.View view )
{ clock.cont( java.lang.System.nanoTime() ); }} );

android.widget.LinearLayout buttons
= new android.widget.LinearLayout( this );

buttons.addView( button0 );
buttons.addView( button1 );
buttons.addView( buttonPause );
buttons.addView( buttonCont );

android.widget.LinearLayout all
= new android.widget.LinearLayout( this );
all.setOrientation( android.widget.LinearLayout.VERTICAL );

all.addView( clocks );
all.addView( buttons );

Main.this.activity.setContentView( all );

clock = new Credit();
clock.setCredit0( 120L * 60L * 1000000000L );
clock.setCredit1( 120L * 60L * 1000000000L );

final android.animation.TimeAnimator animator
= new android.animation.TimeAnimator();
animator.setTimeListener( this );
animator.start(); }}
Christian H. Kuhn
2016-07-19 13:59:48 UTC
Permalink
Post by Stefan Ram
Inzwischen habe ich mein Model mal mit einer einfachen
JavaFX-GUI verbunden. Das sollte mit dem JDK 8 direkt
kompiliert und gestartet werden können, wenn es in eine
Datei »Main.java« kopiert wird.
Ich habe ein wenig gekämpft, bis Eclipse bereit war, da irgendwas zu
starten. Insbesondere hat es eine main()-Methode gebraucht. Aber dann
lief es. Und mit der reduzierten Funktionalität ist es schon fast im
echten Einsatz für Blitzschach zu gebrauchen. Halt etwas intuitivere
Zeitanzeige und ein Knopf für neue Partie :-) Zu den

Der JavaFX-Code liest sich gut. Da werde ich mich wirklich mal
reinarbeiten müssen.

Zunächst sieht der Ansatz mit dem ApplicationTimer natürlich nett aus.
Über Gründe dafür, die Refresh-Taktung in der Clock zu belassen, habe
ich woanders im Thread ausführlich erläutert.

lg
QNo
Christian H. Kuhn
2016-07-17 10:44:15 UTC
Permalink
Post by Stefan Ram
Ich würde das vielleicht so machen (habe aber im Moment keine Zeit,
public void start( final long point )
{ if( this.isRunning )throw new java.lang.IllegalStateException();
Hier und bei end() ist die Exception wohl nicht nötig. Wenn der Knopf
von außen mehrfach gedrückt wird – und das kommt in der Praxis durchaus
vor – ist es unnötig, da nochmal ausdrücklich darauf hinzuweisen. Es
passiert einfach nichts. Eine watch clause if (this.isRunning) {return}
oder andersrum if (!this.isRunning) { Body } reicht völlig.
Post by Stefan Ram
public long duration( final long point )
{ return this.duration +
( this.isRunning ? point - this.startTime : 0 ); }}
Wird nicht unbedingt funktionieren. Funktioniert dann, wenn die
Oberklasse „oft genug“ aktualisiert und bei 0 die gespeicherte duration
weiter verwenden kann. Das kann aber nicht garantiert werden, wenn
zwischen zwei Aktualisierungen längere Zeit liegt, die zuerst laufende
Uhr inzwischen angehalten wurde und mehr Zeit vergangen ist als die
kleinste im Display dargestellte Zeiteinheit.
Post by Stefan Ram
Beim Aufbauen der Swing-Oberfläche wird ohnehin
invokeAndWait verwendet (S), alle anderen Ereignisse sollten
von Swing kommen und damit schon auf dem EDT laufen.
Für QChessTimer und QChessClock keine valide Annahme. Hier soll ein
unabhängiges Modell entworfen werden, auf das beliebige Oberflächen
aufgesetzt werden können, von der Kommandozeile und Tastendrücken über
Swing und JavaFX bis hin zu Weboberflächen, bei denen Uhr und beide
Spieler auf drei verschiedenen Systemen arbeiten.
Post by Stefan Ram
S: Es wird mit »clock = new Clock()« eine neue Uhr erzeugt
(ich nehme zur Vereinfachung nur eine Uhr an).
A: Es wird »clock.start( now() )« aufgerufen, wobei »now()«
die aktuelle Zeit in (beispielsweise) Nanosekunden ist.
O: Es wird »clock.end( now() )« aufgerufen.
Das übernimmt im Modell QChessClock und wird von der Oberfläche
irgendwie gesteuern.
Post by Stefan Ram
R: Ich nehme an, daß Android und Swing es erlauben, sich
regelmäßig Timer-Ereignisse schicken zu lassen, am besten
synchronisiert mit der Bildschirmauffrischung und diese auch
gleich auf dem EDT laufen.
Muss wie gesagt in Java passieren, weil die Technologie der Oberfläche
an dieser Stelle unbekannt ist.
Post by Stefan Ram
Dann wird mit »clock.duration( now() )«
die aktuelle Zeit der Uhr abgefragt und angezeigt.
Ah. DAS ist die Rettung :-) Wenn man beim Abfragen die Zeit mit
übergibt, kann man auf jeden Fall die verbleibende Zeit zurückgeben,
egal ob running oder stopped. Und QChessTimer braucht keine Zeitquelle mehr.
Post by Stefan Ram
Ich würde allerdings gerade bei einem »Lernprojekt« dann
auch JavaFX verwenden, da man ja für die Zukunft lernen will
und nicht für die Vergangenheit.
Werde ich mir anschauen. Der recht knappe deutsche Wikipedia-Artikel
behauptet, JavaFX sei für Rich Internet Applications zuständig. Zur Zeit
nicht mein Thema, kann aber noch kommen. Und auch wenn eine Web-GUI
möglich sein soll, geht es mir doch erstmal um lokale Anwendungen. Daher
Swing zum Lernen, Android für die Produktion.

Der umfangreichere englische Artikel erklärt JavaFX aber auch für
Desktop Applications zuständig. Das erklärt auch, warum JavaFX Swing
ablösen soll; das ist in der dt. Fassung zwar formuliert, klingt da aber
unlogisch. Ich werde mir JavaFX auf jeden Fall anschauen, und vermutlich
probiere ich dann auch eine GUI für QChessClock. Aber erst nach Android.

lg
QNo
Stefan Ram
2016-07-17 13:29:35 UTC
Permalink
Post by Christian H. Kuhn
Post by Stefan Ram
Beim Aufbauen der Swing-Oberfläche wird ohnehin
invokeAndWait verwendet (S), alle anderen Ereignisse sollten
von Swing kommen und damit schon auf dem EDT laufen.
Für QChessTimer und QChessClock keine valide Annahme. Hier soll ein
unabhängiges Modell entworfen werden, auf das beliebige Oberflächen
aufgesetzt werden können, von der Kommandozeile und Tastendrücken über
Swing und JavaFX bis hin zu Weboberflächen, bei denen Uhr und beide
Spieler auf drei verschiedenen Systemen arbeiten.
Mein Modell ist ebenfalls oberflächenunabhängig. Es /kann/
im EDT verwendet werden, muß aber nicht. Nur falls man es
unter Swing in einem anderen Thread verwendet will, /dann/
müßte man sich noch mehr Gedanken machen. Deswegen ist es
am einfachsten, in Swing /alles/ im EDT zu machen.
Post by Christian H. Kuhn
Post by Stefan Ram
R: Ich nehme an, daß Android und Swing es erlauben, sich
regelmäßig Timer-Ereignisse schicken zu lassen, am besten
synchronisiert mit der Bildschirmauffrischung und diese auch
gleich auf dem EDT laufen.
Muss wie gesagt in Java passieren, weil die Technologie der Oberfläche
an dieser Stelle unbekannt ist.
Ich mache es eher so, daß ich das aus dem Model ausspare,
und dann von der jeweiligen Oberfläche erledigen lasse.
Auch dadurch bleibt das Modell oberflächenunabhängig.
Post by Christian H. Kuhn
Werde ich mir anschauen. Der recht knappe deutsche Wikipedia-Artikel
behauptet, JavaFX sei für Rich Internet Applications zuständig.
Es ist wohl so, daß Swing von Oracle nicht mehr weiterentwickelt
wird. Damit ist der Lebenszustand von Swing jetzt mit dem von AWT
vergleichbar. Alle Arbeit steckt Oracle jetzt (/wenn/ sie überhaupt
noch etwas an Java tun) in JavaFX. Damit ist man praktisch zum
Umzug gezwungen, obwohl JavaFX für Büroanwendungen und auf dem
Accessibility-Gebiet schwächer als Swing sein soll.

Ich habe jetzt mal angefangen, eine einfache Uhr in JavaFX
zu schreiben, so wie ich das mit den Video-Frame-Timer-Events
beschrieben hatte:

public final class Main extends javafx.application.Application
{
class Timer extends javafx.animation.AnimationTimer
{ public void handle( final long time )
{ Main.this.handle( time ); }}

javafx.scene.control.TextField text;

private final void handle( final long time )
{ text.setText( java.lang.String.valueOf( time )); }

public void start( final javafx.stage.Stage primaryStage )
{ text = new javafx.scene.control.TextField();
final javafx.scene.Scene scene = new javafx.scene.Scene( text );
primaryStage.setScene( scene );
primaryStage.show();
this.new Timer().start(); }}

Dieses Programm zeigt die aktuelle Zeit in Nanosekunden mit
laufender Aktualisierung an.
Christian H. Kuhn
2016-07-19 12:59:50 UTC
Permalink
Version 0.9.2
Post by Patrick Roemer
interface TimeSource {
long getTimeInNanoSecs();
}
Den Gedanken habe ich aufgegriffen und mal implementiert. Die TimeSource
darf sich dabei aussuchen, welche Zeiteinheit sie verwendet; es muss nur
ein ganzzahliger Bruchteil einer Sekunde sein.

public interface QTimeSource {
long getFactorToSeconds(); // erlaubt versch. time units
long getNow();
}

public class QSystemMilliTime implements QTimeSource {
private static final long MILLIS_PER_SECOND = 1000;
@Override public final long getFactorToSeconds() {
return MILLIS_PER_SECOND;
}
@Override public final long getNow() {
return System.currentTimeMillis();
}
}

private static class TestTimeSource implements QTimeSource {
private static final long SECONDS_TO_SECONDS = 1;
private long now;
@Override public long getFactorToSeconds() {
return SECONDS_TO_SECONDS;
}
@Override public long getNow() {
synchronized (this) {
return now;
}
}
public void setNow(final long _now) {
synchronized (this) {
now = _now;
}
}
}

Dazu habe ich Stefan Rams Timer-Konzept im großen und ganzen übernommen
und mit synchronize(this) (hoffentlich?) thread safe gemacht. Der Timer
arbeitet jetzt mit abstrakten Zeiten. Zur Sicherheit müsste im
Konstruktor die startTime noch auf ein adäquates now gesetzt werden,
falls von start(long) auch Werte <= 0 kommen könnten.

public final class QCountdownTimer {
private transient long remainingTime;
private transient long additionalFischerTime;
private transient long startTime;
private transient long stopTime;
private transient boolean running;

QCountdownTimer(final long _timeOfReflection) {
this(_timeOfReflection, 0);
}
QCountdownTimer(final long _timeOfReflection, final long
_additionalFischerTime) {
running = false;
additionalFischerTime = _additionalFischerTime;
remainingTime = _timeOfReflection + additionalFischerTime;
}

/* default */ long getRemainingTime(final long _now) { // Parameter
has no effect if !running
long actualRemaningTime;
synchronized (this) {
if (running && _now > startTime) {
actualRemaningTime = remainingTime - _now + startTime;
} else {
actualRemaningTime = remainingTime;
}
return actualRemaningTime;
}
}

/* default */ void start(final long _startTime) {
synchronized (this) {
if (!running && _startTime >= stopTime) {
running = true;
startTime = _startTime;
}
}
}

/* default */ void stop(final long _stopTime) {
synchronized (this) {
if (running && _stopTime >= startTime) {
running = false;
stopTime = _stopTime;
remainingTime = remainingTime - (stopTime - startTime) +
additionalFischerTime;
}
}
}

/* default */ boolean isRunning() {
synchronized (this) {
return running;
}
}
}

QChessClock kann jetzt im Konstruktor eine QTimeSource mitbekommen,
ansonsten nimmt sie QSystemMilliTime.
Genau.
Post by Patrick Roemer
Das ist aber nur die halbe Miete. Man würde dann auch den konkreten
j.u.Timer in der Kernklasse wegabstrahieren wollen.
Siehe den langen Subthread „Countdown Timer Design“ mit vielen Beiträgen
von Stefan Ram. Dem bin ich in der ersten Stufe gefolgt: QCountdownTimer
weiss nichts von Zeitquellen und hat auch keinen TimerTask mehr. Der
konkrete Zeitbegriff und die Überprüfung auf Zeitablauf erfolgt im
Verwender.

In einem Punkt stimme ich mit Stefan nicht überein. Er hat auch die
Schachuhr von TimeSource und TimerTask befreit. In der einfachen
Blitzschachuhr, die er benutzt, ist das ok. Da wird bei Blättchenfall
einfach nur eine Anzeige gesetzt und gut.

Abweichend vom sauberen TDD, siehe Beiträge von Wanja Gayk im Thread
„Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle“, weiss ich
aber jetzt schon, dass in der nächsten Ausbaustufe beim Blättchenfall
zusätzliche Funktionalität dazukommt. Die ist in der Begrifflichkeit des
Kano-Modells kein Begeisterungs- oder auch nur Leistungs-, sondern
Basis-Merkmal und wird definitiv im ersten Release enthalten sein. Also
darf ich auch ohne Test in die Richtung planen ;-)

Es geht um mehrere Bedenkzeitperioden. Bislang implementiert sind
Bedenkzeitmodelle mit einer Periode. Ist die Zeit für diese Periode
abgelaufen, ist die Partie im Prinzip vorbei. Die Zeitüberschreitung
wird angezeigt, mehr Funktionalität ist nicht nötig. Das wird in der
Praxis bei sogenannten Blitz- und Rapid-Partien angewandt und betrifft
typischerweise kurze Bedenkzeiten von 1 bis 90 min pro Spieler und
Partie, mit Zeitzuschlägen von 1 bis 20 s.

In „langen“, „ernsten“, „Turnier-“ Partien werden üblicherweise zwei
oder mehr Zeitperioden benutzt. In den unteren Amateurklassen ist sehr
verbreitet: 2 h für die ersten 40 Züge, danach ein Zuschlag von einer
oder einer halben Stunde. Die Bundesliga spielte von 2009 bis 2014 mit
100 min für die ersten 40 Züge, 50 min für die nächsten 20 Züge, 15 min
für den Rest, 30s akkumulierender Zuschlag pro Zug.

Die letzte Zeitperiode wird gehandhabt wie bisher: Bei Ablauf Anzeige
und gut. Die Zeitperioden davor machen mehr Arbeit. Bei Ablauf der
Periode (in der Regel durch Bedenkzeitüberschreitung, gelegentlich auch
durch Erreichen der Zügezahl, wofür noch ein Zugzähler zu implementieren
ist) wird die Zeitüberschreitung beim jeweiligen Spieler so angezeigt,
dass sie über einen längeren Zeitraum festgestellt werden kann.
Gleichzeitig wird die erlaubte Bedenkzeit der nächsten Periode bei
beiden Spielern zur aktuellen Bedenkzeit addiert.

Ich bin ja der Ansicht, dass diese Umstellung der verbleibenden
Bedenkzeiten ein Wissen über Implementationsdetails der Schachuhr
verlangt. Das ist vor anderen Klassen zu verbergen und gehört damit in
die Schachuhr. Auch die regelmäßige Überprüfung auf Zeitüberschreitung
darf nicht anderen Klassen überlassen werden. Sie ist von der Schachuhr
selbst zu leisten; entsprechende Ereignisse sind an Oberflächen zu
senden. Damit benötige ich einen TimerTask in der Schachuhr, ein
ScheduledThreadPoolExecutor wäre ne Nummer zu dick. Der TimerTask
überprüft in sinnvollen Abständen auf ZÜ. Bei Anzeigengenauigkeit von 1s
wäre ein Takt von 1s zu langsam; der durchschnittliche Fehler von einer
halben Sekunde ist beim Blitzen bereits relevant. Der Millisekunden-Takt
der Standard-Zeitquelle würde sehr genau anzeigen, aber eine nicht mehr
wahrnehmbare Genauigkeit produzieren. Der Mensch kann Bilder in Folgen
von 24/s wahrnehmen, Sicherheitsfaktor 2, runden, sind wir bei 1/50 s
oder 20 ms. Und wenn dieser Thread schon mal existiert, kann er auch
veränderte Zeiten an die GUI mitteilen.
Post by Patrick Roemer
Über SwingUtilities#invokeLater().
Mit den beiden invoke bin ich auch am Kämpfen. Beide sorgen auf dem EDT
für eine klare Abfolge von Events. Nur invokeAndWait() sorgt aber auch
für eine happens-before-Beziehung zwischen ausgeführten Befehlen auf dem
aufrufenden Thread und dem EDT. Für manche Tests nicht unwichtig, die
darauf warten müssen, dass das EDT-Ereignis auch ausgeführt ist, bevor
der Test funktionieren kann. Dafür wirft invokeAndWait() eine Exception.
In normalem Code kann die problemlos abgefangen und verarbeitet werden;
in Runnables und actionPerformed muss man entweder eine try-catch mit
leerem Catch-Block machen, was alle statischen Codechecker anmeckern,
oder man macht im Catch-Block so Dinge wie

final Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, e);

die auch keinen Schönheitspreis gewinnen.

lg
QNo
Christian H. Kuhn
2016-07-19 14:06:09 UTC
Permalink
Am 19.07.2016 um 14:59 schrieb Christian H. Kuhn:
[Refresh-Takt in UI oder Model]

Auch hier noch einen: Prinzipiell könnte eine QChessClock mehrere
Observer haben. Hochwertige elektronische Schachuhren haben heute schon
eine Schnittstelle, über die die angezeigte Zeit auch im Internet
abgefragt werden kann. Wenn die Refresh-Taktung in der UI stattfindet,
bekomme ich ein schönes Durcheinander und möglicherweise vorübergehende
Inkonsistenz der Anzeigen. Mit Refresh-Takt im Model vermeide ich das.

Da bemerke ich, dass bei mehreren Observern die notify-Methoden
aufeinander warten. Es könnte u.U. sinnvoll sein, doch einen ThreadPool
aufzumachen und für jeden Observer einen Thread anzulegen, auf dem der
seine Benachrichtigungen bekommt, ohne dass schnellere Observer warten
müssen ...

lg
QNo
Patrick Roemer
2016-07-19 16:59:15 UTC
Permalink
Post by Christian H. Kuhn
Post by Patrick Roemer
Über SwingUtilities#invokeLater().
Mit den beiden invoke bin ich auch am Kämpfen. Beide sorgen auf dem EDT
für eine klare Abfolge von Events. Nur invokeAndWait() sorgt aber auch
für eine happens-before-Beziehung zwischen ausgeführten Befehlen auf dem
aufrufenden Thread und dem EDT.
Nein!

Der Unterschied ist, dass #invokeLater() asynchron ist, d.h. der Aufruf
kehrt "sofort" zurück, und der übergebene Codeblock wird dann irgendwann
später auf dem EDT ausgeführt. #invokeAndWait() ist synchron, d.h. der
Aufruf kehrt erst zurück, wenn der Codeblock auf dem EDT tatsächlich
ausgeführt wurde. Wenn kein besonderer Anlass für die Verwendung von
#invokeAndWait() vorliegt, ist #invokeLater() zu bevorzugen.

In beiden Fällen entsteht *keine* happens-before-Beziehung zwischen
irgendwas. In dem Beispielschnipsel, den ich neulich hier hingeklebt
habe, hat man eine happens-before-Beziehung zwischen dem Schreiben in
die AtomicReference auf dem EDT und dem Lesen dieses Wertes auf dem
main-Thread. Und die entsteht eben durch die Verwendung von
AtomicReference, weil der Wert darin volatile gehalten wird.

Im Prinzip könnte man #invokeAndWait() z.B. dadurch emulieren, dass man
am Ende des #invokeLater() übergebenen Blocks ein CountdownLatch
triggert und direkt nach dem Aufruf von #invokeLater() auf dieses Latch
wartet. Dann hätte man tatsächlich eine happens-before-Beziehung
zwischen diesen beiden Latch-Interaktionen.

Wenn das noch fuzzy ist, war eine Stunde in der S-Bahn offenkundig nicht
ausreichend. :)
Post by Christian H. Kuhn
Für manche Tests nicht unwichtig, die
darauf warten müssen, dass das EDT-Ereignis auch ausgeführt ist, bevor
der Test funktionieren kann. Dafür wirft invokeAndWait() eine Exception.
In normalem Code kann die problemlos abgefangen und verarbeitet werden;
in Runnables und actionPerformed muss man entweder eine try-catch mit
leerem Catch-Block machen, was alle statischen Codechecker anmeckern,
oder man macht im Catch-Block so Dinge wie
final Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
die auch keinen Schönheitspreis gewinnen.
Das klingt auch noch etwas konfus.

In beiden Fällen kann es passieren, dass der übergebene Block eine
Exception wirft. Im Fall von #invokeAndWait() wird diese dann, in eine
InvocationTargetException gewickelt, an den Aufrufer propagiert. Im Fall
von #invokeLater() hat man diese Option nicht, denn der Block wird ja
erst ausgeführt, nachdem der Aufruf zurückgekehrt ist. Also bleibt
nichts, als diese Exception *im EDT* hochblubbern zu lassen, was
wahrscheinlich auch eher unerwünscht ist. Um Exceptions im übergebenen
Block muss sich also in jedem Fall Gedanken machen.

Das Problem mit leeren catch-Blöcken ist sicher nicht rimäar, dass
Codechecker das nicht mögen. Und was ein #invokeAndWait() in einem
#actionPerformed() suchen könnte, will ich glaube ich gar nicht so genau
wissen... ;)

Viele Grüße,
Patrick
Christian H. Kuhn
2016-07-19 20:35:59 UTC
Permalink
Post by Patrick Roemer
In beiden Fällen entsteht *keine* happens-before-Beziehung zwischen
irgendwas.
JETZT bin ich verwirrt.

Ich war der Ansicht, bei

commandA();
SwingUtilities.invokeAndWait(() -> {commandB});
commandC();
SwingUtilities.invokeAndWait(() -> {commandD});

könnte ich mich darauf verlassen, dass die Reihenfolge A-B-C-D
eingehalten wird, sofern A und C nur auf dem gerade laufenden
aufrufenden Thread arbeiten, während bei dem entsprechenden Aufruf mit
invokeLater() lediglich sicher ist, dass A vor C (auf dem Hauptthread)
und B vor D (auf dem Swing-EDT) ausgeführt wird?
Post by Patrick Roemer
Und was ein #invokeAndWait() in einem
#actionPerformed() suchen könnte, will ich glaube ich gar nicht so genau
wissen... ;)
Nix direktes. Warum das eher dämlich wäre, habe ich schon begriffen :-)
Es gibt aber Methoden, die könnten theoretisch vom EDT aus aufgerufen
werden, dann bräuchten sie kein invoke...(). Oder von irgendeinem
anderen Thread. Dann brauchen sie invoke. Den Swing-EDT kann man
feststellen. Die throw-Klausel hat man aber trotzdem, wenn man die
Exception des invoke nicht fangen mag. Das kann auch eine beliebig lange
Kette von Methodenaufrufen sein. Und wenn keine dieser Methoden etwas
Sinnvolles mit der Exception aus invokeAndWait() anfangen kann, landet
die irgendwan in actionPerformed().

lg
QNo
Patrick Roemer
2016-07-20 11:00:55 UTC
Permalink
Post by Christian H. Kuhn
Post by Patrick Roemer
In beiden Fällen entsteht *keine* happens-before-Beziehung zwischen
irgendwas.
JETZT bin ich verwirrt.
Ich war der Ansicht, bei
commandA();
SwingUtilities.invokeAndWait(() -> {commandB});
commandC();
SwingUtilities.invokeAndWait(() -> {commandD});
könnte ich mich darauf verlassen, dass die Reihenfolge A-B-C-D
eingehalten wird, sofern A und C nur auf dem gerade laufenden
aufrufenden Thread arbeiten, während bei dem entsprechenden Aufruf mit
invokeLater() lediglich sicher ist, dass A vor C (auf dem Hauptthread)
und B vor D (auf dem Swing-EDT) ausgeführt wird?
Gemäß wall-time ist dem auch so. Damit ist aber noch nicht garantiert,
dass C die Auswirkungen von Aktionen in B auch sieht. Und *das* ist,
worum es bei happens-before-Beziehungen geht.
Post by Christian H. Kuhn
Post by Patrick Roemer
Und was ein #invokeAndWait() in einem
#actionPerformed() suchen könnte, will ich glaube ich gar nicht so genau
wissen... ;)
Nix direktes. Warum das eher dämlich wäre, habe ich schon begriffen :-)
Es gibt aber Methoden, die könnten theoretisch vom EDT aus aufgerufen
werden, dann bräuchten sie kein invoke...(). Oder von irgendeinem
anderen Thread. Dann brauchen sie invoke. Den Swing-EDT kann man
feststellen.
Klingt schon fishy. Wenn eine Methode aus unterschiedlichen Kontexten
aufgerufen werden kann, sollte der Kontext sie schlicht nicht
interessieren müssen. Das #invoke...() gehört dann in eine Schicht, die
den Kontext kennt, z.B. eben ins Presentation Model. Und Aufrufe, die
darüber laufen, sollten dann eben immer aus einem non-EDT-Thread kommen.
Post by Christian H. Kuhn
Die throw-Klausel hat man aber trotzdem, wenn man die
Exception des invoke nicht fangen mag.
Warum sollte man das nicht mögen? Eine InvocationTargetException will
man eigentlich nicht weiterpropagieren. Und eine InterruptedException
sollte man auch möglichst lokal behandeln.
Post by Christian H. Kuhn
Das kann auch eine beliebig lange
Kette von Methodenaufrufen sein. Und wenn keine dieser Methoden etwas
Sinnvolles mit der Exception aus invokeAndWait() anfangen kann, landet
die irgendwan in actionPerformed().
Das sollte eigentlich nicht passieren. Abgesehen davon: Ich kann mich
nicht erinnern, #invokeAndWait() je (außer eben in Tests) eingesetzt zu
haben. (Allerdings habe ich auch nur kleinere Hobbyprojekte mit Swing
gemacht.) Üblicherweise will man #invokeLater(). Aber auch da muss man
sich natürlich über eventuelle Exceptions Gedanken machen.

Viele Grüße,
Patrick
Stefan Ram
2016-07-20 17:45:47 UTC
Permalink
Post by Patrick Roemer
Gemäß wall-time ist dem auch so. Damit ist aber noch nicht garantiert,
dass C die Auswirkungen von Aktionen in B auch sieht. Und *das* ist,
worum es bei happens-before-Beziehungen geht.
Ich weiß nicht, ob das jetzt relevant ist, weil ich es
selber noch nicht gelesen habe. Aber ich habe es mir zum
lesen vorgemerkt:

...

Oops! Jetzt find' ich es
nicht mehr. Aber ich /glaube/ es war das hier:

www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
(HTTPS)
Post by Patrick Roemer
Das sollte eigentlich nicht passieren. Abgesehen davon: Ich kann mich
nicht erinnern, #invokeAndWait() je (außer eben in Tests) eingesetzt zu
haben. (Allerdings habe ich auch nur kleinere Hobbyprojekte mit Swing
gemacht.) Üblicherweise will man #invokeLater(). Aber auch da muss man
sich natürlich über eventuelle Exceptions Gedanken machen.
Ja, ich meinte auch »invokeLater« als ich »invokeAndWait« schrieb.

Ich benutzte das nur für Swing ganz am Anfang zum Aufbau der ersten
Oberfläche. Zum Beispiel in

class Main extends javax.swing.JComponent implements java.lang.Runnable
{ final int X = 320; final int Y = 200;

@java.lang.Override
public void paintComponent( final java.awt.Graphics graphics )
{ graphics.fillRect( X/2, Y/2, 1, 1 ); }

@java.lang.Override public final void run()
{ final javax.swing.JFrame frame = new javax.swing.JFrame( "Main" );
Main.this.setPreferredSize( new java.awt.Dimension( X, Y ));
frame.setDefaultCloseOperation( javax.swing.JFrame.EXIT_ON_CLOSE );
frame.add( Main.this ); frame.pack(); frame.setVisible( true ); }

public static void main( final java.lang.String args[] )
{ java.awt.EventQueue.invokeLater( new Main() ); }}

Alles andere findet ja bei mir in Ereignisbehandlern statt,
wo man schon auf dem EDT ist. Nur für eine Meßdatenerfassung
hatte ich dann mal so etwas wie einen Worker programmiert.

Und ich hatte mal einen Artikel gelesen, wo jemand vor den
Problemen von Multithreading warnte, so in etwa, daß selbst
Experten keine fehlerfreien Parallelprogramme schreiben können.

Das fand ich dann aber auch nicht wieder. Ersatzweise hatte
ich mir dann so ähnliche Texte zusammengesucht.

»I conjecture that most multithreaded general-purpose
applications are, in fact, so full of concurrency bugs
that as multi-core architectures become commonplace,
these bugs will begin to show up as systems failures.«

Man kann beispielsweise »4 How Bad is it In Practice?« in

www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf
(HTTP)

lesen. Daher versuche ich erst einmal, möglichst alles auf
einem Thread zu machen.
Christian H. Kuhn
2016-07-23 18:36:26 UTC
Permalink
Version 0.12.6
Post by Patrick Roemer
Gemäß wall-time ist dem auch so. Damit ist aber noch nicht garantiert,
dass C die Auswirkungen von Aktionen in B auch sieht. Und *das* ist,
worum es bei happens-before-Beziehungen geht.
Nächster Versuch. Vielleicht habe ich es diesmal richtig verstanden. Ich
nehme es mal als gutes Zeichen, dass ich eine Menge Code gestrichen habe :-)

QChessClock:

Auf Eigenschaften wird schreibend nur durch die Methoden von
QChessClockInterface und durch addObserver() zugegriffen. Gelesen werden
die Eigenschaften nur durch den Bau des Austauschobjekts in
notifyObservers(). Damit sich diese Methoden nicht in die Quere kommen,
werden sie mit synchronized (this) synchronisiert.

Auf expliziten Aufruf von notifyObservers() nach jeder Zustandsänderung
wird verzichtet. Der Zustand der Clock wird alle 20ms an die Observer
verschickt.

QChessClockJavaAV:

GUI-Events und das folgende actionPerformed() sowie die darin
aufgerufenen Methoden liegen schön brav nacheinander auf dem Swing-EDT.
Diese Methoden führen nicht mehr zu einem notifyObservers() und damit zu
einem update(). update() wird nur noch vom TimerTask der Clock via
notifyObservers() aufgerufen und legt alle Zustandsänderungen der GUI
per invokeLater auf den Swing-EDT.

Mögliches Problem: Es könnte sein, dass die Clock einen Zustand annimmt,
in dem ein bestimmter Methodenaufruf unzulässig oder unsinnig ist. Die
Clock ist in diesem Zustand und teilt ihn beim nächsten Click an die GUI
mit. setEnabled(false) für das entsprechende GUI-Element wird auf den
Swing-EDT gelegt und dann auch irgendwann ausgeführt. In der Zeit
zwischen Zustandsänderung der Clock und Sperren des Bedienelements
könnte dieses ausgelöst worden sein und seinen in der Clock nicht mehr
sinnvollen Befehl ausführen. Die Clock muss also unsinnige Eingaben
abfangen. Meines Erachtens tut sie das schon. Ein künftiges Refactoring
während der noch folgenden Zustandserweiterungen könnte möglicherweise
eine Umstellung auf das State- oder Strategy-Entwicklungsmuster beinhalten.

Normaler Ablauf:

Drei Threads. Main erstellt die GUI. Swing-EDT führt GUI-Kommandos und
GUI-Aktualisierungen aus. TimerTask benachrichtigt GUI über
vorzunehmende Änderungen. GUI-Zustand und Clock-Zustand sind nicht
unbedingt synchron. Im normalen Anwendungsfall ändert sich das aber
spätestens durch den nächsten TimerTask. Wann genau der bzw. die von ihm
per invokeLater() ausgelösten Änderungen der GUI wirksam sind, ist so
nicht vorhersehbar. Ich bleibe aber dabei, dass das Risiko, dass ein
notifyObservers() nicht innerhalb der 20ms bis zum nächsten Aufruf
fertig abgeschlossen ist, für sehr überschaubar. Die Inkonsistenz wird
also vom menschlichen Spieler nicht bemerkt.

Tests:

In Tests ist es etwas anderes. Aufeinanderfolgende Methodenaufrufe
erfolgen so schnell hintereinander, dass notifyObservers() dazwischen
eher nicht aufgerufen wird. Daher müssen hier Threads synchronisiert werden.

QChessClockTest:

Die Testklasse implementiert eine Oberfläche, also insbesondere
update(). Die Tests verlaufen so, dass eine neue Clock mit spezieller,
aus dem Test veränderbarer Zeitquelle erstellt wird. Der Test ruft die
zu testende Bedienfunktion auf und/oder verändert die Zeit der Quelle.
Danach wird überprüft, ob die gewünschte Zustandsänderung per update()
übertragen wird. Da nicht vorauszusehen ist, wieviel Zeit zwischen
Aufruf der Methode und Zustandsübertragung erfolgt, wird per guarded
block auf das Ende des update() gewartet.

QChessClockJavaAVTest:

Die Testklasse implementiert die Interfaces QChessClockObservable und
QChessClockInterface. Zu Beginn eines Tests wird eine neue GUI erstellt,
die sich mit addObserver() bei der Testklasse registriert. Das zu
testende GUI-Element wird auf dem Swing-EDT mit invokeAndWait(() ->
doClick()) ausgelöst. Damit wird über actionPerformed() die
entsprechende Methode in der Testklasse aufgerufen und eine
entsprechende Zustandsänderung ausgeführt. Der EDT wird nicht verlassen,
daher wartet invokeAndWait(), bis die dem Event zugeordnete Methode
fertig ausgeführt ist. Das Testklassenobjekt ist damit in einem
testbaren Zustand, und der entsprechende assert kann ausgeführt werden.

Die andere Richtung, Uhr-Zustandsänderungen führen zu Änderung der
Darstellung, wird bislang nur durch Änderungen des Status getestet.
getestet. Hier wird der Zustand der Uhr gesetzt, der dann im TimerTask
per notifyObservers() an die GUI übertragen wird. Der Test muss dann im
guarded Block auf die Nachricht von notifyObservers warten, dass alle
beauftragten GUI-Änderungen auf dem EDT liegen, und kann nach Ende des
wait() die entsprechenden GUI-Eigenschaften abfragen.

Lediglich der aboutTest() macht mir noch Kopfweh. Da warte ich per
sleep() einen geschätzten Zeitraum darauf, dass ein JOptionPane
aufgebaut wird, um den Button abfragen zu können. Gibt es da keine
geschicktere Lösung? invokeAndWait() zum Aufruf des entsprechenden
Menüpunkts geht nicht, weil der Aufruf solange wartet, bis der
schließende Button gedrückt wird, und dann ist das Pane halt weg und
kann nicht mehr untersucht werden.

lg
QNo
Stefan Ram
2016-07-23 20:37:19 UTC
Permalink
Post by Christian H. Kuhn
Drei Threads. Main erstellt die GUI. Swing-EDT führt GUI-Kommandos und
Patrick hatte dazu ja schon geschrieben:

|Ansonsten hast Du auf jeden Fall noch ein Threading-Problem.
|Sowohl der Test (main-Thread) als auch das Kernmodell
|(Timer-Threads) werkeln direkt auf dem UI-Zustand rum. Das
|muss alles über den Swing-EDT laufen.

Dies gilt auch für den Aufbau der graphischen
Benutzeroberfläche (seit zirka 2006).
Christian H. Kuhn
2016-07-23 21:15:32 UTC
Permalink
Post by Stefan Ram
Dies gilt auch für den Aufbau der graphischen
Benutzeroberfläche (seit zirka 2006).
Inzwischen habe ich dann auch entdeckt, dass
https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html
nicht alleine steht, sondern Teil eines Tutorials ist. Als erste
Erkenntnis habe ich dann mal alle Aufrufe des Konstruktors der GUI per
SwingUtilities.invoke...() erledigt. Mit weiteren Erkenntnissen ist zu
rechnen ;-)

lg
QNo

Wanja Gayk
2016-07-19 21:02:37 UTC
Permalink
Post by Christian H. Kuhn
Den Gedanken habe ich aufgegriffen und mal implementiert. Die TimeSource
darf sich dabei aussuchen, welche Zeiteinheit sie verwendet; es muss nur
ein ganzzahliger Bruchteil einer Sekunde sein.
public interface QTimeSource {
long getFactorToSeconds(); // erlaubt versch. time units
long getNow();
}
Ich schätze die Klasse "TimeUnit" könnte dich interessieren.

Siehe:
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/TimeUnit.
html

Gruß,
-Wanja-
--
..Alesi's problem was that the back of the car was jumping up and down
dangerously - and I can assure you from having been teammate to
Jean Alesi and knowing what kind of cars that he can pull up with,
when Jean Alesi says that a car is dangerous - it is. [Jonathan Palmer]
Christian H. Kuhn
2016-07-20 10:33:35 UTC
Permalink
Post by Wanja Gayk
Ich schätze die Klasse "TimeUnit" könnte dich interessieren.
Allerdings. Bin mir nur nicht sicher, ob sie mir hier hilft.
Entsprechend ihrem Package sind die Funktionen sleep(), timedJoin() und
timedWait() wohl die Kernpunkte, die Umwandlungen sind „Zugabe“. Und an
der Stelle brauche ich wohl hauptsächlich die Umrechnungen. Bei denen
bin ich aber bislang flexibler. Ich kann problemlos in
Achtel-Millisekunden arbeiten :-) Ob mir das was bringt, ist was anderes ...

lg
QNo
Christian H. Kuhn
2016-07-15 10:03:13 UTC
Permalink
Topic adapted.
Post by Patrick Roemer
Das muss alles über den Swing-EDT laufen.
Ich habe ne Nacht drüber geschlafen. Und die Doku zu SwingUtilities
gelesen :-)

Um herauszufinden, was wie arbeitet, lasse ich das GUI-Modell als
autonomous view mal unangetastet. Ja, presentation model ist besser und
kommt auch noch. Ein Schritt nach dem anderen.

Aufrufe zur Änderung des GUI-Zustands aus den Tests können mit
SwingUtilities.invokeAndWait() auf den Swing-EDT gelegt werden.

QChessClock greift auf die GUI ausschließlich per notifyObservers() und
darin implementiert über die verschiedenen update() zu. QChessClock
weiss nichts über die Art seiner Oberfläche. Deshalb kommt ein Aufruf
von notifyObservers() (sowieso nicht, ist zu lang und zu komplex und
könnte den EDT blockieren) oder der einzelnen update() per
invokeAndWait() nicht infrage.

QChessClockJavaAVTest greift nur an einer Stelle anders als per
notifyObservers() direkt schreibend auf die GUI zu. Die Stelle ist auf
invokeAndWait() geändert. Alle anderen Schreibzugriffe erfolgen über
notifyObservers(). Die Testklasse darf wissen, dass das Testobjekt in
Swing realisiert ist; es wäre möglich, notifyObservers() (eher nicht,
s.o.) oder update() mit invokeAndWait() aufzurufen. Da das Problem aber
ohnehin in der GUI gelöst werden muss, s.o., brauche ich am Test nichts
zu ändern.

Ich merke, dass ich immer noch in der Vorstellung lebe, dass Objekte und
mit ihnen alle ihre Eigenschaften und Methoden in einem Thread leben.
Für Objekt und Felder mag das gelten, Methoden leben aber in dem Thread,
der sie aufruft. Das übersehe ich gerne. In QChessClockJavaAV betrifft
das die verschiedenen update()-Methoden. Die werden nicht unbedingt in
dem Thread ausgeführt, in dem die GUI angelegt wird. Und selbst wenn:
Sie verändern den GUI-Zustand und gehören daher in den Swing-EDT. Also
ändere ich alle update()-Methoden so, dass sie ihre Schreibzugriffe über
invokeAndWait() ausführen. v0.8.5

Die Zeitprobleme kommen extra.

lg
QNo
Patrick Roemer
2016-07-15 20:43:07 UTC
Permalink
Post by Christian H. Kuhn
QChessClock greift auf die GUI ausschließlich per notifyObservers() und
darin implementiert über die verschiedenen update() zu. QChessClock
weiss nichts über die Art seiner Oberfläche. Deshalb kommt ein Aufruf
von notifyObservers() (sowieso nicht, ist zu lang und zu komplex und
könnte den EDT blockieren) oder der einzelnen update() per
invokeAndWait() nicht infrage.
Natürlich soll QChessClock nichts von Swing wissen. Aber irgendwo musst
Du für den Zugriff auf die GUI halt in den EDT. Und das wäre bei Deinem
aktuellen Codestand halt in den Implementierungen der
#update()-Methoden, wo auf die GUI-Komponenten zugegriffen wird.
Post by Christian H. Kuhn
QChessClockJavaAVTest greift nur an einer Stelle anders als per
notifyObservers() direkt schreibend auf die GUI zu.
Was ist mit #doClick()? Ich bin äußerst zuversichtlich, dass das auch in
den EDT gehört - sonst hätte ich ja auch keine "lost clicks" gesehen. :)
Im von Dir angesprochenen JW-Artikel wird das auch so gemacht. Und auch,
wenn die das nicht so machen: #getChildIndexed() & Co. gehören auch
dahin. Einfach alles, was irgendwie auf GUI-Komponenten zugreift
und/oder GUI-Zustandsänderungen triggert.
Post by Christian H. Kuhn
Ich merke, dass ich immer noch in der Vorstellung lebe, dass Objekte und
mit ihnen alle ihre Eigenschaften und Methoden in einem Thread leben.
Für Objekt und Felder mag das gelten, Methoden leben aber in dem Thread,
der sie aufruft.
Das ist eine sehr eigenwillige Beschreibung, und allerspätestens für
Felder grundfalsch. :)

Du hast Dir mit der Schachuhr ein Projekt ausgesucht, wo Du Concurrency
verstehen musst. Mit wolkigen Metaphern kommst Du nicht weit, die
Thematik ist eher wenig intuitiv. Schau vielleicht mal in die
entsprechende Lesson im Oracle-Tutorial:

https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html

Viele Grüße,
Patrick
Christian H. Kuhn
2016-07-15 21:18:36 UTC
Permalink
Post by Patrick Roemer
Was ist mit #doClick()? Ich bin äußerst zuversichtlich, dass das auch in
den EDT gehört - sonst hätte ich ja auch keine "lost clicks" gesehen. :)
Im von Dir angesprochenen JW-Artikel wird das auch so gemacht. Und auch,
wenn die das nicht so machen: #getChildIndexed() & Co. gehören auch
dahin. Einfach alles, was irgendwie auf GUI-Komponenten zugreift
und/oder GUI-Zustandsänderungen triggert.
Ja. Frag mich nicht, wo ich gelesen habe, dass nur Schreibzugriffe
wichtig sind. Oracle sagt: alles.
https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

Alle Setter sind inzwischen im edt angekommen. Der Rest schaffts am WE.
Und dann werde ich mal notifyObservers aufsplitten. Zeitanzeige und
Fallblättchen müssen getaktet werden, die beiden anderen Funktionen
haben konkrete Auslöser in verschiedenen Methoden und können von dort
aus aufgerufen werden. Und wenn das läuft, gehe ich das Zeitproblem an.
Post by Patrick Roemer
Du hast Dir mit der Schachuhr ein Projekt ausgesucht, wo Du Concurrency
verstehen musst.
Ich suche mir immer „ganz einfache“ Projekte aus, um was über Tools zu
lernen. Und dann laufen die Tools ganz flott, und 99% des Lerninhalts
beinhalten, dass ich falsche Vorstellungen von „ganz einfach“ habe. Aber
das ist gut so. Dann kann ich das, wenn ich später mal beruflich drüber
stolpere.
Post by Patrick Roemer
https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
WE.

lg
QNo
Christian H. Kuhn
2016-07-16 13:24:52 UTC
Permalink
Post by Patrick Roemer
https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
Ich hab nachher ne Stunde S-Bahn vor mir, da lese ich das. Bis dahin
machen mich Zugriffsrechte wahnsinnig.
Post by Patrick Roemer
#getChildIndexed() & Co. gehören auch
dahin.
Genau. getName() ist in meiner Anwendung unkritisch, weil Namen von
GUI-Komponenten nur im Konstruktor gesetzt und nie mehr verändert
werden, da entstehen keine Konflikte, solange nicht lesend auf ein noch
nicht fertig konstruiertes Objekt zugegriffen wird. Ich hoffe, dass die
VM sowas verhindert und ein Objekt erst da ist, wenn der Konstruktor
fertig ausgeführt ist?

Aber TestUtils soll ja allgemeint verwendbar sein, also muss auch
getName() abgesichert werden.

public final class TestUtils {
private static String staticName;
public static Component getChildNamed(final Component _parent, final
String _name) {
class Test {
private Object object;
public void setObject(final Object _object) {
object = _object;
}
}
String name;
StringBuilder nameBuilder = new StringBuilder();
try {
SwingUtilities.invokeAndWait(new Runnable() {

@Override
public void run() {
_parent.getName(); // das geht
name = _parent.getName(); // das geht nicht
test.setObject(_parent.getName()); // das geht
staticName = _parent.getName(); // das geht
nameBuilder.append(_parent.getName()); // das geht
}
});
} catch (InvocationTargetException | InterruptedException e) {
// TODO
}
if (_name.equals(_parent.getName())) {
return _parent;
}
(...)
}
}

run() kann also auf die Variablen der Umgebung, aus der invokeAndWait()
aufgerufen wird, lesend zugreifen, nicht aber nicht zuweisen.

run() kann statischen Variablen der Klasse zuweisen. Ergebnisausgabe in
eine statische Variable der Klasse versorgt mich wieder mit eben den
Konkurrenzsituationen, die ich eigentlich lösen möchte.

Ansonsten kann run() anscheinend Variablen von außerhalb keinen Wert
zuweisen. Es können aber deren Methoden aufgerufen werden. Also muss ich
außerhalb ein geeignetes Objekt instantiieren und dem einen Wert
zuweisen (hier einen leeren Stringbuilder, Wert wird mit append()
gesetzt; für einen int fällt mir schon nichts einfaches mehr ein). Gibt
es kein geeignetes Objekt, funktioniert die o.g. Klasse Test.

Gibt es da denn keinen einfachen Weg?

lg
QNo
Patrick Roemer
2016-07-16 14:42:06 UTC
Permalink
Post by Christian H. Kuhn
Genau. getName() ist in meiner Anwendung unkritisch, weil Namen von
GUI-Komponenten nur im Konstruktor gesetzt und nie mehr verändert
werden, da entstehen keine Konflikte, solange nicht lesend auf ein noch
nicht fertig konstruiertes Objekt zugegriffen wird. Ich hoffe, dass die
VM sowas verhindert und ein Objekt erst da ist, wenn der Konstruktor
fertig ausgeführt ist?
Wenn es keine happens-before-Beziehung gibt: Nein. Beispiel:

"The most obvious reason is that the writes which initialize instance
and the write to the instance field can be reordered by the compiler or
the cache, which would have the effect of returning what appears to be a
partially constructed Something. The result would be that we read an
uninitialized object."
https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl
Post by Christian H. Kuhn
StringBuilder nameBuilder = new StringBuilder();
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
_parent.getName(); // das geht
name = _parent.getName(); // das geht nicht
test.setObject(_parent.getName()); // das geht
staticName = _parent.getName(); // das geht
nameBuilder.append(_parent.getName()); // das geht
}
});
Die letzten drei gehen alle nicht, weil wieder nicht garantiert ist,
dass der main-Thread die Resultate der im EDT ausgeführten Aktion je zu
sehen bekommt. (Schlimmstenfalls bekommt er sie nur teilweise zu sehen.)

public static <T> T fromEDT(Supplier<T> block)
throws InterruptedException {
final AtomicReference<T> ret = new AtomicReference<>();
try {
SwingUtilities.invokeAndWait(() -> ret.set(block.get()));
}
catch (InvocationTargetException exc) {
Throwable cause = exc.getCause();
if(cause instanceof Error) {
throw (Error)cause;
}
if(cause instanceof RuntimeException) {
throw (RuntimeException)cause;
}
// shouldn't happen - no checked exceptions from Supplier/Runnable
throw new RuntimeException(cause);
}
return ret.get();
}

String name = SwingHelper.fromEDT(() -> _parent.getName());

Viele Grüße,
Patrick
Christian H. Kuhn
2016-07-16 21:05:56 UTC
Permalink
Post by Patrick Roemer
Post by Christian H. Kuhn
Ich hoffe, dass die
VM sowas verhindert und ein Objekt erst da ist, wenn der Konstruktor
fertig ausgeführt ist?
Wenn es keine happens-before-Beziehung gibt: Nein.
Google hat mir erzählt, dass ab (oder nach, egal, ich nutze 1.8) Java
1.5 volatile den von mir gewünschten Effekt ergibt, wenn der Konstruktor
sich nicht vor fertigem Aufbau des Objekts in externe Listen o.ä. einträgt.
Post by Patrick Roemer
public static <T> T fromEDT(Supplier<T> block)
throws InterruptedException {
final AtomicReference<T> ret = new AtomicReference<>();
try {
SwingUtilities.invokeAndWait(() -> ret.set(block.get()));
}
catch (InvocationTargetException exc) {
Throwable cause = exc.getCause();
if(cause instanceof Error) {
throw (Error)cause;
}
if(cause instanceof RuntimeException) {
throw (RuntimeException)cause;
}
// shouldn't happen - no checked exceptions from Supplier/Runnable
throw new RuntimeException(cause);
}
return ret.get();
}
String name = SwingHelper.fromEDT(() -> _parent.getName());
Zuerst nur übernommen, inzwischen auch verstanden. Lambda expressions
sind schon c00l :-)

v0.8.6

lg
QNo
Christian H. Kuhn
2016-07-17 14:02:48 UTC
Permalink
Post by Christian H. Kuhn
v0.8.6
Und voll über’s Ziel hinausgeschossen.

In der Swing-GUI liegen echte Clicks (und inzwischen auch doClick() aus
den Tests) auf dem EDT. Die entsprechenden Methoden von QChessClock
werden nie niemals nicht von woanders direkt aufgerufen und liegen
ebenfalls auf dem EDT. Diese Methoden rufen notifyXY auf, was wieder
updateXY der GUI aufruft. Immer noch alles auf dem EDT. Damit muss
SwingUtilities.invoke...() in allen diesen Funktionen NICHT benutzt
werden. (G)UI ohne Swing und Tests, die QChessClockInterface
implementieren, müssen da halt aufpassen. Fehlschlagende Tests haben mir
jedenfalls gut gezeigt, wo noch ein notify oder update in den Thread
hineinmuss. Damit werfen diese ganzen Methoden auch nicht mehr die
Exceptions von invoke...(), und das uncaughtExceptions-Gehampel um
actionPerformed() entfällt.

Etwas Anderes ist es mit dem notifyObservers(), das getaktet ausgelöst
wird. Das läuft definitiv auf einem anderen Thread. Hier wird aber nur
Zeit und Blättchenfall kontrolliert. Da gibt es dann eine
Methodendoppelung. QChessClockObserver müssen dann für Zeit und Flags
eine weitere Update-Methode anbieten, in der sie sich z.B. in Swing
darum kümmern, dass die Zugriffe auf den GUi-Status auf den EDT gelegt wird.

Jetzt sortiere ich noch ein wenig. Das wird v0.8.7

lg
QNo
Lesen Sie weiter auf narkive:
Loading...