Swing Programm läuft nur beim Debuggen korrekt ab

Hallo,

ich würde gerne mein Problem vorstellen und zwar handelt es sich um ein aus meiner Ansicht nach sehr "mysteriöses" Problem. (das aber natürlich trotzdem eine vollkommen rationale Erklärung haben muss, die mir nur nicht einleuchten will.)

Und zwar habe ich ein kleines Programm erstellt, bei dem ein kleiner Ball fällt durch eine Gravity-Klasse. Beim "Aufschlagen" (also die Geschwindigkeit des Balls beim Auftreffen auf den Boden muss eine Mindestgeschwindigkeit überschreiten, dass das Auftreffen als Aufschlagen gilt.) kommt eine Bounce-Klasse hinzu, die nun berechnet, wie sehr der Ball nach oben "dopst" je nach Aufschlaggeschwindigkeit.

Mein Problem ist, dass beim normalen Ablauf nie die Kondition ob der Ball aufgeschlagen ist in der Bounce-Klasse irgendwie abgefragt wird obwohl beide Threads (Gravity & Bounce) laufen.
Beim Debuggen mit einem Breakpoint auf der if-Abfrage funktioniert das Programm plötzlich. Zwar noch nicht perfekt, aber der Ball "dopst" jedenfalls überhaupt irgendwie sichtbar.

Hier sind die betroffenen Klassen:

Java:
package gui;

import cstm.io.CustomIOHandler;
import cstm.geom.HitDetector;
import java.awt.*;
/**
*
* @author EKzyis
* @version 1.1
*/
public class Ball {
    final static boolean d = true;
    final double MASS = 10;
    final int SIZE = 25;
    final Color COLOR = Color.RED;
    boolean ground = false;
    boolean hit = false;
    boolean dragged = false;
    double v;

    /**
     * @param x X-Koordinate des Mittelpunkt des Kreises
     * @param y Y-Koordinate des Mittelpunkt des Kreises.
     */
    int x,y;
    PE_Panel panel;

    /**
     * Einrichtung des Balls
     * @param panel Zeichenfläche, auf der der Ball gezeichnet wird
     */
    public Ball(PE_Panel panel) {
    CustomIOHandler.setDebugMode(d,this);
    x = panel.getPreferredSize().width/2;
    y = panel.getPreferredSize().height/2;
    this.panel = panel;
    }

    /**
     * Legt fest, ob der Ball anvisiert wurde mit einer
     * quadratischen Hitbox, dessen Kanten auf dem Kreis liegen.
     * @param x1 X-Koordinate der Maus
     * @param y1 Y-Koordinate der Maus
     * @return getroffen oder nicht.
     */
    public boolean quadrat_isHit(int x1,int y1) {
    CustomIOHandler.println("x_ball: "+x+"-"+(x+SIZE)+" ; x_maus: " +x1 );
    CustomIOHandler.println("y_ball: "+y+"-"+(y+SIZE)+" ; y_maus " + y1);
    return HitDetector.recIsHit(x-SIZE/2,y-SIZE/2,SIZE,SIZE,x1,y1);
    }

    /**
     * Legt fest, ob er Ball anvisiert wurde mit einer
     * Hitbox, die den Kreis ideal wiedergibt.
     * @param x1 X-Koordinate der Maus
     * @param y1 Y-Koordinate der Maus
     * @return getroffen oder nicht.
     */
    public boolean circle_isHit(int x1, int y1) {
    CustomIOHandler.println("x_ball: "+x+" mit Durchmesser von: "+SIZE+" ; x_maus: " +x1 );
    CustomIOHandler.println("y_ball: "+y+" mit Durchmesser von: "+SIZE+" ; y_maus " + y1);
    return HitDetector.circleIsHit(x, y, SIZE/2, x1, y1);
    }
    /**
     *
     * @return Farbe des Balls
     */
    public Color getColor() {
    return COLOR;
    }

    /**
     *
     * @return X-Koordinate des Balls.
     */
    public int getX() {
    return x;
    }

    /**
     *
     * @return Y-Koordinate des Balls
     */
    public int getY() {
    return y;
    }

    /**
     * gibt Größe des Balls zurück
     * @return Größe des Balls
     */
    public int getSize() {
    return SIZE;
    }

    /**
     * setzt X fest
     * @param x neue X-Koordinate des Balls.
     */
    public void setX(int x) {
    this.x = x;
    }

    /**
     * setzt Y fest
     * @param y neue Y-Koordinate des Balls.
     */
    public void setY(int y) {
    this.y = y;
    if(y+SIZE/2>=panel.getPreferredSize().height){
        CustomIOHandler.println("Ball is on the floor. -ball");
        setIsOnTheGround(true);
        this.y = panel.getPreferredSize().height-SIZE/2;
    }
    else {
        setIsOnTheGround(false);
    }
    }

    /**    
     * Setzt fest, ob der Ball auf dem Boden liegt und
     * berechnet anhand einer Mindestgeschwindigkeit,
     * ob der Ball aufgeschlagen ist oder nicht.
     * (sonst bounct er ewig.)
     * @param set auf dem Boden oder nicht
     */
    public void setIsOnTheGround(boolean set) {
    ground = set;
    if(ground && getV()>1){
    
        setHitTheGround(true);
    }
    else{
        setHitTheGround(false);
    }
    }
    /**
     * Gibt zurück, ob der Ball den Boden berührt.
     * @return auf dem Boden oder nicht
     */
    public boolean isOnTheGround(){
    return ground;
    }

    /**
     * setzt fest, ob der Ball soeben auf den Boden aufgeschlagen ist.
     * @param set hit oder nicht
     */
    public void setHitTheGround(boolean set) {
    hit = set;
    if(hitTheGround()) {
        CustomIOHandler.println("Ball hit the ground with v = " + getV() + " -ball ");
    }
    }

    /**
     * Ist der Ball soeben aufgeschlagen?
     * @return hit oder nicht
     */
    public boolean hitTheGround() {
    return hit;
    }
    /**
     * Wird der Ball gerade bewegt vom User?
     * @return bewegt oder nicht
     */
    public boolean isDragged(){
    return dragged;
    }

    /**
     * Setzt fest, ob der Ball bewegt wird.
     * @param set wird bewegt oder nicht
     */
    public void setDragging(boolean set){
    dragged = set;
    }

    /**
     * Lässt den Ball wissen, wie schnell er bewegt wird.
     * Negative Geschwindigkeit bedeutet steigend,
     * postive Geschwindigkeit bedeutet fallend.
     * @param speed aktuelle Geschwindigkeit des Balls
     */
    public void setV(double speed) {
    this.v = speed;
    }

    /**
     * Gibt aktuelle Geschwindigkeit des Balls zurück.
     * Negative Geschwindigkeit bedeutet steigend,
     * postive Geschwindigkeit bedeutet fallend.
     * @return aktuelle Geschwindigkeit des Balls
     */
    public double getV() {
    return v;
    }

    /**
     * Gibt Masse des Balls zurück (für bspw. Impulsberechnungen.)
     * @return Masse des Balls
     */
    public double getMass() {
    return MASS;
    }
}

Java:
package cmd;

import cstm.io.CustomIOHandler;
import cstm.io.Flag;
import gui.Ball;
import gui.PE_Panel;

/**
* Klasse, die für die Schwerkraft eines Balls zuständig ist.
* @author EKzyis
* @version 1.0
*/
public class Gravity extends Thread {
    final static boolean d = true;
    final static double ACCELERATION = 9.81;
    Ball ball;
    PE_Panel panel;
    Flag flag;
    double t,dT=0.1,v;

    /**
     * Einrichtung Gravity
     * @param ball Ball-Instanz
     * @param panel Zeichenfläche
     */
    public Gravity(Ball ball, PE_Panel panel) {
    flag = new Flag();
    this.ball = ball;
    this.panel = panel;
    CustomIOHandler.setDebugMode(d,this);
    CustomIOHandler.setBrandingMode(false);
    }
    @Override
    public void run() {
    while(true) {
        /**
         * Wenn Ball auf dem Boden ist, und noch kein "onthefloor"-Flag gesetzt wurde
         * (verhindert mehreres Anzeigen von "Ball is on the floor."):
         */
        if(ball.isOnTheGround() && !flag.contains("onthefloor")) {
        flag.addFlag("onthefloor");
        flag.removeFlag("falling");
        }
        /**
         * Refresh-Intervall = 0,1s
         */
        try {
        sleep(100);
        } catch (InterruptedException ex){
        CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
        }
        /**
         * Solange Ball nicht auf dem Boden ist & Ball nicht vom User gezogen wird:
         */
        while(!ball.isOnTheGround() && !ball.isDragged()) {    
        flag.addFlag("running");
        /**
         * Wenn noch kein "falling"-Flag gesetzt wurde (verhindert mehreres Anzeigen von "Ball is falling, watch out!")
         * (Das bedeutet, dass Ball nun beginnt zu fallen, t=0):
         */
        if(!flag.contains("falling")){
            CustomIOHandler.println("Ball is falling, watch out! -gravity");
            flag.addFlag("falling");
            flag.removeFlag("onthefloor");
            t=0;
        }
        /**
         * Refresh-Intervall 10ms
         */
        try {
            sleep(10);
        } catch (InterruptedException ex) {
            CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
        }    
        v = ACCELERATION*t;
        CustomIOHandler.println("v = "+v+ " -gravity");
        ball.setV(ACCELERATION*t);
        /**
         * alle 0,1s wird Ball-Instanz neue Y-Koordinate zugewiesen nach der Formel s = 0.5*a*(t^2)
         */
        ball.setY((int)(ball.getY()+(0.5*(ACCELERATION*t*t))));
        panel.repaint();
        t=t+dT;
        }
        t=0;
        flag.removeFlag("running");
    }

    }
}

Java:
package cmd;

import cstm.io.CustomIOHandler;
import cstm.io.Flag;
import gui.Ball;
import gui.PE_Panel;

/**
* Klasse, die für das Dopsen eines Balls zuständig ist.
* @author EKzyis
* @version 1.0
*/
public class Bounce extends Thread {
    final static boolean d = true;
    Ball b;
    PE_Panel panel;
    double t;
    double v0;
    Flag flag;
    public Bounce(Ball ball, PE_Panel panel){
    this.b = ball;
    this.panel = panel;
    CustomIOHandler.setDebugMode(d,this);
    CustomIOHandler.setBrandingMode(false);
    flag = new Flag();
    }

    @Override
    public void run() {
    while(true) {    
        if(b.hitTheGround()) {
        if(!flag.contains("bouncing")){
            CustomIOHandler.println("Ball is bouncing. -bounce");
            flag.addFlag("bouncing");
        }
        try {
        sleep(10);
        } catch (InterruptedException ex) {
            CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
        }
        t=0.1;
        b.setV(b.getV()*-1);
        v0 = b.getV();    
        while(b.getV()<0){
            CustomIOHandler.println("v0 = "+v0 +" -bounce");
            b.setY((int)(b.getY()+b.getV()*t));
            b.setV(v0+Gravity.ACCELERATION*t);
            panel.repaint();
            try {
            sleep(10);
            } catch (InterruptedException ex) {
            CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
            }
            t=t+0.1;
        }
        b.setHitTheGround(false);
        b.setIsOnTheGround(false);
        flag.removeFlag("bouncing");
        }
    }
    }
}

Java:
package gui;

import cstm.gui.CustomGraphicsHandler;
import cstm.io.CustomIOHandler;
import javax.swing.*;
import java.awt.*;

/**
* Zeichenklasse des Programms.
* @author EKzyis
*/
public class PE_Panel extends JPanel {
    final static boolean d = true;
    Ball b;
    /**
     * Einrichtung der Zeichenfläche
     */
    public PE_Panel() {
    this.setPreferredSize(new Dimension(500,1000));
    CustomIOHandler.setDebugMode(d,this);
    CustomIOHandler.setBrandingMode(false);
    }

    @Override
    public void paintComponent(Graphics g) {
    super.paintComponent(g);
    setBackground(Color.WHITE);
    g.setColor(b.getColor());
    //g.drawOval(b.getX(),b.getY(),b.getSize(),b.getSize());
    //Zeichnet einen Kreis und legt x und y als Mittelpunkt fest (nicht als oberen, linken Eckpunkt)
    CustomGraphicsHandler.fillCircle(g, b.getX(), b.getY(), b.getSize());
    g.setColor(Color.BLACK);
    int x = b.getX();
    int y = b.getY();
    int size = b.getSize();
    //g.drawRect(x-size/2,y-size/2,size,size);
    }

    public void addBall(Ball b) {
    this.b = b;
    }
}

Java:
package gui;

import cmd.Bounce;
import cmd.Gravity;
import cmd.PE_MouseAdapter;
import javax.swing.JFrame;

/**
* Hauptfenster des Programms.
* @author EKzyis
*/
public class PE_MFrame extends JFrame {
    PE_Panel panel;
    PE_MouseAdapter adapter;
    Ball ball;
    Gravity gravity;
 
    public PE_MFrame(String title) {
    super(title);
    setLocation(5,5);
    setResizable(false);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    panel = new PE_Panel();
    ball = new Ball(panel); 
    adapter = new PE_MouseAdapter();
    adapter.addPanel(panel);
    adapter.addBall(ball);
    gravity = new Gravity(ball,panel);
    Bounce bounce = new Bounce(ball,panel);
    panel.addMouseMotionListener(adapter);
    panel.addMouseListener(adapter);
    panel.addBall(ball);
    getContentPane().add(panel);
    pack();
    gravity.start();
    bounce.start();
    }
}
Wäre wirklich toll, wenn mir jemand helfen könnte, denn ich finde das wirklich sehr komisch.

Danke.
 
Zuletzt bearbeitet:
Kann nicht das ganze Programm hochladen, da komischerweise .rar-Dateien nicht erlaubt sind. Außerdem wird zum Ausführen eine eigene Bibliothek verwendet, da ich eine selber erstellt habe um nützliche Methoden für verschiedene Projekte zu nutzen... so könnt ihr aber nicht wirklich das Problem rekonstruieren :/ Aber vielleicht sieht ja jemand anhand des Codes schon was :/

edit:

Habe die Dateien auf Dropbox hochgeladen:

CustomLibrary: https://www.dropbox.com/s/vitag5oqrl6td05/cstmLibrary.rar?dl=0

PhysicsEngine: https://www.dropbox.com/s/2ns01x9ekw4xdlj/physics.rar?dl=0
 
Zuletzt bearbeitet:
Ist jetzt nur eine Vermutung aber klingt für mich danach als würden die Threads nicht sauber laufen.
Hast du jede Methode auf die ein anderer Thread zugreift mit synchronized geschützt (vorausgesetzt deine Methode braucht das aus irgendeinem Grund nicht)?
 
Habe das Problem inzwischen gelöst. Zwar weiß ich nicht genau, woran es lag, jedoch war die Lösung, dass ich den Bounce-Thread mit sleep(100) laufen lasse anstatt mit sleep(10). Habe aber nun auch die Threads thread-sicher gemacht und es läuft bestens 🙂

Anbei nochmal die beiden Thread-Klassen:

Java:
package cmd;

import cstm.io.CustomIOHandler;
import cstm.io.Flag;
import gui.Ball;
import gui.PE_Panel;
import static cmd.PE_Main.REFRESH_STANDBY;
import static cmd.PE_Main.REFRESH_RUNNING;
/**
* Klasse, die für die Schwerkraft eines Balls zuständig ist.
* @author EKzyis
* @version 1.0
*/
public class Gravity extends Thread {
    final static boolean d = true;
    final static double ACC_GRAVITY = 9.81;
    Ball ball;
    PE_Panel panel;
    Flag flag;
    double t,dT=0.1,v;
    int y0;
   
    /**
     * Einrichtung Gravity
     * @param ball Ball-Instanz
     * @param panel Zeichenfläche
     */
    public Gravity(Ball ball, PE_Panel panel) {
    flag = new Flag();
    this.ball = ball;
    this.panel = panel;
    CustomIOHandler.setDebugMode(d,this);
    CustomIOHandler.setBrandingMode(false);
    }
    @Override
    public void run() {
    while(true) {
        /**
         * Wenn Ball auf dem Boden ist, und noch kein "onthefloor"-Flag gesetzt wurde.
         */
        if(ball.isOnTheGround() && !flag.contains("onthefloor")) {
        flag.addFlag("onthefloor");               
        }   
        /**
         * Wenn Ball nicht auf dem Boden ist & Ball nicht vom User gezogen wird
         */
        if(!ball.isOnTheGround() && !ball.isDragged()) {       
        /**
         * Thread muss den Lock für Ball haben, um fortzuschreiten
         */
        synchronized(ball) {
            CustomIOHandler.println("Does Gravity have lock? - " +holdsLock(ball));
            /**
             * Solange Ball nicht auf dem Boden ist & Ball nicht vom User gezogen wird
             */
            while(!ball.isOnTheGround() && !ball.isDragged()) {
            /**
             * Wenn noch kein "falling"-Flag gesetzt wurde, wird nun eins gesetzt.
             * Verhindert mehreres Anzeigen von "Ball is falling.".
             * Das bedeutet, dass Ball nun beginnt zu fallen
             * -> t=0
             */
            if(!flag.contains("falling")){
                CustomIOHandler.println("↓ Ball is falling. -gravity");
                flag.addFlag("falling");
                flag.removeFlag("onthefloor");
                t=0;
                y0 = ball.getY();
            }
            /**
             * Neue Geschwindigkeit wird zugewiesen nach der Formel
             * v(t) = a * t
             */
            v = ACC_GRAVITY*t;
            CustomIOHandler.print("↓ v = "+v+ " ");
            ball.setV(ACC_GRAVITY*t);
            /**
             * Ball-Instanz wird neue Y-Koordinate zugewiesen nach der Formel
             * y(t) = y0+0.5gt²
             * ball.setY() löst ball.setIsOnTheGround() aus
             */
            ball.setY((int)(y0+(0.5*(ACC_GRAVITY*t*t))));
            panel.repaint();
            /**
             * Neuer Zyklus wird eingeleitet.
             */
            t=t+dT;
            /**
             * --- Refresh-Intervall "RUNNING"
             */
            try {
                sleep(REFRESH_RUNNING);
            } catch (InterruptedException ex) {
                CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
            }
            CustomIOHandler.println("↓ y = "+ball.getY()+" -gravity");
            }
        }       
        }       
        /**
        * Ball ist fertig mit fallen. Lock freigegeben.
        */
        flag.removeFlag("falling");
        /**
         * --- Refresh-Intervall "STANDBY"
         */
        try {
        sleep(REFRESH_STANDBY);
        } catch (InterruptedException ex){
        CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
        }       
    }   
    }
}

Java:
package cmd;

import cstm.io.CustomIOHandler;
import cstm.io.Flag;
import gui.Ball;
import gui.PE_Panel;
import static cmd.PE_Main.REFRESH_STANDBY;
import static cmd.PE_Main.REFRESH_RUNNING;
import static cmd.Gravity.ACC_GRAVITY;

/**
* Klasse, die für das Dopsen eines Balls zuständig ist.
* @author EKzyis
* @version 1.0
*/
public class Bounce extends Thread {
    final static boolean d = true;
    Ball ball;
    PE_Panel panel;
    double t, v0;
    int y0;
    Flag flag;
    public Bounce(Ball ball, PE_Panel panel){
    this.ball = ball;
    this.panel = panel;
    CustomIOHandler.setDebugMode(d,this);
    CustomIOHandler.setBrandingMode(false);
    flag = new Flag();
    }

    @Override
    public void run() {
    while(true) {   
        /**
         * Wenn der Ball noch nicht aufgeschlagen ist und noch kein "falling"-Flag
         * gesetzt wurde.
         */
        if(!ball.hitTheGround() && !flag.contains("falling")){
            flag.addFlag("falling");
        flag.removeFlag("bouncing");
        }
        /**
         * Sobald Ball auf den Boden eingeschlagen ist
         */
        if(ball.hitTheGround()){
        /**
         * Thread muss den Lock für Ball haben
         */
        synchronized(ball) {       
            CustomIOHandler.println("Does Bounce have lock? - " +holdsLock(ball));
            /**
             * Wenn noch kein "bouncing"-Flag gesetzt wurde, wird nun eins gesetzt.
             * Verhindert mehreres Anzeigen von "Ball is bouncing."
             * -> Ball beginnt zu bouncen.
             * -> t=0.1;
             */
            if(!flag.contains("bouncing")){
            CustomIOHandler.println("↑ Ball is bouncing. -bounce");
            flag.addFlag("bouncing");
            flag.removeFlag("falling");
            t=0;
            }
            /**
             * Anfangsgeschwindigkeit v0 = umgekehrte Aufprallgeschwindigkeit
             */
            ball.setV(-1*ball.getV());           
            v0 = ball.getV();
            /**
             * Speichern der Anfangshöhe als Nullniveau
             */
            y0 = ball.getY();
            CustomIOHandler.println("! v0 = "+v0 +" -bounce");
            /**
             * solange wie Geschwindigkeit des Balles kleiner als 0 ist,
             * behält Bounce den Lock für Ball.
             * -> Ball bounct
             */           
            while(ball.getV()<0){
            /**
             * Ball-Instanz wird neue Y-Koordinate zugewiesen nach der Formel
             * y(t) = y0+v0*t-0.5gt²
             */
            ball.setY((int)(y0+(v0*t+(0.5*(ACC_GRAVITY*t*t)))));
            //CustomIOHandler.println("↑ y = "+ball.getY()+" -bounce");
            /**
             * Ball-Instanz wird neue Geschwindigkeit zugewiesen nach der Formel
             * v(t) = v0*t+0.5gt²
             */
            ball.setV(v0+ACC_GRAVITY*t);
            CustomIOHandler.print("↑ v = "+ball.getV()+" ");
            panel.repaint();
            try {
            sleep(REFRESH_RUNNING);
            } catch (InterruptedException ex) {
            CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
            }
            t=t+0.1;
            CustomIOHandler.println("↑ y = "+ball.getY()+" -bounce");
            }           
        }
        }               
        flag.removeFlag("bouncing");
        /**
         * Thread in Standby.
         */
        try {
        sleep(REFRESH_STANDBY);
        } catch (InterruptedException ex) {
            CustomIOHandler.println("***THREAD HAS BEEN INTERRUPTED***",true);
        }       
    }       
    }
}

Aber danke für die Antwort 🙂

REFRESH_STANDBY & REFRESH_RUNNING sind statische Variablen, sodass ich nicht mehr unterschiedliche "Pause"-Zeiten (kann man das so nennen? Ist das überhaupt sauberer Programmierstil Threads in einer while-Schleife schlafen zu schicken?) für meine Threads haben kann.
 
Dachte ich mir es doch 🙂 Saß selber mal an einem solchen Problem Stundenlang 😉

Ich würde sagen Nein. Mache es aber selber so und kenne auch keinen anderen Weg. In sauberer Programierung bin ich leider selbst nicht besser als ein Anfänger 😉
 

Zurück
Oben