While porting the Pepper Tickling app from the old Python base to the new Android/Java base, I faced a big problem trying to handle the same Say action amongst multiple asynchronous methods.
Futures are objects that wrap asynchronous methods. It allows for asynchronous processing of code despite writing in a sequential manner. [More on Futures here.]
In the official Pepper API documentation, explanations and examples are provided for the use case where one multiple Futures can be used within a single method in order to chain operations together, but within the use case of pepper-tickle
, it was necessary to make use of multiple shared Futures within multiple asynchronous methods, and because REASON THIS IS NOT POSSILE
, this was not possible unless we made use of Atomic Variables.
As previously stated, due to concurrency issues, it is not possible to share Futures within multiple methods via conventional means. In this case, Atomic Variables may be used, which will handle concurrency issues for us.
While Atomic Variables are not at all documented in regards to the Pepper API, fortunately, it is possible to cast an Atomic Reference to a Future making it possible to share Futures within multiple asynchronous methods.
Conventionally, this is how one would declare a Future for an action.
Future<Void> sayFuture;
But, this would return an error ERROR HERE
when trying to share said Future within multiple asynchronous methods. In order to make it usable, it is necessary to cast it into an Atomic Reference like so.
AtomicReference<Future<Void>> sayFuture;
In my use case, it was only necessary to have a single Say function to say various different phrases, and it was necessary to be able to cancel it as it is run.
First, I wrote this function with phrase as a parameter. This will run the Say action in a new thread.
public Future<Void> speakNow(QiContext qiContext, Phrase phrase) {
Future<Say> say = SayBuilder.with(qiContext).withPhrase(phrase).buildAsync();
Future<Void> sayFuture = null;
try {
sayFuture = say.get().async().run();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
return sayFuture;
}
Next, we will implement this function into the actual main codebase. As an example, I will use the head touch sensor.
headTouchSensor.addOnStateChangedListener(touchState -> {
Log.i("TAG", "Sensor " + (touchState.getTouched() ? "touched" : "released") + " at " + touchState.getTime());
if (touchState.getTouched()) {
String randomHand = whichHand();
gameRunning.set(true);
sayFuture.get().cancel(true);
sayFuture.set(speakNow(qiContext, phraseStartGame));
Log.i("TAG", "Game is now running, correct hand is " + randomHand);
}
});
Over here, we make use of another Atomic Variable AtomicReference<Boolean> gameRunning
which will determine whether the tickle game is still running or not. As you can see, we make use of sayFuture.get().cancel(true);
to first cancel the previously running Say action (and if there is none running, it will simply continue), and sayFuture.set(speakNow(qiContext, phraseStartGame));
in order to pass the Say action to the future with the phrase as a parameter in order to run it asynchronously.