We are about to switch to a new forum software. Until then we have removed the registration on this forum.
I'm designing this generative visuals system that uses OSC data from a musician to produce these kind of abstract "asteroids" in real time. I'm running into this Concurrent Modification Exception which I'm assuming has to do with the fact that the OSC Event is another thread (or something? happening alongside my draw loop) and so maybe when iterating through my asteroid objects an asteroid is added, causing an error. I thought the synchronize trick would work, no such luck. Any help is appreciated!
Note: Just tried it with also protecting/removing the .remove() portion of the code, same error (although perhaps less frequent?)
// OSC STUFF
volatile boolean isBusy = false;
/* incoming osc message are forwarded to the oscEvent method. */
void oscEvent(OscMessage theOscMessage) {
    if (theOscMessage.addrPattern().equals("/peter/rhythm")) {
        if (!isBusy)
        asteroids.add(new Asteroid(0));
    }
}
// DRAW STUFF
void wormhole() {
    pg.background(bgColor, bgTrans);
    // there are stars from last scene, animate those suckers
    for (Star s : stars) {
        s.display();
    }
    isBusy = true;
    // update, display asteroids
    synchronized (asteroids) {
        for (Asteroid a : asteroids) {
            a.update();
            a.display();
        }
    }
    isBusy = false;
}
// DELETING ASTEROIDS STUFF FROM DRAW() maybe this needs synch too?
  // destroy anything that is offscreen
  for (int i = asteroids.size() - 1; i >= 0; i--) {
    Asteroid a = asteroids.get(i);
    if (a.dead()) {
      asteroids.remove(i);
    }
  }
// ASTEROID class
float asteroidTrans = 120;  // global transparency modifier for asteroids
class Asteroid {
  PVector pos;
  color cFill, cStroke;
  float r;  // rotation
  float rotateSpeed = .005;
  float sw = 2; //strokeWeight
  float ld = 5; // line distance
  float p;  // phase
  float pRate = .3;  // phase rate
  float pAdjust;  // use this number to add to s [or any other value)
  float speed = 1;
  float s = 1;  // starting size
  float growthRate = .05; // growth per frame
  int type = 0; // type of asteroid
  // properties
  // phase pulsates size of asteroid
  // corona gives it a circular outline
  // death color makes it change space bg color on impact w screen
  // behaviors
  boolean ROTATE, PHASE;
  // shape types
  boolean BOX, SPHERE, LINES, CORONA, TRI;
  // on death/ birth
  boolean DEATH_COLOR;
  // extras
  Asteroid(int type) {
    this.type = type;
    pos = new PVector(random(width), random(height), far);
    // randomize its initial rotation
    r = random(TWO_PI);
    switch (type) {
    case 0: // black cube
      cFill = color(0);
      cStroke = color(255);
      ROTATE = true;
      BOX = true;
      break;
    case 1: // colorful, pulsating circles, on death make bg that color
      sw = 0;
      // random colors
      cFill = color(random(255), random(255), random(255), random(20, 255));
      cStroke = cFill;
      ROTATE = true;
      PHASE = true;
      SPHERE = true;
      CORONA = true;
      DEATH_COLOR = true;
      break;
    case 2: // line creature..?
      LINES = true;
      PHASE = true;
      //CORONA = true;
      //pRate = .001;
      ld = 20;
      sw = 1;
      cStroke = color(255);
      break;
    case 3: // triangles
      TRI = true;
      ROTATE = true;
      PHASE = true;
      cFill = color(255);
      rotateSpeed = .01;
      break;
    }
  }
  void update() {
    // all asteroid types move towards you (and grow)
    pos.z += speed * hyperspaceModifier;
    s += growthRate * hyperspaceModifier;
    // rotating asteroids
    if (ROTATE) {
      r += rotateSpeed * hyperspaceModifier;
    }
    if (PHASE) {
      p += pRate; // inc phase
      pAdjust += sin(p) * .5;
    }
  }
  void display() {
    // TRIANGLE
    if (TRI) {
      pg.pushMatrix();
      pg.translate(pos.x, pos.y, pos.z);
      if (ROTATE) {
        pg.rotateZ(r);
      }
      pg.rotateX(3 * PI / 2);
      pg.stroke(255);
      if (PHASE) {
        float r = map(sin(p), -1, 1, 0, 255);
        float g = map(cos(p), -1, 1, 0, 255);
        float b = map(tan(p), -1, 1, 0, 255);
        cFill = color(r, g, b);
      }
      pg.fill(cFill);
      pg.beginShape(TRIANGLES);
      pg.vertex(-12, -12, -12);
      pg.vertex( 12, -12, -12);
      pg.vertex(   0,    0,  12);
      pg.vertex( 12, -12, -12);
      pg.vertex( 12,  12, -12);
      pg.vertex(   0,    0,  12);
      pg.vertex( 12, 12, -12);
      pg.vertex(-12, 12, -12);
      pg.vertex(   0,   0,  12);
      pg.vertex(-12,  12, -12);
      pg.vertex(-12, -12, -12);
      pg.vertex(   0,    0,  12);
      pg.endShape();
      //pg.triangle(0, -10, 8, 10, -8, 10);
      pg.popMatrix();
    }
    // LINE
    if (LINES) {
      pg.pushMatrix();
      pg.translate(pos.x, pos.y, pos.z);
      pg.stroke(cStroke);
      pg.strokeWeight(sw);
      int segments = 8;
      PVector[] linePV = new PVector[segments];
      linePV[0] = new PVector(0, 0, 0);
      for (int i = 1; i < segments; i++) {
        float x = cos( p + (segments / TWO_PI * i)) * ld * (i * .1);
        float y = sin( p + (segments / TWO_PI * i)) * ld * (i * .1);
        float z = linePV[i - 1].z - ld;
        linePV[i] = new PVector(x, y, z);
        pg.line(linePV[i - 1].x, linePV[i - 1].y, linePV[i - 1].z, x, y, z);
      }
      pg.popMatrix();
    }
    // CORONA
    if (CORONA) {
      pg.pushMatrix();
      pg.translate(pos.x, pos.y, pos.z);
      pg.stroke(cStroke, asteroidTrans);
      pg.noFill();
      pg.strokeWeight(sw + 4 + pAdjust);
      pg.ellipse(0, 0, (s + pAdjust) * 1.3, (s + pAdjust) * 1.3);
      pg.popMatrix();
    }
    // BOX
    if (BOX) {
      pg.pushMatrix();
      pg.translate(pos.x, pos.y, pos.z);
      if (ROTATE) rotate();
      pg.fill(cFill, asteroidTrans);
      pg.strokeWeight(sw);
      pg.stroke(cStroke);
      pg.box(s + pAdjust);
      pg.popMatrix();
    }
    // SPHERE
    if (SPHERE) {
      pg.pushMatrix();
      pg.translate(pos.x, pos.y, pos.z);
      pg.strokeWeight(sw);
      pg.stroke(cStroke);
      pg.fill(cFill, asteroidTrans);
      //pg.sphereDetail(7);
      //if (ROTATE) rotate();
      //pg.sphere(s + pAdjust);
      pg.ellipse(0, 0, s + pAdjust, s + pAdjust);
      pg.popMatrix();
    }
  }
  boolean dead() {
    // if object gets totally offscreen...
    if (pos.z > 1000) {
      // DEATH COLOR asteroids change bg color on impact
      if (DEATH_COLOR) {
        bgColor = cFill;
      }
      return true;  // destroy object
    } else {
      return false;
    }
  }
  // if this asteroid is a rotater
  void rotate() {
    pg.rotateY(r);
    pg.rotateZ(r * 2);
    pg.rotateX(r * 3);
  }
}
Answers
Ah, the classic multi-threading CM Exception.
There are definitely more effective ways of getting around this problem that I haven't thought of, but the easiest option would be to replace the type of the variable asteroids to a CopyOnWriteArrayList, since I'm assuming asteroids is a regular ArrayList.
You can use this thread-safe ArrayList by placing this at the top of your code:
volatileisBusy doesn't cut for protecting asteroids against ConcurrentModificationException at all! :-Ssynchronizedin all places that its size() is modified.synchronized@ line 24 wasn't so necessary for no change in size() happens there!synchronizedblocks for the add() & remove() parts:And a similar online example as well: :bz
http://studio.ProcessingTogether.com/sp/pad/export/ro.9K-kjhuONcJDZ/latest
Ok I'm having the same issue trying to bring in OSC data then call a class. I tried both import java.util.concurrent.CopyOnWriteArrayList; and synchronized to no avail. I'm not sure my implementation of synchronized was correct though.
Any pointers in the right directions would be super helpful!
Here is a basic example:
The overall idea being that i am bringing data in via OSC and then passing that data to the ball class to make an instance each time data is passed in.
Set a flag in oscevent(), add stuff at end of draw if flag is set?
DUDE!!! Rock and Roll :D:D:D that totally works. And thanks for the quick reply.
I now see the implementation of the synchronized in the draw loop and the osc receiver. And basically that will synchronize any writing to or removing from the array? Very cool! Thanks.
If you need to mutate a lot of different lists/state in response to OSC messages you can also put the OSC messages into a list and then synchronize on that list to take care of this issue across the whole sketch.
import oscP5.*; import netP5.*; OscP5 oscP5; ArrayList oscMessageQueue = new ArrayList(); void setup() { oscP5 = new OscP5(this, 12000); } void draw() { // take care of the OSC messages once per frame dispatchOSCMessages(); // Do the rest of your rendering here, it won't be bothered by OSC messages showing up. } // Handle all the enqueued OSC messages at once. void dispatchOSCMessages() { synchronized(oscMessageQueue) { for(OscMessage message : oscMessageQueue) { // Do things here that require modifying lists or other actions // which should never happen half-way through the draw function. } // clear the list of messages. oscMessageQueue.clear(); } } // Add the OSC messages to the queue to be handled later. void oscEvent(OscMessage message) { synchronized(oscMessageQueue) { oscMessageQueue.add(message); } }