Nice! On Tue, Jul 22, 2014 at 6:09 PM, Mike Hearn <m...@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) > > 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.
You can also use ReactFX streams to transfer events (here list changes) to another thread: ObservableList<T> list = ...; EventStream<Change<? extends T>> changesOnFxThread = EventStreams.changesOf(list) .threadBridgeToFx(backendExecutor); See http://www.reactfx.org/javadoc/org/reactfx/EventStream.html#threadBridgeToFx-java.util.concurrent.Executor- > > Of course you could have an ObservableMirrors equivalent that uses a > regular Java executor, you'd just lose some safety and short circuiting.