Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
134 views
in Technique[技术] by (71.8m points)

java - n-body simulation - IndexOutOfBoundsException occurring randomly

Let me be brief. I am creating a n-body simulation for a school-project. I've encountered a problem: I get the error IndexOutOfBoundsException only sometimes when I run the simulation. There does not seem to be any correlation between when I get the error. I am assuming I am destroying the bodies (via the collision detection method) too quickly for it to register, and it tries to access an index that no longer exists. I thought

if(bodies.get(i)!=null&&bodies.get(n)!= null) 

in the update method would fix it, but it did not. Could someone take a look at the code and tell me what might be causing the error? To recreate the error: Simply press 'o' in the simulation, or randomly spawn massive bodies with 'm' until it occurs. Please keep in mind that I am only in high school, and I have not got a lot of programming experience.

MAIN:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Main extends JPanel implements Runnable, KeyListener{
    public static int width = 1400;
    public static int height = (width/16) * 9;
    public String title = "Orbital Mechanics Simulator -";
    public Dimension dim = new Dimension(width, height);
    public boolean running;

    Color bgColor = new Color(0x000000);
    public static long timeScale = (long) Math.pow(10,7.5); //how many simulation-seconds one IRL-second represents.
    public static long distanceScale = ((Physics.astUnit*10L) / width); //how many meters does one pixel represent

    ArrayList<Body> bodies = new ArrayList<Body>();
    long initFpsTime;
    long secondFpsTime;
    long nowTime;
    long lastTime;
    long timeElapsed;
    float deltaTime;

    int fps = 200; 

    public Main(){
        this.setPreferredSize(dim);
        this.setBackground(bgColor);
        this.start();
    }

    public void start(){
        running = true;
        Thread program = new Thread(this, "update");
        program.start();
    }

    public void run(){      
        lastTime = System.nanoTime();
        while(running){
            nowTime = System.nanoTime();
            deltaTime = nowTime - lastTime;
            update(deltaTime);
            initFpsTime = System.currentTimeMillis();
            if((initFpsTime - secondFpsTime) > Math.pow(10,3) / fps){
                render();
                secondFpsTime = System.currentTimeMillis();
            }
        }
    }

    public void update(float deltaTime){
        for(int i=0; i<bodies.size();i++){

            resetForces();

            bodies.get(i).update((float)(deltaTime / Math.pow(10,9))*timeScale);
            lastTime = System.nanoTime();

            //sets the forces for all bodies
            for(int n=0; n<bodies.size();n++){
                if(bodies.get(i)!=bodies.get(n)){
                    if(bodies.get(i)!=null&&bodies.get(n)!= null)
                        bodies.get(i).setForce(Physics.getFx(bodies.get(i), bodies.get(n)), Physics.getFy(bodies.get(i), bodies.get(n)));

                        //collision detection
                        if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
                            collision(bodies.get(i),bodies.get(n)); 
                        }
                }
            }
        }

    }

    //DIFFERENT SYSTEMS
    public void solarSystem(){
        Body sun;
        Body earth;
        Body mars;

        sun = new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0);
        earth = new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0);
        mars = new Body("Mars", Physics.massEarth, 10, (long)(1.5*Physics.astUnit/distanceScale) ,0, new Color(0x00ff00), (float)0, (float)0);

        earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
        mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));

        bodies.add(sun);
        bodies.add(earth);
        bodies.add(mars);
    }

    public void twoBodies(){


        bodies.add(new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0));
        bodies.add(new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0));
        bodies.add(new Body("Mars", Physics.massMars, 10, (long)(1.5*Physics.astUnit/(distanceScale)),0 ,new Color(0x00ff00), (float)0, (float)0));

        //earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
        //mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));

    }

    public void createRandomBody(){

        bodies.add(new Body("randomBody",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));

    }

    public void createMassiveBody(){
        bodies.add(new Body("Sun",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));
    }

    public void createSmallBody(){
        bodies.add(new Body("Earth",Physics.massEarth,10, 0, 0, Physics.randomColor(), (float)0, (float)0));

    }

    public void createSystem(){

        for(int i=0; i<20;i++){
            for(int n=0; n<20;n++){
                bodies.add(new Body("Random", Physics.massSun, 4, -width/2 + n*20 , height/2 - i*20 , Color.WHITE, (float)0, (float)0 ));
            }
        }
    }

    public void resetForces(){
        if(bodies.get(0) != null);
            for(int i=0;i<bodies.size();i++){
                if(i<=0 && i < bodies.size()){
                    bodies.get(i).resetForce();
                }
        }
    }

    public void collision(Body a, Body b){

        Body newBody = new Body("newBody", a.mass+b.mass, 20, (a.xPos+b.xPos)/2, (a.yPos+b.yPos)/2, Physics.randomColor(), (a.mass*a.vx + b.mass*b.vx)/(a.mass+b.mass), (a.mass*a.vy+b.mass*b.vy)/(a.mass+b.mass));
        bodies.remove(a);
        bodies.remove(b);
        bodies.add(newBody);

    }

    public void render(){
        repaint();
    }

    public void keyPressed(KeyEvent event) {
        switch(event.getKeyChar()){
            case 'r': createRandomBody();
                break;
            case 'f': twoBodies();
                break;
            case 'm':
                createMassiveBody();
                break;
            case 'z':
                createSmallBody();
                break;
            case 'o': 
                createSystem();
                break;
            case 's': 
                solarSystem();
                break;
        }

    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D)g;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //quick vs quality - preferring quality 

        for(int i=0;i<bodies.size();i++){
            bodies.get(i).displayPlanet(g2d);

        }
    }

    public static void gui(){
        Main main = new Main();
        JFrame frame = new JFrame();
        frame.setResizable(false);
        frame.setTitle(main.title);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(main);
        frame.addKeyListener(main);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]){
        SwingUtilities.invokeLater(new Runnable(){ //Event dispatching thread
            public void run(){
                gui();
            }
        });
    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }
}

BODY

import java.awt.Color;
import java.awt.Graphics;

public class Body {

    public static int xOrigo = Main.width / 2;
    public static int yOrigo = ((Main.width * 9) / 16) / 2;
    public static int numOfBodies; // how many bodies the program contains.
    public static float distanceScale = Main.distanceScale;

    public String name;
    public float mass, fx, fy, accX, accY, vx, vy, initVx, initVy, deltaX, deltaY;
    public long xDisplay, yDisplay;
    public long xPos, yPos;
    public int radius; 
    public Color color;


    public Body(String name, float mass, int radius, long xPos, long yPos, Color color, float initVx, float initVy){
        this.name = name;
        this.mass = mass;
        this.radius = radius;
        this.color = color;

        numOfBodies++;

        setX(xPos);
        setY(yPos);
        setVelocity(initVx, initVy);

    }

    public void update(float deltaTime){

        this.deltaX += this.vx * deltaTime;

        if(this.deltaX >= distanceScale){
            this.incX();
            deltaX = 0;
        }else if(this.deltaX <= -distanceScale){
            this.decX();
            deltaX = 0;
        }

        this.deltaY += this.vy * deltaTime;

        if(this.deltaY >= distanceScale){
            this.incY();
            deltaY = 0;
        }else if(this.deltaY <= -distanceScale){
            this.decY();
            deltaY = 0;
        }

        this.vx += (this.accX * deltaTime);
        this.vy += (this.accY * deltaTime);

    }

    public void setX(long xPos){
        this.xPos = xPos;
        this.xDisplay = xOrigo + xPos - radius;
    }

    public void setY(long yPos){
        this.yPos = yPos;
        this.yDisplay = yOrigo - yPos - radius;
    }

    public void incX(){
        this.xPos++;
        this.xDisplay = xOrigo + xPos - radius;
    }

    public void decX(){
        this.xPos--;
        this.xDisplay = xOrigo + xPos - radius;
    }


    public void incY(){
        this.yPos++;
        this.yDisplay = yOrigo - yPos - radius;
    }

    public void decY(){
        this.yPos--;
        this.yDisplay = yOrigo - yPos - radius;
    }

    public void setForce(float fx, float fy){
        this.fx += fx;
        this.accX = fx / this.mass;

        this.fy += fy;
        this.accY = fy / this.mass;
    }

    public void setVelocity(float vx, float vy){
        this.vx += vx;
        this.vy += vy;
    }


    public void displayPlanet(Graphics g){
        g.setColor(this.color);
        g.fillOval((int )this.xDisplay, (int)this.yDisplay, this.radius*2, this.radius*2); //temporary fix
    }

    public long getXPos(){
        return this.xPos;
    }

    public long getYPos(){
        return this.yPos;
    }

    public void resetForce(){
        this.fx = 0;
        this.fy = 0;
    }

}

PHYSICS:

import java.awt.Color;
import java.util.Random;


public class Physics {
    public static Random rand = new Random();

    public static final double G = 6.67384*(Math.pow(10, -11));
    public static long astUnit = 149597871000L; //L IS TO INDICATE IT'S A LONG VALUE, otherwise neg value
    public static float massEarth = (float)(5.97219*Math.pow(10,24));
    public static float massSun = (float)(1.9891*Math.pow(10,30));
    public stati

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

(Given the few information and the wall of code, I may be going too far out on a limb, but in doubt, I can delete the answer)

You are accessing the same list with different threads. The collision handling method

public void collision(Body a, Body b) {
    ...
    bodies.remove(a);
    bodies.remove(b);
    bodies.add(newBody);
}

is executed by the main physics thread that is started in the Main#start() method. This thread is modifying the list. And this may happen while the Swing Event Dispatch Thread (that is also reponsible for painting) is iterating over the bodies in the paintComponent method:

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    ...
    for(int i=0;i<bodies.size();i++){
        bodies.get(i).displayPlanet(g2d);

    }
}

Adding additional checks will not solve this issue. You'll need some form of synchronization. The brute-force-hammer would be to just synchronize on the bodies list:

public void collision(Body a, Body b) {
    ...
    synchronized (bodies) 
    {
        bodies.remove(a);
        bodies.remove(b);
        bodies.add(newBody);
    }
}

and

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    ...

    synchronized (bodies)
    {
        for(int i=0;i<bodies.size();i++){
            bodies.get(i).displayPlanet(g2d);
        }
    }
}

(basically, something like this would have to be done in all places where one thread might read the list while another thread is writing to the list).

But again: This is rather pragmatic. You could consider one of the thread-safe data structures from the java.util.concurrent package, or some manual, more fine-grained locking solution, probably with some ReadWriteLock.

It's far too much code to give a more focussed answer here (but at least, I wanted to point out what is (almost certainly) the reason for your problem)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...