InhiBeans

JordanMartinez edited this page May 12, 2016 · 7 revisions

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.

API

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.

Bindings factory methods

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);
});

Motivational Example

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);
}

Task

Provide an implementation with the following properties:

  1. Correctness: actually implement an AND gate.
  2. 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.
  3. 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.

Test case

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;
}

Solution

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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.