Исключение параллельной модификации в эскизе обработки

Я работаю над модификацией примера Attraction2D из библиотеки Toxiclibs, чтобы он управлялся жестами с датчика Leap Motion, как в отличие от мыши в примере.

Я выполняю все свои действия по распознаванию жестов в приложении Open Frameworks и отправляю их через OSC.

Когда происходит событие Gesture 0, я вызываю метод ниже, чтобы удалить gestureAttractor из объекта physics:

void resetAttraction() {
  if (gestureAttractor != null){
      physics.removeBehavior(gestureAttractor);
      println("ATTRACTOR NULL");
     } else {
        println("not null");
     }
}

Если происходит событие Gesture 1, я вызываю этот метод для создания нового gestureAttractor и добавляю его обратно в объект physics:

void addAttraction(){ 
   if (gestureAttractor == null) { 
       println("ATTRACTOR NULL"); 
       position1.set(340, 191); 
       gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f); 
       physics.addBehavior(gestureAttractor); 
   } else { 
       println("not null"); 
   } 
}

Кажется, что постоянно происходит то, что всякий раз, когда изменяется состояние жеста, я получаю ConcurrentModificationException сбой в physics.update(); в методе draw.

Я уверен, что это как-то связано с тем, как обрабатывается жизненный цикл этих объектов, но я пока ничего не смог определить - у кого-нибудь есть идеи?

Ниже представлен весь эскиз:

import toxi.geom.*;
import toxi.physics2d.*;
import toxi.physics2d.behaviors.*;

import oscP5.*;
import netP5.*;

OscP5 oscP5;

int NUM_PARTICLES = 750;

VerletPhysics2D physics;
//AttractionBehavior2D mouseAttractor;
AttractionBehavior2D gestureAttractor;


//Vec2D mousePos;
Vec2D position1;


boolean isGestureAttractorAdded;

void setup() {
  size(680, 382,P3D);
  // setup physics with 10% drag
  physics = new VerletPhysics2D();
  physics.setDrag(0.05f);
  physics.setWorldBounds(new Rect(0, 0, width, height));
  // the NEW way to add gravity to the simulation, using behaviors
  physics.addBehavior(new GravityBehavior2D(new Vec2D(0, 0.15f)));

  // start oscP5, listening for incoming messages at port 12000 
  oscP5 = new OscP5(this, 6000);

  position1 = new Vec2D(340, 191);

  addAttraction();

  //gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f);
  //physics.addBehavior(gestureAttractor);
}

void addParticle() {
  VerletParticle2D p = new VerletParticle2D(Vec2D.randomVector().scale(5).addSelf(width / 2, 0));
  physics.addParticle(p);
  // add a negative attraction force field around the new particle
  physics.addBehavior(new AttractionBehavior2D(p, 20, -1.2f, 0.01f));
}

void draw() {
  background(255,0,0);
  noStroke();
  fill(255);
  if (physics.particles.size() < NUM_PARTICLES) {
    addParticle();
  }
  physics.update();
  for (VerletParticle2D p : physics.particles) {
    ellipse(p.x, p.y, 5, 5);
  }
}

void mousePressed() {
  //position1 = new Vec2D(mouseX, mouseY);
   //create a new positive attraction force field around the mouse position (radius=250px)
  //gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f);
  //physics.addBehavior(gestureAttractor);

  //println(physics.behaviors);
}

void mouseDragged() {
  // update mouse attraction focal point
  //position1.set(mouseX, mouseY);
}

void mouseReleased() {
  // remove the mouse attraction when button has been released
  //physics.removeBehavior(gestureAttractor);
}


///// OSC RECEIVING

void oscEvent(OscMessage theOscMessage) {
  /* check if theOscMessage has the address pattern we are looking for. */

  if (theOscMessage.checkAddrPattern("/gesture_classification") == true)  {
    /* check if the typetag is the right one. */
    if(theOscMessage.checkTypetag("i")) {
      /* parse theOscMessage and extract the values from the osc message arguments. */
      int gestureClassLabel = theOscMessage.get(0).intValue();  
      println(" Gesture is: ", gestureClassLabel);

      if (gestureClassLabel == 0){   
        resetAttraction();
      } else if (gestureClassLabel == 1) {       
        addAttraction();
      } else if (gestureClassLabel == 2) {
          //physics.removeBehavior(gestureAttractor);
      } 
    }  
  } 

}

//////METHODS FOR SETTING POSITION / REMOVAL OF ATTRACTORS...

void resetAttraction() {
  if (gestureAttractor != null){
      physics.removeBehavior(gestureAttractor);
      println("ATTRACTOR NULL");
     } else {
        println("not null");
     }
 }

void addAttraction(){
     if (gestureAttractor == null) {
         println("ATTRACTOR NULL");
         position1.set(340, 191);
         gestureAttractor = new AttractionBehavior2D(position1, 250, 0.9f);
         physics.addBehavior(gestureAttractor);
     } else {
       println("not null");
     }
}

person narner    schedule 05.06.2017    source источник
comment
Может быть, то, как вы зацикливаетесь здесь сразу после physics.update();, здесь: for (VerletParticle2D p : physics.particles) { ellipse(p.x, p.y, 5, 5); }. Можете ли вы попробовать использовать старый добрый цикл for? for (int i = 0 ; i < physics.particles.size() ; i++) {VerletParticle2D p = physics.particles.get(i); ellipse(p.x, p.y, 5, 5); }   -  person George Profenza    schedule 05.06.2017
comment
Вы можете вставить трассировку стека исключения?   -  person Abhijith Nagarajan    schedule 05.06.2017
comment
@GeorgeProfenza Спасибо за предложение! К сожалению, поведение остается прежним :/   -  person narner    schedule 05.06.2017
comment
На самом деле, попытка напечатать исключение, похоже, решила проблему @AbhijithNagarajan - результаты будут представлены в ответе ниже...   -  person narner    schedule 05.06.2017


Ответы (1)


tl;dr: Вы не должны изменять структуру данных в одном потоке, пока вы перебираете ее в другом потоке.

Функция draw() выполняется в одном потоке и обращается к структурам данных внутри физической библиотеки и изменяет их.

Функция oscEvent() выполняется в другом потоке, а также обращается к тем же структурам данных и изменяет их. Вот что вызывает вашу ошибку. Обратите внимание, что помещение его в блок try ничего не исправит. Он просто распечатывает дополнительную информацию, когда происходит ошибка.

Чтобы действительно решить проблему, вам нужно прочитать о синхронизации доступа к данным между потоками. Например, вы можете использовать синхронизированные блоки, чтобы предотвратить доступ разных потоков к одной и той же структуре данных. Вы найдете массу результатов, если погуглите свою ошибку.

person Kevin Workman    schedule 05.06.2017
comment
Спасибо за разъяснение - похоже, что материал в этой ветке на форуме Processing (где есть некоторые из ваших ответов!) - это то, что мне нужно: forum.processing.org/two/discussion/5886/многопоточность-безумие - person narner; 06.06.2017