Mirrored observable collections
tomas.mikula at gmail.com
Tue Jul 22 22:36:11 UTC 2014
On Tue, Jul 22, 2014 at 6:09 PM, Mike Hearn <mike at plan99.net> wrote:
> 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)
> 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
> 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.
You can also use ReactFX streams to transfer events (here list
changes) to another thread:
ObservableList<T> list = ...;
EventStream<Change<? extends T>> changesOnFxThread =
> 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