Fixed issue with "out of order" event dispatch execution for GTK.

Runnables are now added to a FIFO queue when executed during GTK
execution.
This commit is contained in:
nathan 2017-09-17 21:05:14 +02:00
parent 0d73eb6b1e
commit f303b6cca2
1 changed files with 130 additions and 62 deletions

View File

@ -39,8 +39,11 @@ class GtkEventDispatch {
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
private static final LinkedList<Object> gtkCallbacks = new LinkedList<Object>(); private static final LinkedList<Object> gtkCallbacks = new LinkedList<Object>();
// this contains 'runnable's that have tried to be executed while on the dispatch thread. They are now executed on a queue instead.
private static final LinkedList<Runnable> eventDispatchQueue = new LinkedList<Runnable>();
// This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread // This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread
private static ThreadLocal<Boolean> isDispatch = new ThreadLocal<Boolean>() { public static ThreadLocal<Boolean> isDispatch = new ThreadLocal<Boolean>() {
@Override @Override
protected protected
Boolean initialValue() { Boolean initialValue() {
@ -213,11 +216,76 @@ class GtkEventDispatch {
} }
} }
/**
* Runs all entries in the dispatch queue, until there are no more.
*/
private static void runDispatchQueue() {
while (true) {
Runnable remove;
synchronized (eventDispatchQueue) {
if (eventDispatchQueue.isEmpty()) {
return;
}
remove = eventDispatchQueue.remove(0);
}
// we are in the dispatch thread - so always run as such
dispatchAlways(true, remove);
}
}
private static
Runnable makeDispatchRunnable(final Runnable runnable) {
return new Runnable() {
@Override
public
void run() {
isDispatch.set(true);
try {
runnable.run();
} catch (Throwable t) {
LoggerFactory.getLogger(GtkEventDispatch.class).error("Error during GTK run loop: ", t);
}
runDispatchQueue();
isDispatch.set(false);
}
};
}
/** /**
* Best practices for GTK, is to call EVERYTHING for it on the GTK THREAD. This accomplishes that. * Best practices for GTK, is to call EVERYTHING for it on the GTK THREAD. This accomplishes that.
*/ */
public static public static
void dispatch(final Runnable runnable) { void dispatch(final Runnable runnable) {
dispatch(isDispatch.get(), runnable);
}
/**
* Best practices for GTK, is to call EVERYTHING for it on the GTK THREAD. This accomplishes that.
*/
private static
void dispatch(final boolean isDispatch, final Runnable runnable) {
// queue dispatch methods, to make sure that they occur in-order, and prevent items from adding children before they are ready.
if (isDispatch) {
synchronized (eventDispatchQueue) {
eventDispatchQueue.add(runnable);
}
return;
}
// any events that are dispatched DURING our dispatch method (either GTK/JavaFX/SWT/etc) will be queued in order.
final Runnable dispatchRunnable = makeDispatchRunnable(runnable);
dispatchAlways(false, dispatchRunnable);
}
/**
* Always run the dispatch event, even if we are in the dispatch thread. This is to make running the dispatch queue easier.
*/
private static
void dispatchAlways(final boolean isDispatch, final Runnable runnable) {
if (GtkLoader.alreadyRunningGTK) { if (GtkLoader.alreadyRunningGTK) {
if (JavaFX.isLoaded) { if (JavaFX.isLoaded) {
// JavaFX only // JavaFX only
@ -228,7 +296,6 @@ class GtkEventDispatch {
else { else {
JavaFX.dispatch(runnable); JavaFX.dispatch(runnable);
} }
return; return;
} }
@ -236,7 +303,6 @@ class GtkEventDispatch {
if (Swt.isEventThread()) { if (Swt.isEventThread()) {
// Run directly on the SWT event thread. If it's not on the dispatch thread, we can use raw GTK to put it there // Run directly on the SWT event thread. If it's not on the dispatch thread, we can use raw GTK to put it there
runnable.run(); runnable.run();
return; return;
} }
} }
@ -244,11 +310,12 @@ class GtkEventDispatch {
// not javafx // not javafx
// gtk/swt are **mostly** the same in how events are dispatched, so we can use "raw" gtk methods for SWT // gtk/swt are **mostly** the same in how events are dispatched, so we can use "raw" gtk methods for SWT
if (isDispatch.get()) { if (isDispatch) {
// Run directly on the dispatch thread // Run directly on the dispatch thread. This will be false unless we are running the dispatch queue.
runnable.run(); runnable.run();
return;
} }
else {
final FuncCallback callback = new FuncCallback() { final FuncCallback callback = new FuncCallback() {
@Override @Override
public public
@ -257,13 +324,7 @@ class GtkEventDispatch {
gtkCallbacks.removeFirst(); // now that we've 'handled' it, we can remove it from our callback list gtkCallbacks.removeFirst(); // now that we've 'handled' it, we can remove it from our callback list
} }
isDispatch.set(true);
try {
runnable.run(); runnable.run();
} finally {
isDispatch.set(false);
}
return Gtk2.FALSE; // don't want to call this again return Gtk2.FALSE; // don't want to call this again
} }
@ -276,18 +337,21 @@ class GtkEventDispatch {
// the correct way to do it. Add with a slightly higher value // the correct way to do it. Add with a slightly higher value
Gtk2.gdk_threads_add_idle_full(100, callback, null, null); Gtk2.gdk_threads_add_idle_full(100, callback, null, null);
} }
}
public static public static
void dispatchAndWait(final Runnable runnable) { void dispatchAndWait(final Runnable runnable) {
if (isDispatch.get()) { // if we are on the dispatch queue, do not block
// Run directly on the dispatch thread (should not "redispatch" this again) Boolean isDispatch = GtkEventDispatch.isDispatch.get();
runnable.run(); if (isDispatch) {
// don't block. The ORIGINAL call (before items were queued) will still be blocking. If the original call was a "normal"
// dispatch, then subsequent dispatchAndWait calls are irrelevant (as they happen in the GTK thread, and not the main thread).
dispatch(true, runnable);
return;
} }
else {
final CountDownLatch countDownLatch = new CountDownLatch(1);
dispatch(new Runnable() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
dispatch(false, new Runnable() {
@Override @Override
public public
void run() { void run() {
@ -319,7 +383,6 @@ class GtkEventDispatch {
LoggerFactory.getLogger(GtkEventDispatch.class).error("Error waiting for dispatch to complete.", new Exception("")); LoggerFactory.getLogger(GtkEventDispatch.class).error("Error waiting for dispatch to complete.", new Exception(""));
} }
} }
}
/** /**
* required to properly setup the dispatch flag when using native menus * required to properly setup the dispatch flag when using native menus
@ -339,9 +402,14 @@ class GtkEventDispatch {
// toggled // toggled
callback.actionPerformed(null); callback.actionPerformed(null);
} }
} finally { } catch (Throwable t) {
isDispatch.set(false); LoggerFactory.getLogger(GtkEventDispatch.class)
.error("Error during GTK click callback: ", t);
} }
runDispatchQueue();
isDispatch.set(false);
} }
public static synchronized public static synchronized
void shutdownGui() { void shutdownGui() {