We are about to switch to a new forum software. Until then we have removed the registration on this forum.
Hi all,
I'm working on modifying the Attraction2D example from the Toxiclibs library to be controlled by gestures from a Leap Motion sensor, as opposed to the mouse in the example.
I'm doing all my gesture recognition in an Open Frameworks app, and sending that over OSC.
When a "Gesture 0" event occurs, I call the method below to remove the gestureAttractor
from the physics
object:
void resetAttraction() {
if (gestureAttractor != null){
physics.removeBehavior(gestureAttractor);
println("ATTRACTOR NULL");
} else {
println("not null");
}
}
If a "Gesture 1" event occurs, I call this method to create a new gestureAttractor
, and add it back to the physics
object:
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"); } }
What seems to happen consistently is whenever the gesture state changes, I'll get a "Concurrent Modification Exception" crash at physics.update();
in the draw
method.
I'm sure it has something to do with the way the lifecycle of these objects are handled, but I haven't been able to determine anything yet - anyone have any ideas?
Below is the entirety of the sketch:
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");
}
}
Answers
I figured out a workaround - by adding in code to print the error exception, the crash didn't occur anymore:
For reference, the result of printing the stacktrace is:
at java.util.TimerThread.mainLoop(Timer.java:555) at java.util.TimerThread.run(Timer.java:505)
@GoToLoop, any advice on handling concurrent modification in this scenario?
Past related discussions:
@jeremydouglass: @GoToLoop's answers in this thread to answer my question:
https://forum.processing.org/two/discussion/comment/90265/#Comment_90265
I added
protected volatile
to my potentially unthread safe variables - that seems to do the trick, but I'm of course open to any other thoughts!I think I was wrong - that really didn't do much when pushing the sketch further (i.e., adding more particles).
@GoToLoop, any thoughts at all?
Like I've just advised on this recent forum thread below:
https://forum.Processing.org/two/discussion/23063/how-to-run-a-segment-of-code-uninterrupted#Item_2
You should transfer VerletPhysics2D::particles container so it is handled by 1 Thread only.
Rather than calling functions resetAttraction() & addAttraction(), which mutates the VerletPhysics2D::particles btW, from within oscEvent(), which runs outside the "Animation" Thread, make gestureClassLabel global, and just set it within oscEvent().
Within draw(), check gestureClassLabel within a
switch () / case:
block, calling the corresponding function according to the gestureClassLabel's current value.Don't forget to always reset it back to some value which represents no action, like
-1
or something like that, right after theswitch () / case:
block. :PHey @GoToLoop, thank you for this - that helps a lot.
I tried your suggestion, and seem to have resolved the concurrency errors I was experiencing.
However, what seems to be happening now is that
So, here's what the sketch looks like before I added your changes in, but with the concurrency errors (every time a new gesture occurs, the particles move).
Here's what happens when I add your changes in - no concurrency errors, but it seems that the sketch is only processing the change from a single gesture...after one occurs, any additional gestures have no effect.
As you suggested, I made
gestureClassLabel
a global variable:int gestureClassLabel;
And inside my OSC message method, I set the value of that variable accordingly.
Inside my
draw
method, I've added the switch/case statement as so:You've forgotten about keyword
break
to isolate eachcase:
! #-ohttps://Processing.org/reference/switch.html
Obviously the last
case:
doesn't need it. But most folks like the redundantbreak
there too. :))P.S.: Curly
{}
brackets are also unnecessary forcase:
's statements. ;;)@GoToLoop Oh...well that was dumb of me :(
So, I made the change you suggested here, but the same behavior still occurs...wonder if I'm going to have to rethink how I'm approaching all of this...
-1
to gestureClassLabel. b-(-1
before it had a chance to be processed later within draw(), given each function is concurrently executed by their own threads! #-o@GoToLoop Also makes sense! Still the same :/ Wonder if adjusting the sketch's framerate will help...
So, it seems that swapping out
switch/case
for anif/else
block seems to be doing the trick...else if {}
would work whileswitch () { case: }
wouldn't! :-/That worked amazingly @GoToLoop...thank you so much for the help!!!