Mirrored observable collections

Mike Hearn mike at plan99.net
Tue Jul 22 16:09:40 UTC 2014


I have what I imagine is a fairly typical JavaFX application (once it's
released I'll post more about it). It has a GUI, some mostly asynchronous
state management, and interactions with various servers that can change the
apps state.

At first I tried the simple and obvious approach in which the backend would
schedule closures onto the UI thread using Platform.runLater() when things
changed, sometimes via ad-hoc event handlers or callbacks. But it ended up
getting more and more complicated and unclear. So I ended up rewriting
things to be more actor-ish in which the backend code ran mostly in its own
thread and vended "mirrored observables".

I figured mirrored observables are a generally useful concept that probably
JavaFX should have itself.

The idea is simple enough. Given an ObservableList or ObservableSet (I
didn't need map yet), calling a static utility function with that list and
an Executor returns a new list in which all updates run in the context of
that executor. This means a piece of code that's responding to changes in
state held elsewhere e.g. via a network connection which receives updates
from a server can have its own thread, and update its own
ObservableLists/Sets/Maps without thinking about threading as long as the
only public accessors for these collections vend mirrored versions. Note
that mirrors are read-only, I don't attempt to do two-way sync (with
conflict resolution?!). If you want to update the "real" list you have to
schedule a closure onto the backend thread to do it and wait for the change
to re-propagate to the frontend thread.

Once this is in place, you can then bind the collections to UI controls
using some extra transformers in the standard manner, and everything hangs
together nicely.

The code I'm using is here (Apache licensed, go wild)

   https://gist.github.com/mikehearn/4781ce7f00228762adfb

There are three files. AffinityExecutor is an extended version of the
Executor interface which has a notion of thread ownership (supports short
circuiting and assertions), along with static methods to create:

   1. AffinityExecutors that are bound to a dedicated new thread with a
   task queue.
   2. An AffinityExecutor that queues up tasks but doesn't execute them
   until explicitly requested, this is useful for unit testing.
   3. An AffinityExecutor that wraps Platform.runLater and
   Platform.isFxApplicationThread
   4. An executor that just runs closures immediately on the same thread.

Then ObservableMirrors creates sets/lists in the same way a content binding
would, but which re-applies changes in the given thread or short-circuits
and does so immediately if the listener on the mirror is running on the
same thread as the caller.

There's also a set of static addListener methods in MarshallingObservers
that just relays into the right thread as well, if you only care about
changes and not full content.

Of course you could have an ObservableMirrors equivalent that uses a
regular Java executor, you'd just lose some safety and short circuiting.


More information about the openjfx-dev mailing list