-
Notifications
You must be signed in to change notification settings - Fork 47
InhiBeans
Note: This page is obsolete. Please see the Home Page
InhiBeans are extensions of bindings and properties from javafx.beans.*
that help prevent redundant invalidations and recalculations.
When there is a network of bound values, it often happens that a single user action on one end of the network results in a succession of changes of a value on the other end of the network. Most of the time, redundant invalidation and change events do not cause problems, but they can come with a performance penalty if the attached listeners eagerly perform expensive computations. InhiBeans help inhibit this invalidation madness.
InhiBeans extend classes from javafx.beans.*
and add these methods:
interface Observable extends javafx.beans.Observable {
Guard block();
void blockWhile(Runnable);
<T> T blockWhile(Supplier<T> f);
}
The block()
method prevents notification of invalidation and change listeners. It returns an instance of Guard
which is a handle to release the acquired block. After the returned guard is closed, notifications resume.
Any number of notifications suppressed while blocked results in a single notification when the block is released.
Guard
is an AutoCloseable
, which makes it convenient to use in try-with-resources. In the following code snippet, property p
's invalidation and change notifications are guaranteed to resume even in case of an exception.
try(Guard g = p.block()) {
// stuff that causes multiple invalidations of p
// and possibly throws an exception
}
blockWhile(runnable);
is equivalent to
try(Guard g = block()) {
runnable.run();
}
and T t = blockWhile(supplier);
is equivalent to
T t;
try(Guard g = block()) {
t = supplier.get();
}
The implementation is quite straightforward, have a look at, e.g. SimpleBooleanProperty.java.
Inhibitory versions of bindings provide factory methods that wrap an eager ObservableValue
and return an inhibitory binding. For example:
IntegerProperty a;
IntegerProperty b;
NumberBinding sum = a.add(b);
IntegerBinding relaxedSum = inhibeans.binding.IntegerBinding.wrap(sum);
Now, in the following code, change listeners on sum
fire twice, while change listeners on relaxedSum
fire only once.
relaxedSum.blockWhile(() -> {
a.set(1);
b.set(2);
});
Let's implement a logical AND gate. An AND gate has two inputs, a
and b
, and one output where the result of a & b
is pushed. We want all three values to be observable (in the sense of javafx.beans.value.ObservableValue
). Finally, we need a method to set the inputs.
interface AndGate {
ObservableBooleanValue a();
ObservableBooleanValue b();
ObservableBooleanValue output();
void setInputs(boolean a, boolean b);
}
Provide an implementation with the following properties:
- Correctness: actually implement an AND gate.
-
Consistency: the state of observable values has to appear consistent (i.e.
output = a & b
) at all times to any observer within the same thread. -
Efficiency: for a single call to
setInputs(a, b)
, the observer receives at most one invalidation notification.
Now take a few minutes to think about how one would implement that.
You can use this method to test your implementation (don't forget to enable assertions):
void test(AndGate gate) {
class Counter {
int count = 0;
public void inc() { count += 1; }
public int get() { return count; }
}
Predicate<AndGate> consistent = g ->
g.output().get() == (g.a().get() && g.b().get());
gate.setInputs(false, false);
assert gate.output().get() == false;
Counter na = new Counter();
Counter nb = new Counter();
Counter no = new Counter();
gate.a().addListener(observable -> {
assert consistent.test(gate);
na.inc();
});
gate.b().addListener(observable -> {
assert consistent.test(gate);
nb.inc();
});
gate.output().addListener(observable -> {
assert consistent.test(gate);
no.inc();
});
gate.setInputs(true, true);
assert gate.output().get() == true;
assert na.get() == 1;
assert nb.get() == 1;
assert no.get() == 1;
}
This is the solution using InhiBeans. It is just two more lines (the ones with comments) compared to the naive (inefficient, in the above sense) implementation.
import org.reactfx.inhibeans.binding.BooleanBinding; // Note BooleanBinding imported from inhibeans.
class AndGateImpl {
private final BooleanProperty a = new SimpleBooleanProperty();
private final BooleanProperty b = new SimpleBooleanProperty();
private final BooleanBinding output = BooleanBinding.wrap(a.and(b));
@Override
public void setInputs(boolean a, boolean b) {
Guard guard = output.block(); // suppress notifications temporarily
this.a.set(a);
this.b.set(b);
guard.close(); // continue delivering notifications
}
@Override public ObservableBooleanValue a() { return a; }
@Override public ObservableBooleanValue b() { return b; }
@Override public ObservableBooleanValue output() { return output; }
}
There is also a runnable version.
Note that consistency (in the above sense) is satisfied thanks to the property that invalidation listeners are executed in the order they were registered (and before any change listeners). This guarantees that, when any of the inputs changes, output
is the first one to be notified, before any other invalidation listener registered on a
or b
.