Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Spiel-Performance verbessern

0 views
Skip to first unread message

Jens Lehmann

unread,
Jul 4, 2003, 5:00:52 AM7/4/03
to
Hallo,

in einem einfachen Spiel lade ich Bilder folgendermaßen (das ganze Spiel
ist in einer jar-Datei):

private Image foo =
getToolkit().getImage(getClass().getResource("foo.png"));

Das Spiel implementiert Runnable. Die Run-Methode wird aller 40 ms
durchlaufen und ruft ein repaint() auf. In der paint-Methode zeichne ich
Bilder z.B. mit

g2.drawImage(foo, xPos, yPos, this);

Das Ganze läuft auf einem JPanel mit DoubleBuffer ab. Dieses JPanel
liegt in einem JFrame, welches zusätzlich Statusanzeigen zum Spiel enthält.

Das Spiel funktioniert sehr gut, jedoch ist die Performance nicht sehr
beeindruckend (abhängig von Plattform und Java-Version). Es wäre
natürlich schön, wenn das Spiel auch auf langsamen Rechnern flüssig
laufen würde, zumal es wirklich nicht sehr umfangreich ist. Könnt Ihr
mir Tipps geben, wie man die Performance steigern kann?

Jens

Stefan Matthias Aust

unread,
Jul 4, 2003, 5:05:21 AM7/4/03
to
Jens Lehmann wrote:

> Das Spiel funktioniert sehr gut, jedoch ist die Performance nicht sehr
> beeindruckend (abhängig von Plattform und Java-Version). Es wäre
> natürlich schön, wenn das Spiel auch auf langsamen Rechnern flüssig
> laufen würde, zumal es wirklich nicht sehr umfangreich ist. Könnt Ihr
> mir Tipps geben, wie man die Performance steigern kann?

Um den Eindruck des flüssigeren Laufens zu gewährleisten, würde ich
nicht eine feste Bildwiederholungsrate (deine 40ms) definieren, sondern
die Animationen alle von der Zeit abhängig machen und dann eben so viele
Durchläufe durch die Hauptschleife starten, wie das System hergibt.

Die 40ms kannst du systembedingt eh nicht genau genug einhalten.
Außerdem kann je nach Spiel eventuell so viel Objektmüll erzeugt werden,
dass die Pausen durch die garbage collection spürbar wird. Diesen
Effekt kannst du mit der Kommandozeilenoption "-Xincgc" reduzieren oder
sogar beseitigen.

An dem Verfahren, wie du Bilder lädst und malst ist nichts auszusetzen.


bye
--
Stefan Matthias Aust // "Ist es normal, nur weil alle es tun?" -F4

Mathias Weyel

unread,
Jul 4, 2003, 6:11:05 AM7/4/03
to
"Jens Lehmann" schrieb ...

> Das Spiel implementiert Runnable. Die Run-Methode wird aller 40 ms
> durchlaufen und ruft ein repaint() auf. In der paint-Methode zeichne ich
> Bilder z.B. mit
>
> g2.drawImage(foo, xPos, yPos, this);

Rufe in der run()-Methode nicht repaint() auf, sondern
paint(this.getGraphics()), oder überschreibe die repaint()-Methode
entsprechend, daß nur paint() aufgerufen wird; die Graphics-Referenz kannst
Du von dem Objekt auf dem Du zeichnest mit getGraphics() holen. Dann sollte
alles wesentlich besser laufen; ich hatte bis gestern das Gleiche Problem.


Jens Lehmann

unread,
Jul 4, 2003, 6:27:29 AM7/4/03
to
Stefan Matthias Aust wrote:
> Jens Lehmann wrote:
>
>> Das Spiel funktioniert sehr gut, jedoch ist die Performance nicht sehr
>> beeindruckend (abhängig von Plattform und Java-Version). Es wäre
>> natürlich schön, wenn das Spiel auch auf langsamen Rechnern flüssig
>> laufen würde, zumal es wirklich nicht sehr umfangreich ist. Könnt Ihr
>> mir Tipps geben, wie man die Performance steigern kann?
>
>
> Um den Eindruck des flüssigeren Laufens zu gewährleisten, würde ich
> nicht eine feste Bildwiederholungsrate (deine 40ms) definieren, sondern
> die Animationen alle von der Zeit abhängig machen und dann eben so viele
> Durchläufe durch die Hauptschleife starten, wie das System hergibt.

Wie realisiere ich das?

Die run-Methode sieht folgendermaßen aus:

while (remainingTurns > 0)
{
// ... viele Anweisungen

// aktuellen Durchlauf hochzählen
threadCurrentTurn ++;

// neuzeichnen
repaint();

try
{
Thread.sleep(threadSleepingTime);
}
catch (InterruptedException ex)
{
System.err.println("interrupted sleep");
}
}

In einer Zählvariable werden die Durchläufe durch die while-Schleife
gezählt und abhängig davon (per Zufall) neue Bilder erzeugt, die dann
von oben nach unten durch das JPanel wandern.

Damit das anschaulich wird, gebe ich doch mal den Link zum Spieldownload:
http://www.inf.tu-dresden.de/~swt03-35/game/waeschfangen.jar

Start einfach mit java -jar waeschefangen.jar oder Klick.

Jens

Frank Buss

unread,
Jul 4, 2003, 7:36:55 AM7/4/03
to
Jens Lehmann <jens.l...@goldmail.de> wrote:

>> Um den Eindruck des flüssigeren Laufens zu gewährleisten, würde ich
>> nicht eine feste Bildwiederholungsrate (deine 40ms) definieren,
>> sondern die Animationen alle von der Zeit abhängig machen und dann
>> eben so viele Durchläufe durch die Hauptschleife starten, wie das
>> System hergibt.
>
> Wie realisiere ich das?

Statt per sleep die Geschwindigkeit einzustellen, stellst du z.B. für
deine Wäschestücke eine Bewegungsgleichung der Form h(t) = v * t + h0
(h=Höhe, v=Geschwindigkeit, t=Zeit, h0=Starthöhe) auf. Für t setzt du
dann System.currentTimeMillis ein, abzüglich des Startzeitpunkts. Du
kannst natürlich auch gerne h(t)=a*t^2+v*t verwenden, um etwas
Gravitation mit einzubringen.

Das berechnest du jeweils in der paint-Methode, da der Aufruf des
repaints in der while-Schleife die paint-Methode nicht vorhersagbar
verzögert aufruft, was dann wieder ungleichmäßige Animationen geben
würde. Vielleicht brauchst du dann die while-Schleife gar nicht mehr und
kannst das repaint ans Ende des paint setzen? Musst du mal ausprobieren.

--
Frank Buß, f...@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de

Andree Große

unread,
Jul 4, 2003, 7:30:42 AM7/4/03
to
Jens Lehmann wrote:
> ...

> Damit das anschaulich wird, gebe ich doch mal den Link zum Spieldownload:
> http://www.inf.tu-dresden.de/~swt03-35/game/waeschfangen.jar
>
> Start einfach mit java -jar waeschefangen.jar oder Klick.

C:\java\projects\newsgroup>java -jar waeschefangen.jar
Exception in thread "main" java.util.zip.ZipException: Das System kann die
angegebene Datei nicht finden
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(Unknown Source)
at java.util.jar.JarFile.<init>(Unknown Source)


C:\java\projects\newsgroup>java -version
java version "1.3.1_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_04-b02)
Java HotSpot(TM) Client VM (build 1.3.1_04-b02, mixed mode)


A.G.


Frank Buss

unread,
Jul 4, 2003, 7:47:59 AM7/4/03
to
Andree Große <A.Gr...@deutschepost.de> wrote:

> C:\java\projects\newsgroup>java -version
> java version "1.3.1_04"
> Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_04-b02)
> Java HotSpot(TM) Client VM (build 1.3.1_04-b02, mixed mode)

C:\tmp>java -version
java version "1.4.1_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_02-b06)
Java HotSpot(TM) Client VM (build 1.4.1_02-b06, mixed mode)

Klick -> läuft.

Andree Große

unread,
Jul 4, 2003, 8:19:22 AM7/4/03
to
Frank Buss wrote:
> ...
>
> Klick -> läuft.


Klick-Starter sind irgendwie 'n bissel krank. Das erste, was
ich dem OS sofort abgewöhne, wenn Java installiert wurde.
Im übrigen ist das kein Argument dafür, dass es per Kdo-Zeile
nicht geht.

Klick & Klack, Drag & Drops etc... - die heile Klicki-Bunti-Welt,
in der GUI's halt mal fix zusammen geklickt werden und Anwendungen
nicht mit Inhalt sondern mit hüpfenden Männchen gänzlich ausgefüllt
sind.

A.G.

Jens Lehmann

unread,
Jul 4, 2003, 8:43:44 AM7/4/03
to
Andree Große wrote:
> Jens Lehmann wrote:
>
>> ...
>> Damit das anschaulich wird, gebe ich doch mal den Link zum Spieldownload:
>> http://www.inf.tu-dresden.de/~swt03-35/game/waeschfangen.jar
>>
>> Start einfach mit java -jar waeschefangen.jar oder Klick.
>
>
> C:\java\projects\newsgroup>java -jar waeschefangen.jar
> Exception in thread "main" java.util.zip.ZipException: Das System kann
> die angegebene Datei nicht finden
> at java.util.zip.ZipFile.open(Native Method)
> at java.util.zip.ZipFile.<init>(Unknown Source)
> at java.util.jar.JarFile.<init>(Unknown Source)

Da ist wohl ein "e" zuviel im Befehl, also

java -jar waeschfangen.jar

Jens

Axel Sachmann

unread,
Jul 4, 2003, 8:52:02 AM7/4/03
to
Andree Große wrote:

> Frank Buss wrote:
>
>> ...
>>
>> Klick -> läuft.
>
>
>
> Klick-Starter sind irgendwie 'n bissel krank. Das erste, was
> ich dem OS sofort abgewöhne, wenn Java installiert wurde.
> Im übrigen ist das kein Argument dafür, dass es per Kdo-Zeile
> nicht geht.

Vielleicht solltest du es angeschaltet lassen - dann läuft das Programm
auch bei dir......

Max Pauer

unread,
Jul 4, 2003, 8:53:21 AM7/4/03
to
Andree Große wrote:

[KlickiBuntiBashi]

Hey, heute ist der Vorabend des Wochenendes.
Niemand kann nur auf Deine Vorlieben Rücksicht nehmen.
Respekt ist eine Tugend, die gerade freitag nachmittag
selbstverständlich sein sollte. Hat Dir jemand
das wöchentliche Release versaut?

Smoooooooooth :)

Jens Lehmann

unread,
Jul 4, 2003, 9:01:25 AM7/4/03
to
Mathias Weyel wrote:
> "Jens Lehmann" schrieb ...
>
>
>>Das Spiel implementiert Runnable. Die Run-Methode wird aller 40 ms
>>durchlaufen und ruft ein repaint() auf. In der paint-Methode zeichne ich
>>Bilder z.B. mit
>>
>>g2.drawImage(foo, xPos, yPos, this);
>
>
> Rufe in der run()-Methode nicht repaint() auf, sondern
> paint(this.getGraphics()),

Das führt bei mir zu starkem Flimmern.

> oder überschreibe die repaint()-Methode
> entsprechend, daß nur paint() aufgerufen wird; die Graphics-Referenz kannst
> Du von dem Objekt auf dem Du zeichnest mit getGraphics() holen. Dann sollte
> alles wesentlich besser laufen; ich hatte bis gestern das Gleiche Problem.

Bei mir ergibt das keine Verbesserung. Ich hatte bereits die
update-Methode so umgeschrieben, dass diese nur paint() aufruft.

Jens

Mathias Weyel

unread,
Jul 4, 2003, 9:22:08 AM7/4/03
to
Jens Lehmann schrieb:

> Bei mir ergibt das keine Verbesserung. Ich hatte bereits die
> update-Methode so umgeschrieben, dass diese nur paint() aufruft.

Du kannst versuchen, in der paint()-Methode Dein eigenes Double-Buffering zu
implementieren. Dann könnte es klappen.
Beispiel:

public void paint(Graphics graphics) {
bild = (BufferedImage) this.createImage(getWidth() , getHeight());
Graphics2D g = bild.createGraphics();

// Hier mit Hilfe von g zeichnen...

graphics.drawImage(bild , 0 , 0);
}


Frank Buss

unread,
Jul 4, 2003, 9:23:59 AM7/4/03
to
Jens Lehmann <jens.l...@goldmail.de> wrote:

>> Rufe in der run()-Methode nicht repaint() auf, sondern
>> paint(this.getGraphics()),
>
> Das führt bei mir zu starkem Flimmern.

Das ist auch nicht zu empfehlen, da dann nicht aus dem UI-Thread heraus
gezeichnet wird.

Alternativ kannst du dir deine eigene Zeichenschleife mit BufferStrategy
und mehreren Offscreen-Images bauen, die dann per Page-Flip umgeschaltet
werden, wie hier zu sehen:

http://www.frank-buss.de/demo/

> Bei mir ergibt das keine Verbesserung. Ich hatte bereits die
> update-Methode so umgeschrieben, dass diese nur paint() aufruft.

Ich habe eben mal deine SpielPanel-Klasse decompiliert, da du leider
keinen Quelltext beigelegt hast und ein möglicher Grund, neben dem schon
von SMA erwähnten, könnte sein, daß du mehrere Aufrufe auf dem
Graphics2D-Objekt machst. Lege besser ein Offscreen-Image an, zeichne
alles da drauf und dann nur noch dieses Image ins Graphics2D-Objekt.

Jens Lehmann

unread,
Jul 4, 2003, 9:25:43 AM7/4/03
to

Das hatte ich auch mal gemacht, aber JPanel macht den DoubleBuffer
bereits selber. Das lässt sich über den Konstruktor einstellen.

Jens

Frank Buss

unread,
Jul 4, 2003, 9:59:57 AM7/4/03
to
Jens Lehmann <jens.l...@goldmail.de> wrote:

> Das hatte ich auch mal gemacht, aber JPanel macht den DoubleBuffer
> bereits selber. Das lässt sich über den Konstruktor einstellen.

Nein, macht es bei deiner Implementierung nicht, da du fälschlicherweise
die paint-Methode statt die paintComponent-Methode überschrieben hast.
Standardmäßig ist double buffering sowieso immer eingeschaltet (bei Swing)
und mit der paintComponent-Methode kannst du dir dann den manuellen
DoubleBuffer sparen.

Mehr Hintergrundinformationen findest du hier:

http://java.sun.com/products/jfc/tsc/articles/painting/index.html#paint_process

Andree Große

unread,
Jul 6, 2003, 12:53:44 AM7/6/03
to
Axel Sachmann wrote:
> Vielleicht solltest du es angeschaltet lassen - dann läuft das Programm
> auch bei dir......

Ganz sicher nicht. Ich sehe schon - die Kdo-zeile
ist nicht das Ding einiger Java-Entwickler.
A.G.


Andree Große

unread,
Jul 6, 2003, 12:59:16 AM7/6/03
to
Jens Lehmann wrote:
>
> Da ist wohl ein "e" zuviel im Befehl, also
>
> java -jar waeschfangen.jar

That's was it. Eine etwas undlückliche Bezeichnung. ;-))
Was so ein kleines 'e' alles ausmacht. (Brille putzen..)
A.G.


Jens Lehmann

unread,
Jul 6, 2003, 6:51:22 AM7/6/03
to
Frank Buss wrote:

> Jens Lehmann <jens.l...@goldmail.de> wrote:
>
> Ich habe eben mal deine SpielPanel-Klasse decompiliert, da du leider
> keinen Quelltext beigelegt hast und ein möglicher Grund, neben dem schon
> von SMA erwähnten, könnte sein, daß du mehrere Aufrufe auf dem
> Graphics2D-Objekt machst. Lege besser ein Offscreen-Image an, zeichne
> alles da drauf und dann nur noch dieses Image ins Graphics2D-Objekt.

Warum ist das günstiger? Wenn ich es richtig verstanden habe (was
wahrscheinlich nicht der Fall ist), dann zeichne ich doch bereits auf
einem Buffer, der erst dann angezeigt wird, wenn er fertig ist.

Meinst Du so etwas an den Anfang der paintComponent-Methode zu setzen?

BufferedImage bild = (BufferedImage) this.createImage(getWidth() ,
getHeight());
Graphics2D g2 = bild.createGraphics();

Im weiteren soll dann g2 zum Zeichnen benutzt werden, oder?

Am Ende dann das Bild auf den Parameter g2real zeichnen?

g2real.drawImage(bild , 0 , 0, this);

So habe ich es probiert und sehe keinen Unterschied. Läuft das Spiel
eigentlich bei Dir flüssig? Welche Java-Version, Betriebssystem und
Rechner hast Du?

Jens


Jens Lehmann

unread,
Jul 6, 2003, 6:54:47 AM7/6/03
to
Frank Buss wrote:
> Jens Lehmann <jens.l...@goldmail.de> wrote:
>
>
>>Das hatte ich auch mal gemacht, aber JPanel macht den DoubleBuffer
>>bereits selber. Das lässt sich über den Konstruktor einstellen.
>
>
> Nein, macht es bei deiner Implementierung nicht, da du fälschlicherweise
> die paint-Methode statt die paintComponent-Methode überschrieben hast.
> Standardmäßig ist double buffering sowieso immer eingeschaltet (bei Swing)
> und mit der paintComponent-Methode kannst du dir dann den manuellen
> DoubleBuffer sparen.
>
> Mehr Hintergrundinformationen findest du hier:
>
> http://java.sun.com/products/jfc/tsc/articles/painting/index.html#paint_process

Ich habe jetzt einfach die paint-Methode durch paintComponent ersetzt.
Einen Unterschied sehe ich leider nicht. Ich vermute fast, dass das
Problem doch spezifisch für meinen Rechner ist (warum auch immer). Siehe
auch den Thread "Performanceproblem mit J2SDK 1.4.1". Danke Dir trotzdem
für die vielen Hilfestellungen.

Jens

Jens Lehmann

unread,
Jul 6, 2003, 7:04:20 AM7/6/03
to
Frank Buss wrote:
> Jens Lehmann <jens.l...@goldmail.de> wrote:
>
>
> Statt per sleep die Geschwindigkeit einzustellen, stellst du z.B. für
> deine Wäschestücke eine Bewegungsgleichung der Form h(t) = v * t + h0
> (h=Höhe, v=Geschwindigkeit, t=Zeit, h0=Starthöhe) auf. Für t setzt du
> dann System.currentTimeMillis ein, abzüglich des Startzeitpunkts. Du
> kannst natürlich auch gerne h(t)=a*t^2+v*t verwenden, um etwas
> Gravitation mit einzubringen.
>
> Das berechnest du jeweils in der paint-Methode, da der Aufruf des
> repaints in der while-Schleife die paint-Methode nicht vorhersagbar
> verzögert aufruft, was dann wieder ungleichmäßige Animationen geben
> würde. Vielleicht brauchst du dann die while-Schleife gar nicht mehr und
> kannst das repaint ans Ende des paint setzen? Musst du mal ausprobieren.

Das habe ich jetzt ebenfalls probiert. Es funktioniert gut und ist die
bessere Variante. Leider läuft das Spiel trotzdem nicht flüssiger. Ein
repaint-Aufruf direkt in paint scheint mir gefährlich, aber das ging
auch, wenn ich vorher ein kurzes sleep einbaue.

Jens

Frank Buss

unread,
Jul 6, 2003, 7:43:27 AM7/6/03
to
Jens Lehmann <jens.l...@goldmail.de> wrote:

> Warum ist das günstiger? Wenn ich es richtig verstanden habe (was
> wahrscheinlich nicht der Fall ist), dann zeichne ich doch bereits auf
> einem Buffer, der erst dann angezeigt wird, wenn er fertig ist.

Ja, das ist dann dasselbe und du brauchst keinen manuellen Doublebuffer.

> So habe ich es probiert und sehe keinen Unterschied. Läuft das Spiel
> eigentlich bei Dir flüssig? Welche Java-Version, Betriebssystem und
> Rechner hast Du?

Läuft bei mir flüssig mit Suns Java 1.4.1_02 auf Windows XP mit einem 1.5
GHz P4. Allerdings sieht die Bewegung der Teile etwas wie bei Stroboskop-
Licht-Beleuchtung aus, was wahrscheinlich daran liegt, daß du nicht mit dem
Vertical-Retrace synchronisiert. Ich glaube per Page-Flip wird das
automatische mit dem Retrace synchronisiert, was aber nur im Fullscreen-
Modus geht:

http://javaalmanac.com/egs/java.awt/screen_Flip.html

Jens Lehmann

unread,
Jul 6, 2003, 6:54:23 PM7/6/03
to
Frank Buss wrote:
> Jens Lehmann <jens.l...@goldmail.de> wrote:
>
> Statt per sleep die Geschwindigkeit einzustellen, stellst du z.B. für
> deine Wäschestücke eine Bewegungsgleichung der Form h(t) = v * t + h0
> (h=Höhe, v=Geschwindigkeit, t=Zeit, h0=Starthöhe) auf. Für t setzt du
> dann System.currentTimeMillis ein, abzüglich des Startzeitpunkts. Du
> kannst natürlich auch gerne h(t)=a*t^2+v*t verwenden, um etwas
> Gravitation mit einzubringen.
>
> Das berechnest du jeweils in der paint-Methode, da der Aufruf des
> repaints in der while-Schleife die paint-Methode nicht vorhersagbar
> verzögert aufruft, was dann wieder ungleichmäßige Animationen geben
> würde. Vielleicht brauchst du dann die while-Schleife gar nicht mehr und
> kannst das repaint ans Ende des paint setzen? Musst du mal ausprobieren.

Mittlerweile habe ich das gesamte Spiel so umgeschrieben, dass alle
Ereignisse unabhängig von der Framerate sind. Das Thread.sleep brauche
ich jetzt theoretisch nicht mehr. Ich setze es dennoch ein (1ms), da
Frameraten im vierstelligen Bereich nicht so sinnvoll sind. Jetzt läuft
es unter Java 1.4.1 bei mir besser wie vorher. Danke nochmal für die
Hinweise.

Jens

Axel Sachmann

unread,
Jul 7, 2003, 4:39:19 AM7/7/03
to
Dein Ding anscheinend auch nicht - hast es ja selber geschrieben das es
bei dir nicht läuft.

>

>

Andree Große

unread,
Jul 7, 2003, 11:54:14 AM7/7/03
to
Axel Sachmann wrote:
>
> Dein Ding anscheinend auch nicht - hast es ja selber geschrieben das es
> bei dir nicht läuft.

Na dann solltest du mal Brille putzen. Es lag an
der überaus glücklichen Bezeichnung des Archives,
die da lautete: waeschfangen.jar statt wie von mir
eingegeben waeschefangen.jar.

Guckst du - lesen bildet und wo ein 'e' zu viel,
da nix gehen wollen.

A.G.
btw: alle meine progs starten auf der kdo-zeile


Frank Buss

unread,
Jul 7, 2003, 12:41:41 PM7/7/03
to
Andree Große <A.Gr...@deutschepost.de> wrote:

> Guckst du - lesen bildet und wo ein 'e' zu viel,
> da nix gehen wollen.

Auf die Idee wäre ich gar nicht gekommen, da ich auch viel von der
Kommandozeile arbeite, aber die Tab-Completion eingeschaltet habe und
somit eigentlich nie Dateinamen selbst ausschreiben brauche, mal
abgesehen davon, daß ein Doppelklick in diesem Fall schneller ist.

HKEY_CURRENT_USER\Software\Microsoft\Command Processor\CompletionChar, REG_DWORD auf 9:
http://www.microsoft.com/technet/treeview/default.asp?url=/technet/prodtechnol/windowsserver2003/proddocs/entserver/cmd.asp

Jens Lehmann

unread,
Jul 7, 2003, 3:35:57 PM7/7/03
to
Andree Große wrote:
> Axel Sachmann wrote:
>
>>
>> Dein Ding anscheinend auch nicht - hast es ja selber geschrieben das
>> es bei dir nicht läuft.
>
>
> Na dann solltest du mal Brille putzen. Es lag an
> der überaus glücklichen Bezeichnung des Archives,
> die da lautete: waeschfangen.jar statt wie von mir
> eingegeben waeschefangen.jar.

Derjenige, der es hochgeladen hat, hat beim umbennen ein "e" vergessen.
Ist mir auch nicht gleich aufgefallen. Streitet Euch deswegen nicht. ;-)

Jens

0 new messages