Skip to content

Commit

Permalink
Expose FxTimer as public API.
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasMikula committed Jun 3, 2014
1 parent 0204954 commit e754843
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 41 deletions.
4 changes: 3 additions & 1 deletion reactfx/src/main/java/org/reactfx/EventStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import javafx.concurrent.Task;

import org.reactfx.util.Either;
import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer;

/**
* Stream of values (events).
Expand Down Expand Up @@ -335,7 +337,7 @@ default <U> AwaitingEventStream<U> reduceSuccessions(
}

Function<Runnable, Timer> timerFactory =
action -> new FxTimer(timeout, action);
action -> FxTimer.create(timeout, action);
return new SuccessionReducingStream<T, U>(
this, initialTransformation, reduction, timerFactory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,7 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;

interface Timer {
void restart();
void stop();
}

class FxTimer implements Timer {
private final Animation animation;
private final Runnable action;

private long seq = 0;

FxTimer(java.time.Duration timeout, Runnable action) {
Duration fxTimeout = Duration.millis(timeout.toMillis());
this.animation = new Timeline(new KeyFrame(fxTimeout));
this.action = action;
}

@Override
public void restart() {
stop();
long expected = seq;
animation.setOnFinished(ae -> {
if(seq == expected) {
action.run();
}
});
animation.play();
}

@Override
public void stop() {
animation.stop();
++seq;
}
}
import org.reactfx.util.Timer;

class ScheduledExecutorServiceTimer implements Timer {
private final long timeout;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.function.BiFunction;
import java.util.function.Function;

import org.reactfx.util.Timer;

import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableBooleanValue;

Expand Down
78 changes: 78 additions & 0 deletions reactfx/src/main/java/org/reactfx/util/FxTimer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.reactfx.util;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;

/**
* Provides factory methods for timers that are manipulated from and execute
* their action on the JavaFX application thread.
*/
public class FxTimer implements Timer {

/**
* Prepares a (stopped) timer with the given delay and action.
*/
public static Timer create(java.time.Duration delay, Runnable action) {
return new FxTimer(delay, action, 1);
}

/**
* Equivalent to {@code create(delay, action).restart()}.
*/
public static Timer runLater(java.time.Duration delay, Runnable action) {
Timer timer = create(delay, action);
timer.restart();
return timer;
}

/**
* Prepares a (stopped) timer that executes the given action periodically
* with the given interval.
*/
public static Timer createPeriodic(java.time.Duration interval, Runnable action) {
return new FxTimer(interval, action, Animation.INDEFINITE);
}

/**
* Equivalent to {@code createPeriodic(interval, action).restart()}.
*/
public static Timer runPeriodically(java.time.Duration interval, Runnable action) {
Timer timer = createPeriodic(interval, action);
timer.restart();
return timer;
}

private final Duration timeout;
private final Timeline timeline;
private final Runnable action;

private long seq = 0;

private FxTimer(java.time.Duration timeout, Runnable action, int cycles) {
this.timeout = Duration.millis(timeout.toMillis());
this.timeline = new Timeline();
this.action = action;

timeline.setCycleCount(cycles);
}

@Override
public void restart() {
stop();
long expected = seq;
timeline.getKeyFrames().setAll(new KeyFrame(timeout, ae -> {
if(seq == expected) {
action.run();
}
}));
timeline.play();
}

@Override
public void stop() {
timeline.stop();
++seq;
}
}
64 changes: 64 additions & 0 deletions reactfx/src/main/java/org/reactfx/util/Timer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.reactfx.util;


/**
* Timer represents a delayed action. This means that every timer has an
* associated action and an associated delay. Action and delay are specified
* on timer creation.
*
* <p>Every timer also has an associated thread (such as JavaFX application
* thread or a single-thread executor's thread). Timer may only be accessed
* from its associated thread. Timer's action is executed on its associated
* thread, too. This design allows to implement guarantees provided by
* {@link #stop()}.
*/
public interface Timer {
/**
* Schedules the associated action to be executed after the associated
* delay. If the action is already scheduled but hasn't been executed yet,
* the timeout is reset, so that the action won't be executed before the
* full delay from now.
*/
void restart();

/**
* If the associated action has been scheduled for execution but not yet
* executed, this method prevents it from being executed at all. This is
* also true in case the timer's timeout has already expired, but the
* associated action hasn't had a chance to be executed on the associated
* thread. Note that this is a stronger guarantee than the one given by
* {@link javafx.animation.Animation#stop()}:
*
* <pre>
* {@code
* Timeline timeline = new Timeline(new KeyFrame(
* Duration.millis(1000),
* ae -> System.out.println("FIRED ANYWAY")));
* timeline.play();
*
* // later on the JavaFX application thread,
* // but still before the action has been executed
* timeline.stop();
*
* // later, "FIRED ANYWAY" may still be printed
* }
* </pre>
*
* In contrast, using the {@link FxTimer}, the action is guaranteed not to
* be executed after {@code stop()}:
* <pre>
* {@code
* Timer timer = FxTimer.runLater(
* Duration.ofMillis(1000),
* () -> System.out.println("FIRED"));
*
* // later on the JavaFX application thread,
* // but still before the action has been executed
* timer.stop();
*
* // "FIRED" is guaranteed *not* to be printed
* }
* </pre>
*/
void stop();
}

0 comments on commit e754843

Please sign in to comment.