I would like to preface this by claiming that I am very new to android studio and java (and even this level programming in general). I am working on a simple audio synthesis application, and I was testing how multiple threads would work so that I could synthesize music and hold down multiple keys at once rather than pressing a key, calculating the entire audio buffer, and playing it once.
Here's the setup: I have 25 keys (view/buttons), that I can detect when they are pressed down and lifted up in my main UI thread. My second thread then constantly runs in the background and constantly calculates the audio buffer, and passes it to the AudioTrack stream to play. If no keys are pressed, it simply passes it a zero buffer so that nothing gets played essentially.
Here is the problem: I have set up a volatile array to represent which keys are pressed, so that when a button gets pressed in my app on the tablet, it sets an appropriate flag in the boolean array. My second thread then should be able to use that array to further calculate the audio buffer. Currently, I have it setup for 25 keys, but have only linked 2 keys in my UI, so it only recognizes when 2 of them are pressed, and the other 25 are simply set to zero. The second thread is able to recognize when either one or both of the keys are pressed, but there is a lag. In my main UI thread, I have set it up where it changes the color of the key when it detects a touch event, and that responds immediately, but the sound playing starts after a while. However, I can tap the key quickly, and it will detect the two events such as touch down and release (indicated by the keys changing color), but its as if the second thread did not recognize that I pressed the key, as it does not play the sound for even a brief moment (milliseconds). It seems like there is a few hundred millisecond lag between the key press being registered between the two threads. However, I think that the volatile boolean array keyPress does change the variable quickly, because if it didn't, then even with the lag, the second thread would play the sound for a brief second. But rather, it seems like the main UI thread changes the appropriate keyPress array element to true when a key is pressed, and also sets it to false when I lift my finger, but I can somehow do this without the second thread recognizing this happened? How is this possible? Is the main thread not actually changing the volatile array properly quick enough? Like it recognizes that a key is pressed, changes the local cached copy of the variable, and then changes the volatile variable? What would cause this lag? Is it based on the nature of the loops I have formed in the second threadLoop() thread? I don't think the thread is too slow for computing a sound buffer, because it doesn't break up the sound between buffers, so I don't think that is the problem.
I should also mention that I am not sure if "volatile" is the best variable type to use here either, so if there's some other data type, or something else I should be using, please let me know. I looked a little bit into Handlers and Loopers, but I thought that wasn't required here because those queue the tasks while I want keyPresses to be detected by the thread responsible for playing audio instantly (and also because it was kinda hard for me to wrap my head around them so I didn't bother with it, but if you think I am doing it incorrectly with this method of using a thread, please let me know). I am also using a Lenovo tab M10 in case that is relevant information. If there is any more information you would like from me, please let me know, and if a similar post already exists (I couldn't find something that answered my exact question), please point me in the right direction. Thank you very much for your help!
public class MainActivity extends AppCompatActivity {
private boolean isActive = false;
private Thread audioThread;
volatile boolean[] keyPress = new boolean[25];
//volatile boolean keypress;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Arrays.fill(keyPress, false);
//keypress = false;
final View c4 = (View) findViewById(R.id.c4);
final View d4 = (View) findViewById(R.id.d4);
c4.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();//.getActionMasked(motionEvent);
if (action == MotionEvent.ACTION_DOWN) {
c4.setBackgroundColor(Color.parseColor("#ed1b24")); //"#81DAF5"
keyPress[0] = true;
//keypress = true;
//audioThread.start();
}
else if (action == MotionEvent.ACTION_UP) {
c4.setBackgroundColor(Color.parseColor("#FFFFFF"));
keyPress[0] = false;
//keypress = false;
//audioThread.stop();
}
return true;
}
});
d4.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();//.getActionMasked(motionEvent);
if (action == MotionEvent.ACTION_DOWN) {
d4.setBackgroundColor(Color.parseColor("#ed1b24")); //"#81DAF5"
keyPress[1] = true;
//audioThread.start();
}
else if (action == MotionEvent.ACTION_UP) {
d4.setBackgroundColor(Color.parseColor("#FFFFFF"));
keyPress[1] = false;
//audioThread.stop();
}
return true;
}
});
new Thread(new Runnable() {
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#Override
public void run() {
threadLoop();
}
}).start();
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void threadLoop(){
AudioTrack audioTrack;
int intBufferSize;
short[] shortAudioData;
int intSampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
intBufferSize = AudioTrack.getMinBufferSize(intSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
shortAudioData = new short[intBufferSize];
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
intSampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
intBufferSize,
AudioTrack.MODE_STREAM);
audioTrack.setPlaybackRate(intSampleRate);
int n = 0;
float x;
float TS = 1.0f/intSampleRate;
float freq = 440.0f;
float PI = 3.14159f;
float omega = 2.0f*PI*freq*TS;
int numKeysPressed;
int incrementer = shortAudioData.length;
audioTrack.play();
while(true){
for(int i = 0; i < incrementer; ++i){
if(n == Integer.MAX_VALUE | n < 0){
n = 0;
}
x = 0;
numKeysPressed = 0;
for(int a = 0; a < 25; ++a){
if(keyPress[a] == true) {
x += (float) (Math.sin((float) (omega * n*(a+1))));
numKeysPressed++;
}
}
x = (float)(x/numKeysPressed);
++n;
shortAudioData[i] = (short)(x * Short.MAX_VALUE);
}
audioTrack.write(shortAudioData, 0, shortAudioData.length);
}
//audioTrack.stop();
}
}
I think this could be the problem:
volatile boolean[] keyPress = new boolean[25];
What that declares is a volatile variable containing a reference to an array. The volatile semantics apply to to the variable that contains the reference. Not to the array that it refers to.
That means that when you assign to a cell of the array that keyPressed refers to, there is not going to be an write to the (volatile) variable itself. Therefore, there is no cache flush (or whatever) to guarantee that another thread will see the update to the array cell.
(A more technically accurate explanation entails the discussion of the happens before relationships, but the outcome is the same.)
One alternative would be to use an atomic array class. For example, you could use an AtomicIntegerArray where each int encodes a truth value:
// (This needs to be 'final' ... to guarantee that the keypress
// variable is visible without any additional synchronization.)
final AtomicIntegerArray keyPress = new AtomicIntegerArray(25);
// To test if a flag is set:
if (keyPress.get(i) != 0) {
// it is set.
}
// To set a flag (atomically)
keyPress.set(i, flag ? 0 : 1);
And you can do other things like atomically toggling or atomically incrementing (a counter) by using other api methods.
An AtomicReferenceArray<Boolean> would also work. I'll leave you to figure out the details.
Related
I want to remove an index from an array after some time, I want it to fall, then delete it.
private final int gravity = 4;
private long lifeTime=0;
private long delay = 20000L;
public void objGravity() {
while (array.get(array.size - 1).y > 0)
array.get(array.size - 1).y -= gravity;
lifeTime = System.currentTimeMillis();
lifeTime += Gdx.graphics.getDeltaTime();
if (lifeTime > delay) {
array.removeIndex(array.size - 1);
lifeTime = 0;
}
}
The object gets removed from array immediately as if there's no delay whatsoever, so it doesn't appear to be falling down on the screen.
Update
I removed time and so on. I added a counter to count how many objects need to be removed and then I do a loop that removes array.size-1 as long as counter>0.
Not sure if it's the best approach, seems like a hack but I don't want to do threads on old mobile phones. If you have a better idea please share
Don't fire threads inside the render method, it's not safe, can cause thread leaks, a lot of other problems and will be harder to maintain your code, to handle time use a variable adding delta time every time render is called, when this variable is superior a 1.0f means that one second has passed, your code would be something like this:
private float timeSeconds = 0f;
private float period = 1f;
public void render() {
//Execute handleEvent each 1 second
timeSeconds +=Gdx.graphics.getRawDeltaTime();
if(timeSeconds > period){
timeSeconds-=period;
handleEvent();
}
[...]
}
public void handleEvent() {
[...]
}
To keep organized i personally have an array on my main game class that holds all my timed events and process everything on the render cycle.
So, I have a player body + fixture etc, it is essentially a ball that bounces around.
I want to detect when it is 'pretty much' finished moving.
At the moment I do this:
public Boolean isStopped() {
return body.getLinearVelocity().x <= 0.3f && body.getLinearVelocity().y <= 0.3f;
}
This mostly works, the problem being when the player hits something, there's a split second where its velocity is 0, so this returns true. What I really wanted is to just return true when it is basically finished. Preferably within a range that I can set to whatever I like as I tweak the physics of my game world.
I can't use a check on whether it is sleeping or not as that comes too late, it doesn't sleep until after it has stopped having forces act upon it, I need just before.
I could just store how long it has been stopped/a count of stopped steps, but I was hoping there would be a nice pre existing method that I missed.
Any ideas?
You can keep track of recent movement and update it by mixing in a little of the current speed each time step:
float speedNow = body.getLinearVelocity().len();
recentSpeed = 0.1 * speedNow + 0.9 * recentSpeed;
if ( recentSpeed < someThreshold )
... do something ...
You would need to set recentSpeed to a suitably high value to begin with, otherwise it might be below the threshold in the first time step.
Seeing how you've determined that your false positives are caused by the body making contact with another, why not add a couple of lines in your ContactListener's beginContact method, storing the body's current speed in its user data? Then you can check that speed in your isStopped method. If there is a stored speed and the current speed isn't greater, this means the body is in the process of bouncing off whatever it hit: ignore. If there is a stored speed and the current speed is greater, the ball has bounced and is proceeding in some new direction: clear the stored speed. If there is no stored speed and the current speed is below your threshold, you've detected the sought situation.
In your ContactListener:
public void beginContact(Contact contact) {
Body a = contact.getFixtureA().getBody();
Body b = contact.getFixtureB().getBody();
if (a == mBall) {
a.setUserData(a.getLinearVelocity().len());
} else if (b == mBall) {
b.setUserData(b.getLinearVelocity().len());
}
}
And in your isStopped check:
public Boolean isStopped() {
float storedSpd = (Float) body.getUserData();
float currentSpd = body.getLinearVelocity().len();
if ((storedSpd > Float.MIN_VALUE) && (currentSpd > storedSpd)) {
body.setUserData(Float.MIN_VALUE);
return false;
} else {
return (currentSpd < THRESHOLD);
}
}
This is untested, but you get the idea. Also, remember to initially set the user data to Float.MIN_VALUE.
In the end I have simply passed the delta from each render call to the isStopped() method.
public Boolean isStopped(float delta) {
boolean isMoving = (
Math.abs(body.getLinearVelocity().x) >= 0.25f || Math.abs(body.getLinearVelocity().y) >= 0.25f);
if(isMoving) {
timeStopped = 0f;
return false;
} else {
timeStopped += delta;
return timeStopped >= 0.3f;
}
}
timeStopped is just a class property that starts off as zero. This does return true for the beginning of the game (before the user has made a move) but in my application that is absolutely fine. Besides which, it is true to say it has stopped in that circumstance.
I'd still love to see a way to do this without storing extra crap, since I'm guessing box2d must have this information somewhere in order to figure out if a body with zero velocity has no force acting upon or if it is just changing direction after an impact.
(please read UPDATE 3 at the end)I'm developing an app that continually works with the sensors of device, works with Accelerometer and Magnetic sensors to retrieve the orientation of device(the purpose is mentioned here). in other words, my app needs to know the orientation of device in Real-time(however this is never possible, so as fast as possible instead, but really as fast as possible !). as mentioned in professional Android 4 Application Development by Reto Meier:
The accelerometers can update hundreds of times a second...
I must not lose any data that sensors report and I also want to do time-consuming operations on these data(retrieve the orientation and then do calculations... ). I decided to solve my problem by using LinkedBlockingQueue:
public void startSensors() {
LinkedBlockingQueue<float[][]> array=new LinkedBlockingQueue();
sensorListenerForOrientation = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
aValues = (event.values.clone());
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mValues = (event.values.clone());
if (aValues != null && mValues != null) {
try {
array.put(new float[][] { aValues, mValues });
} catch (InterruptedException e) {
}
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
Sensor aSensor = sm.getSensorList(Sensor.TYPE_ACCELEROMETER).get(
sm.getSensorList(Sensor.TYPE_ACCELEROMETER).size() - 1);
Sensor mSensor = sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(
sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).size() - 1);
sm.registerListener(sensorListenerForOrientation, aSensor,
SensorManager.SENSOR_DELAY_FASTEST);
sm.registerListener(sensorListenerForOrientation, mSensor,
SensorManager.SENSOR_DELAY_FASTEST);
executor.execute(new Runnable() {
#Override
public void run() {
doCalculations();
}
});
}
and
public void doCalculations() {
for (;;) {
float[][] result = null;
try {
result = array.take();
} catch (InterruptedException e) {
}
float[] aValues, mValues;
aValues = result[0];
mValues = result[1];
int[] degrees=getOrientation(aValues,mValues);
Log.e("",String.valueOf(degrees[0]));
//other calculations...
}
}
now I pick up my device and rotate it about 90 degrees to right and then return it to the first position fast(for example in 1.5 seconds) but as I look at the orientations that are registered in device I see for example: 0,1,2,3,4,5.......,40,39,38,37,....,0
I just want to say that I can't see a large domain of degrees in my result .
based on what I have done and what I have researched I just can be sure that I am NOT losing any data, any new data reported by sensors are recorded.
any Idea, solution?!
Regards!
UPDATE 1: I did another experiment with my device and got shocking results! if I rotate my device over an axis 90 degrees fast (less than a second), I can see all degrees in my result: 0,1,2,3,....,89,90 (for example) but if I rotate it 90 degrees and then rotate it back to its first position, the result would be 0,1,2,...,36,37,36,...2,1,0(for example)...really confusing !
UPDATE 2: I updated doCalculations() method to be more clear what I have done
UPDATE 3: I think maybe we can solve the problem in another way! I have clear purposes for this code. please have a look at this. I
have mentioned what is going to happen, I need to detect an specific
movement gesture. so maybe the whole way that I have chosen(the
technique above) is not a good way for solving this problem. maybe
it's better to detect that gesture by using other sensors or using the
same sensors in other way. what do you think?
So it looks like you are trying to find high throughput low latency solution for a standard "Producer-Consumer" problem. Basically the idea is quite straightforward: decrease data handling overhead, process data in parallel. Suggestions are the following:
1. Use "low latency" libraries
javolution.org - is a real-time library aiming to make Java or Java-Like/C++ applications faster and more time predictable. It includes Android support.
mentaqueue - is a super-fast, garbage-less, lock-free, two-thread (producer-consumer) queue based on the Disruptor ideas. Android support is undefined (it looks like it should work).
disruptor - yet another lightning fast library
trove - provides high speed regular and primitive collections for Java.
Any of these solution will let you save a lot of CPU cycles.
2. Process data wisely
There is an overhead every time you submit a job. Batch processing can be really helpful.
Process data continuously. Note, executor.execute will consume quite a lot. Several long-living consumers might help.
3. Finally, use micro optimization techniques
For example, get rid of if-else-if in favor of switch.
Track performance all the time in order to identify good and bad solutions. Experiment.
Happy coding.
Just thinking: please try the following:
public void startSensors() {
final Stack<Runnable> mStack = new Stack<Runnable>();
sensorListenerForOrientation = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
aValues = (event.values.clone());
else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mValues = (event.values.clone());
if (aValues != null && mValues != null) {
mStack.push(new Calculater(new float[][] { aValues, mValues });
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
Sensor aSensor = sm.getSensorList(Sensor.TYPE_ACCELEROMETER).get(
sm.getSensorList(Sensor.TYPE_ACCELEROMETER).size() - 1);
Sensor mSensor = sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).get(
sm.getSensorList(Sensor.TYPE_MAGNETIC_FIELD).size() - 1);
sm.registerListener(sensorListenerForOrientation, aSensor,
SensorManager.SENSOR_DELAY_FASTEST);
sm.registerListener(sensorListenerForOrientation, mSensor,
SensorManager.SENSOR_DELAY_FASTEST);
new Thread() {
public void run() {
while(true)
{
try {
Runnable r = mStack.pop();
r.run();
} catch(Exception ex){}
}
}
}.start();
}
private class Calculater implements Runnable {
float[][] theValues;
public Calculater(float[][] values) {
theValues = values;
}
public void run() {
int[] degrees= getOrientation(theValues[0], theValues[1]);
Log.e("",String.valueOf(degrees[0]));
}
}
Your code looks reasonable. A big unknown is how good the sensors and sensor fusion are in your device. Quick angle change readings rely on integration of angular acceleration or else a physical gyroscope with magnetic data mixed in to make the result absolutely align with the earth. Magnetic data are subject to surroundings. If your device has low quality sensors or there are magnetic disturbances in your environment, it's entirely possible to see the kinds of error you are seeing. Big metal structures and magnetic equipment (like motors or even fluorescent light ballasts) can blank the field or introduce arbitrary errors. For normal uses, a device only needs an accelerometer to accurately determine which way is down so screen flips are accurate. This only needs to work when the device is not moving, where a gyro has no role. If you have a phone or tablet with sensors meant only to serve this purpose - therefore with no gyro or an inaccurate one - you are seeing a device limitation. The erratic values are other evidence that your device is low quality and/or that you are in a location where the earth's magnetic field is being distorted. Try the program on another (preferably expensive) device outside and in the open, and see what you get.
The usual thing to do within an event block is to do almost nothing, since this is really fast.
"Almost" being the important word. In your case, the event could just add the data of the event (from the event parameter) to some data structure (list, stack, circular buffer... your pick). That way you should lose less events (if any).
Which means that you can then (for instance periodically) read the stored events and decide if a gesture was made. That means that your intensive calculations are made less often. But you don't lose any events. I think this is acceptable because of your purpose, which is gesture recognition. I assume it doesn't have to be that fast (ie. you don't have to calculate it every time the sensor updates).
Note : this is one common way to handle IT in the Linux world.
just a thought. I have a similar problem when I needed to collect several large sample sizes an perform calculations. My situation was probably quite different from yours as I just needed acceleration. What I did was create an array list. calculated acceleration per every record reported :
#Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float acceleration = FloatMath.sqrt((x * x) + (y * y) + (z * z));
Then in the same onSensorChanged method, I wait until the size hits a certain limit, like 300, clone that sample to a new list,clear out original, perform calculations on new list and continue in that manner. I get results in secs. I am not sure how much down time is allowed for your application but when I run this I get what I am looking for in less that 5 secs. If you need more sample code let me know, but that is the gist. Sorry if I didn't understand your question properly but I think you were asking for a way to calculate data without losing much? Also I have this running on a separate handler when I register the listener, not to interfere with the main thread, not to effect user experience.
Change variable declaration:
List<float[][]> array = Collections.synchronizedList(new ArrayList<float[][]>());
Inside the runnable:
Iterator<float[][]> values = array.iterator();
while (values.hasNext()) {
float[][] result = values.next();
//calculating.
//after calculating remove the items.
values.remove();
}
This is what's wrong with your code. Fast as possible requires fast coding techniques. Save the sensor type instead of evaluating it twice.
#Override
public void onSensorChanged(SensorEvent event) {
int i = event.sensor.getType();
if (i == Sensor.TYPE_ACCELEROMETER)
aValues = (event.values.clone());
else if (i == Sensor.TYPE_MAGNETIC_FIELD)
mValues = (event.values.clone());
}
I am using AndEngine to develop my game, though I'm thinking this problem is unrelated to AndEngine.
I have two possible dialogs that fire if:
User touches down in an incorrect area or
Users lifts up from an incorrect area.
Unfortunately, if a user touches down in an incorrect area, when they lift up they are also satisfying error 2--lifting up from an incorrect area.
Here's my code in a nutshell:
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
float y = pSceneTouchEvent.getY();
int dialog_count = 0;
if (pSceneTouchEvent.isActionDown() && y < 1000) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
AlertDialog Code
..............
}
}
dialog_count ++;
Log.d("Dialog Count", "Count is " + dialog_count);
} else if (dialog_count < 1 && pSceneTouchEvent.isActionUp() && y > 105) {
Log.d("Dialog Count", "Count is still " + dialog_count);
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
Second AlertDialog Code
.................
}
}
}
return false;
}
Now, my first log for dialog_count shows a value of 1. However when I lift up the second log shows a value of 0. Somehow this value is either getting reset or my Else statement just can't see the updated value of dialog_count because I get the second dialog popping on top of my first.
Any ideas?
Your code initializes
int dialog_count = 0;
each time it runs.
To keep the value you saw the last time, make dialog_count an instance variable in the class.
Notice that you will be seeing two events, one for "down" and one for "up".
If you want to show only one then you need to use a flag to check if the user has touched down and then if you want not to show the touch up dialog just check from the flag. But the Touch down will always follow the Touch Up. You can use the Touch Move method if you want to show the dialog when the user has moved a bit.
If I understand correctly, what you are trying to do
dialogCount is function local (garbage collected, after you exit the method). So it will be 0 on each new run of the method. (Make it private class variable).
If you are referring to your dialogCount in multiple threads, dialogCount must be thread safe, so use concurrent primitives - AtomicInteger
all. A thread in a Java program I am working on animates a random walk in space (or at least, it will once this problem is resolved). It contains the following two methods:
public void run() {
while(gw.checkBoundingBox()) {
if(!gw.pause) step();
}
}
public void step() {
Point3d p1, p2;
//get the last point, step, get the new point
p1 = new Point3d(gw.position);
gw.randomStep();
p2 = new Point3d(gw.position);
//create the Alpha that will do the animation, and wrap it in an AlphaControl,
//which will set this object's pause flag until the Alpha finishes
Alpha alpha = new Alpha();
alpha.setLoopCount(1);
if(alpha.finished()) System.out.println("DEBUG: I'm already dead.");
AlphaControl ac = new AlphaControl(alpha,gw);
ac.start();
//create a piece of the path, attach an interpolator to do the animation
PathCyl cyl = new PathCyl(p1,p2);
StretchInterpolator si = new StretchInterpolator(alpha, cyl.anchor);
si.setSchedulingBounds(new BoundingSphere(new Point3d(0,0,0),50));
cyl.anchor.addChild(si);
//put it all together
BranchGroup b = new BranchGroup();
b.addChild(cyl.tg);
trans.addChild(b);
}
So if the pause flag is not set, it runs step(). The only part of step() that you need to look at is the few lines about the Alpha object. (Alpha is an API class that produces a time-dependent function used for animating). So step() creates an Alpha object, which it then feeds to another thread called AlphaControl. AlphaControl tells the program to stop calculating points until this step is done animating. It does this by setting the pause flag that is checked in the run() method.
So what's the problem? Note that I added a debug line that immediately checks if the Alpha is finished after it is created. It seems like this line of code should never execute. Is the Alpha finished? Of course not, we just created it. But this line executes every time that the function is called AFTER the first time. Somehow it is hanging onto the same Alpha instance and using it over and over. I assume that this is because of the reference to the Alpha that is still alive in the AlphaControl thread.
So how do I fix this? I have tried several things. I created a huge array of Alphas, initialized them all before the walk even started, and tried to tell it to use a different alpha from the array at every step, but this had the same result. I also tried using the AlphaControl to set alpha to null before it closes, but this didn't work either. Is it possible to destroy this object? By the time the step() code gets back to running again, the AlphaControl that was created the first time around should be done and waiting for garbage collection.
Also, just in case it would be helpful to see it, here is the AlphaControl class.
public class AlphaControl extends Thread {
public Alpha alpha;
public GraphicalWalker gw;
public AlphaControl(GraphicalWalker gw, Alpha alpha) {
this.gw = gw;
this.alpha = alpha;
}
public void run() {
boolean stop = false;
boolean finished;
while(!stop) {
finished = alpha.finished();
if( !finished && !gw.pause ) gw.pause = true;
if( finished && gw.pause ) gw.pause = false;
if(finished) stop = true;
}
}
}
Thanks in advance for any help.
Jeff
I think key to understanding why your code fails is understanding what the Alpha class of the Java3D library does.
From your question it seems to me you do not understand it correctly. It does not loop. If you check the source code (http://www.java2s.com/Open-Source/Java/6.0-JDK-Modules/java-3d/javax/media/j3d/Alpha.java.htm) you will see it does not contain a single loop structure. What it does do, is define a function that maps a time value to a value in the range [0,1].
If you look at the source of the finished() method :
/**
* Query to test if this alpha object is past its activity window,
* that is, if it has finished looping.
* #return true if no longer looping, false otherwise
*/
public boolean finished() {
long currentTime = paused ? pauseTime : J3dClock.currentTimeMillis();
return ((loopCount != -1) &&
((float)(currentTime - startTime) * .001f > stopTime));
}
you notice that its value depends on when it is called.
And since you basically defined your alpha to have 1 loop of 1 millisecond, starting from the time of creation it will not be finished for 1 millisecond after you create that alpha, and finished ever after.
I hope this helps.
I'm not sure if I completely follow the intent of the program, but it seems like you have a race condition. When you call ac.start() on your AlphaControl object, there is no guarantee that gw.pause will be set prior to the next round of your loop executing. In fact, there's a fair chance that the loop will execute many times prior to gw.pause getting set.
Just for kicks, I suggest calling ac.run() instead of ac.start() on a run-through, as a shortcut to take threading out of the picture. If the intended behavior occurs, then I would look hard at your reason for doing this in multiple threads instead of a single one.
I think the problem is one of two issues:
Alpha's state is kept as a static variable, meaning that it's the same for all instances of Alpha, and you're likely not resetting it in the constructor (rightfully so).
Some part of your state isn't marked volatile, which breaks the semantics the compiler relies on to know that a marked field will always require a fresh value lookup (otherwise the compiler is free to cache the value)