X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=4aaab1d05f5ee2b1e1ff6791321b45cbc1b7d5e6;hb=34a42e784bf1238c6bb2847c52d7c841fcfdef5f;hp=8071ae8edc2a5a0f5762dcdd2d2871db960dbe54;hpb=30bd4abd2a85c162bdf0a1cc687b366345182bc1;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 8071ae8..4aaab1d 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -64,6 +64,11 @@ import static jexer.TKeypress.*; */ public class TApplication { + /** + * If true, emit thread stuff to System.err. + */ + private static final boolean debugThreads = false; + /** * WidgetEventHandler is the main event consumer loop. There are at most * two such threads in existence: the primary for normal case and a @@ -134,6 +139,10 @@ public class TApplication { } event = application.drainEventQueue.remove(0); } + // Wait for drawAll() or doIdle() to be done, then handle + // the event. + boolean oldLock = lockHandleEvent(); + assert (oldLock == false); if (primary) { primaryHandleEvent(event); } else { @@ -142,8 +151,17 @@ public class TApplication { if ((!primary) && (application.secondaryEventReceiver == null) ) { - // Secondary thread, time to exit + // Secondary thread, time to exit. + + // DO NOT UNLOCK. Primary thread just came back from + // primaryHandleEvent() and will unlock in the else + // block below. return; + } else { + // Unlock. Either I am primary thread, or I am + // secondary thread and still running. + oldLock = unlockHandleEvent(); + assert (oldLock == true); } } } // while (true) (main runnable loop) @@ -165,6 +183,96 @@ public class TApplication { */ private TWidget secondaryEventReceiver; + /** + * Spinlock for the primary and secondary event handlers. + * WidgetEventHandler.run() is responsible for setting this value. + */ + private volatile boolean insideHandleEvent = false; + + /** + * Set the insideHandleEvent flag to true. lockoutEventHandlers() will + * spin indefinitely until unlockHandleEvent() is called. + * + * @return the old value of insideHandleEvent + */ + private boolean lockHandleEvent() { + if (debugThreads) { + System.err.printf(" >> lockHandleEvent(): oldValue %s", + insideHandleEvent); + } + boolean oldValue = true; + + synchronized (this) { + // Wait for TApplication.run() to finish using the global state + // before allowing further event processing. + while (lockoutHandleEvent == true); + + oldValue = insideHandleEvent; + insideHandleEvent = true; + } + + if (debugThreads) { + System.err.printf(" ***\n"); + } + return oldValue; + } + + /** + * Set the insideHandleEvent flag to false. lockoutEventHandlers() will + * spin indefinitely until unlockHandleEvent() is called. + * + * @return the old value of insideHandleEvent + */ + private boolean unlockHandleEvent() { + if (debugThreads) { + System.err.printf(" << unlockHandleEvent(): oldValue %s\n", + insideHandleEvent); + } + synchronized (this) { + boolean oldValue = insideHandleEvent; + insideHandleEvent = false; + return oldValue; + } + } + + /** + * Spinlock for the primary and secondary event handlers. When true, the + * event handlers will spinlock wait before calling handleEvent(). + */ + private volatile boolean lockoutHandleEvent = false; + + /** + * TApplication.run() needs to be able rely on the global data structures + * being intact when calling doIdle() and drawAll(). Tell the event + * handlers to wait for an unlock before handling their events. + */ + private void stopEventHandlers() { + if (debugThreads) { + System.err.printf(">> stopEventHandlers()"); + } + + lockoutHandleEvent = true; + // Wait for the last event to finish processing before returning + // control to TApplication.run(). + while (insideHandleEvent == true); + + if (debugThreads) { + System.err.printf(" XXX\n"); + } + } + + /** + * TApplication.run() needs to be able rely on the global data structures + * being intact when calling doIdle() and drawAll(). Tell the event + * handlers that it is now OK to handle their events. + */ + private void startEventHandlers() { + if (debugThreads) { + System.err.printf("<< startEventHandlers()\n"); + } + lockoutHandleEvent = false; + } + /** * Access to the physical screen, keyboard, and mouse. */ @@ -366,6 +474,10 @@ public class TApplication { * Draw everything. */ public final void drawAll() { + if (debugThreads) { + System.err.printf("drawAll() enter\n"); + } + if ((flush) && (!repaint)) { backend.flushScreen(); flush = false; @@ -376,6 +488,10 @@ public class TApplication { return; } + if (debugThreads) { + System.err.printf("drawAll() REDRAW\n"); + } + // If true, the cursor is not visible boolean cursor = false; @@ -497,11 +613,19 @@ public class TApplication { metaHandleEvent(event); } + // Prevent stepping on the primary or secondary event handler. + stopEventHandlers(); + // Process timers and call doIdle()'s doIdle(); // Update the screen - drawAll(); + synchronized (getScreen()) { + drawAll(); + } + + // Let the event handlers run again. + startEventHandlers(); } // Shutdown the consumer threads @@ -720,6 +844,13 @@ public class TApplication { */ public final void yield() { assert (secondaryEventReceiver != null); + // This is where we handoff the event handler lock from the primary + // to secondary thread. We unlock here, and in a future loop the + // secondary thread locks again. When it gives up, we have the + // single lock back. + boolean oldLock = unlockHandleEvent(); + assert (oldLock == true); + while (secondaryEventReceiver != null) { synchronized (this) { try { @@ -735,6 +866,10 @@ public class TApplication { * Do stuff when there is no user input. */ private void doIdle() { + if (debugThreads) { + System.err.printf("doIdle()\n"); + } + // Now run any timers that have timed out Date now = new Date(); List keepTimers = new LinkedList(); @@ -1124,14 +1259,11 @@ public class TApplication { return true; } - /* - TODO if (command.equals(cmShell)) { - openTerminal(0, 0, TWindow.Flag.RESIZABLE); + openTerminal(0, 0, TWindow.RESIZABLE); repaint = true; return true; } - */ if (command.equals(cmTile)) { tileWindows(); @@ -1172,14 +1304,11 @@ public class TApplication { return true; } - /* - TODO - if (menu.id == TMenu.MID_SHELL) { - openTerminal(0, 0, TWindow.Flag.RESIZABLE); + if (menu.getId() == TMenu.MID_SHELL) { + openTerminal(0, 0, TWindow.RESIZABLE); repaint = true; return true; } - */ if (menu.getId() == TMenu.MID_TILE) { tileWindows(); @@ -1287,7 +1416,7 @@ public class TApplication { * @param title menu title * @return the new menu */ - public final TMenu addMenu(String title) { + public final TMenu addMenu(final String title) { int x = 0; int y = 0; TMenu menu = new TMenu(this, x, y, title); @@ -1386,8 +1515,6 @@ public class TApplication { int newWidth = (getScreen().getWidth() / a); int newHeight1 = ((getScreen().getHeight() - 1) / b); int newHeight2 = ((getScreen().getHeight() - 1) / (b + c)); - // System.err.printf("Z %s a %s b %s c %s newWidth %s newHeight1 %s newHeight2 %s", - // z, a, b, c, newWidth, newHeight1, newHeight2); List sorted = new LinkedList(windows); Collections.sort(sorted); @@ -1527,4 +1654,29 @@ public class TApplication { return new TInputBox(this, title, caption, text); } + /** + * Convenience function to open a terminal window. + * + * @param x column relative to parent + * @param y row relative to parent + * @return the terminal new window + */ + public final TTerminalWindow openTerminal(final int x, final int y) { + return openTerminal(x, y, TWindow.RESIZABLE); + } + + /** + * Convenience function to open a terminal window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param flags mask of CENTERED, MODAL, or RESIZABLE + * @return the terminal new window + */ + public final TTerminalWindow openTerminal(final int x, final int y, + final int flags) { + + return new TTerminalWindow(this, x, y, flags); + } + }