Concurrent Modification Exception trying to add objects on OSC event

edited February 2016 in Library Questions

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

  • edited July 2015

    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:

    import java.util.concurrent.CopyOnWriteArrayList;
    
  • edited July 2015

    I'm assuming has to do with the fact that the OSCEvent() is another Thread...

    • You're dead on it! >-)
    • Indeed, as @MenteCode tipped ya, CopyOnWriteArrayList() is the easiest approach.
    • Although it's the slowest too! Since every single access to it clones its internal array. YIKES!
    • I'm afraid that your volatile isBusy doesn't cut for protecting asteroids against ConcurrentModificationException at all! :-S
    • If you choose to stick w/ the regular ArrayList container, you're gonna need synchronized in all places that its size() is modified.
    • Some of those methods include add(), addAll(), remove(), clear(), etc.
    • The only place you've originally tried out synchronized @ line 24 wasn't so necessary for no change in size() happens there!
    • Instead, you're gonna need synchronized blocks for the add() & remove() parts:


    void oscEvent(OscMessage osc) {
      if (osc.addrPattern().equals("/peter/rhythm"))  synchronized (asteroids) {
        asteroids.add( new Asteroid(0) );
      }
    }
    

    synchronized (asteroids) {
      for (int a = asteroids.size(), len = a; a-- != 0;)  if (asteroids.get(a).dead()) {
        asteroids.set(a, asteroids.get(--len));
        asteroids.remove(len);
      }
    }
    

    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:

    import java.util.concurrent.CopyOnWriteArrayList;
    import oscP5.*;
    
    OscP5 oscP5;
    float inx, iny;
    ArrayList<Ball> balls = new ArrayList();
    
    void setup() {
      size(600, 600);
      smooth();
      oscP5 = new OscP5(this, 11111);
    }
    
    void draw() {
      background(230);
      for (Ball b: balls)  b.display();
    }
    
    void adder(float inxx,float inyy) {
      balls.add( new Ball(inxx,inyy) );
      println(inxx,inyy);
    }
    
    void oscEvent(OscMessage theOscMessage) {
      float inyy = 0;
      float inxx = 0;
    
      if (theOscMessage.checkAddrPattern("/x")==true) {
          inxx = theOscMessage.get(0).floatValue();
          inyy = theOscMessage.get(1).floatValue();
          adder(inxx, inyy);
          return;
      }
    }
    
    class Ball{
      float x, y, x1, x2, x3, y1, y2, y3;
      float diameter;
      boolean on = false;
    
      Ball(float inx, float iny) {   
      x=inx;
      y=iny;
      on = true;
      diameter = 1;
      }
    
      void display() {
        if (on == true) {
          noFill();
          strokeWeight(4);
          stroke(225);
          diameter += 0.5;
          quad(
            x+(-diameter),y+(-diameter),
            x+diameter*x,y+(-diameter)*y,
            x+(-diameter)*x,y+diameter*y,
            x+diameter*x,y+diameter*y
            );
        }
      }
    }
    

    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?

  • edited February 2016
    // forum.Processing.org/two/discussion/11634/
    // concurrent-modification-exception-trying-to-add-objects-on-osc-event
    
    // GoToLoop (2016-Feb-08)
    
    import oscP5.*;
    import java.util.List;
    
    final List<Ball> balls = new ArrayList<Ball>();
    
    void setup() {
      size(600, 600, FX2D);
      smooth(4);
    
      rectMode(CORNER);
      strokeWeight(1.5);
      stroke(0);
      fill(#FFFF00);
    
      balls.add(new Ball(random(width - Ball.DIAM), random(height - Ball.DIAM)));
    
      new OscP5(this, 11111);
    }
    
    void draw() {
      background(0350);
    
      synchronized (balls) {
        for (final Ball b : balls)  b.display();
      }
    }
    
    void oscEvent(final OscMessage osc) {
      if (osc.checkAddrPattern("/x")) {
        final float x = osc.get(0).floatValue();
        final float y = osc.get(1).floatValue();
        final Ball  b = new Ball(x, y);
    
        synchronized (balls) {
          balls.add(b);
        }
      }
    }
    
    class Ball {
      static final int DIAM = 30;
      float x, y;
    
      Ball(float xx, float yy) {
        x = xx;
        y = yy;
      }
    
      void display() {
        rect(x, y, DIAM, DIAM);
      }
    }
    
  • 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);
      }
    }
    
Sign In or Register to comment.