X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=5813d6f3ca15402ebbee2fbc0f592744c4c5e8e3;hb=bb35d91958450cc7152d2063f1d6cd34c15e2a3d;hp=9d845be18affeecbd94a91ad2939a9ad8da4fa2c;hpb=a83fea2bae838f4b9bbf59ce3832e0e67be41378;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 9d845be..5813d6f 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -120,12 +120,33 @@ public class TApplication { break; } } - synchronized (application) { - application.wait(); + + synchronized (this) { + /* + System.err.printf("%s %s sleep\n", this, primary ? + "primary" : "secondary"); + */ + + this.wait(); + + /* + System.err.printf("%s %s AWAKE\n", this, primary ? + "primary" : "secondary"); + */ + if ((!primary) && (application.secondaryEventReceiver == null) ) { - // Secondary thread, time to exit + // Secondary thread, emergency exit. If we + // got here then something went wrong with + // the handoff between yield() and + // closeWindow(). + + System.err.printf("secondary exiting at wrong time, why?\n"); + synchronized (application.primaryEventHandler) { + application.primaryEventHandler.notify(); + } + application.secondaryEventHandler = null; return; } break; @@ -153,6 +174,7 @@ public class TApplication { } else { secondaryHandleEvent(event); } + application.repaint = true; if ((!primary) && (application.secondaryEventReceiver == null) ) { @@ -160,7 +182,15 @@ public class TApplication { // DO NOT UNLOCK. Primary thread just came back from // primaryHandleEvent() and will unlock in the else - // block below. + // block below. Just wake it up. + synchronized (application.primaryEventHandler) { + application.primaryEventHandler.notify(); + } + // Now eliminate my reference so that + // wakeEventHandler() resumes working on the primary. + application.secondaryEventHandler = null; + + // All done! return; } else { // Unlock. Either I am primary thread, or I am @@ -168,7 +198,14 @@ public class TApplication { oldLock = unlockHandleEvent(); assert (oldLock == true); } + } // for (;;) + + // I have done some work of some kind. Tell the main run() + // loop to wake up now. + synchronized (application) { + application.notify(); } + } // while (true) (main runnable loop) } } @@ -176,17 +213,17 @@ public class TApplication { /** * The primary event handler thread. */ - private WidgetEventHandler primaryEventHandler; + private volatile WidgetEventHandler primaryEventHandler; /** * The secondary event handler thread. */ - private WidgetEventHandler secondaryEventHandler; + private volatile WidgetEventHandler secondaryEventHandler; /** * The widget receiving events from the secondary event handler thread. */ - private TWidget secondaryEventReceiver; + private volatile TWidget secondaryEventReceiver; /** * Spinlock for the primary and secondary event handlers. @@ -194,6 +231,22 @@ public class TApplication { */ private volatile boolean insideHandleEvent = false; + /** + * Wake the sleeping active event handler. + */ + private void wakeEventHandler() { + if (secondaryEventHandler != null) { + synchronized (secondaryEventHandler) { + secondaryEventHandler.notify(); + } + } else { + assert (primaryEventHandler != null); + synchronized (primaryEventHandler) { + primaryEventHandler.notify(); + } + } + } + /** * Set the insideHandleEvent flag to true. lockoutEventHandlers() will * spin indefinitely until unlockHandleEvent() is called. @@ -360,22 +413,16 @@ public class TApplication { /** * When true, exit the application. */ - private boolean quit = false; + private volatile boolean quit = false; /** * When true, repaint the entire screen. */ - private boolean repaint = true; - - /** - * Request full repaint on next screen refresh. - */ - public final void setRepaint() { - repaint = true; - } + private volatile boolean repaint = true; /** - * When true, just flush updates from the screen. + * When true, just flush updates from the screen. This is only used to + * minimize physical writes for the mouse cursor. */ private boolean flush = false; @@ -441,9 +488,9 @@ public class TApplication { if (useAWT) { - backend = new AWTBackend(); + backend = new AWTBackend(this); } else { - backend = new ECMA48Backend(input, output); + backend = new ECMA48Backend(this, input, output); } theme = new ColorTheme(); desktopBottom = getScreen().getHeight() - 1; @@ -489,10 +536,6 @@ public class TApplication { return; } - if (!repaint) { - return; - } - if (debugThreads) { System.err.printf("drawAll() REDRAW\n"); } @@ -587,24 +630,48 @@ public class TApplication { while (!quit) { // Timeout is in milliseconds, so default timeout after 1 second // of inactivity. - int timeout = getSleepTime(1000); - - // See if there are any definitely events waiting to be processed - // or a screen redraw to do. If so, do not wait if there is no - // I/O coming in. - synchronized (drainEventQueue) { - if (drainEventQueue.size() > 0) { - timeout = 0; + long timeout = 0; + + // If I've got no updates to render, wait for something from the + // backend or a timer. + if (!repaint && !flush) { + // Never sleep longer than 100 millis, to get windows with + // background tasks an opportunity to update the display. + timeout = getSleepTime(100); + + // See if there are any definitely events waiting to be + // processed. If so, do not wait -- either there is I/O + // coming in or the primary/secondary threads are still + // working. + synchronized (drainEventQueue) { + if (drainEventQueue.size() > 0) { + timeout = 0; + } + } + synchronized (fillEventQueue) { + if (fillEventQueue.size() > 0) { + timeout = 0; + } } } - synchronized (fillEventQueue) { - if (fillEventQueue.size() > 0) { - timeout = 0; + + if (timeout > 0) { + // As of now, I've got nothing to do: no I/O, nothing from + // the consumer threads, no timers that need to run ASAP. So + // wait until either the backend or the consumer threads have + // something to do. + try { + synchronized (this) { + this.wait(timeout); + } + } catch (InterruptedException e) { + // I'm awake and don't care why, let's see what's going + // on out there. } } // Pull any pending I/O events - backend.getEvents(fillEventQueue, timeout); + backend.getEvents(fillEventQueue); // Dispatch each event to the appropriate handler, one at a time. for (;;) { @@ -618,6 +685,13 @@ public class TApplication { metaHandleEvent(event); } + // Wake a consumer thread if we have any pending events. + synchronized (drainEventQueue) { + if (drainEventQueue.size() > 0) { + wakeEventHandler(); + } + } + // Prevent stepping on the primary or secondary event handler. stopEventHandlers(); @@ -631,14 +705,28 @@ public class TApplication { // Let the event handlers run again. startEventHandlers(); - } - // Shutdown the consumer threads - synchronized (this) { - this.notifyAll(); + } // while (!quit) + + // Shutdown the event consumer threads + if (secondaryEventHandler != null) { + synchronized (secondaryEventHandler) { + secondaryEventHandler.notify(); + } + } + if (primaryEventHandler != null) { + synchronized (primaryEventHandler) { + primaryEventHandler.notify(); + } } + // Shutdown the user I/O thread(s) backend.shutdown(); + + // Close all the windows. This gives them an opportunity to release + // resources. + closeAllWindows(); + } /** @@ -697,13 +785,6 @@ public class TApplication { synchronized (drainEventQueue) { drainEventQueue.add(event); } - - // Wake all threads: primary thread will either be consuming events - // again or waiting in yield(), and secondary thread will either not - // exist or consuming events. - synchronized (this) { - this.notifyAll(); - } } /** @@ -861,15 +942,19 @@ public class TApplication { boolean oldLock = unlockHandleEvent(); assert (oldLock == true); + // System.err.printf("YIELD\n"); + while (secondaryEventReceiver != null) { - synchronized (this) { + synchronized (primaryEventHandler) { try { - this.wait(); + primaryEventHandler.wait(); } catch (InterruptedException e) { // SQUASH } } } + + // System.err.printf("EXIT YIELD\n"); } /** @@ -884,7 +969,7 @@ public class TApplication { Date now = new Date(); List keepTimers = new LinkedList(); for (TTimer timer: timers) { - if (timer.getNextTick().getTime() < now.getTime()) { + if (timer.getNextTick().getTime() <= now.getTime()) { timer.tick(); if (timer.recurring) { keepTimers.add(timer); @@ -904,24 +989,27 @@ public class TApplication { /** * Get the amount of time I can sleep before missing a Timer tick. * - * @param timeout = initial (maximum) timeout + * @param timeout = initial (maximum) timeout in millis * @return number of milliseconds between now and the next timer event */ - protected int getSleepTime(final int timeout) { + protected long getSleepTime(final long timeout) { Date now = new Date(); + long nowTime = now.getTime(); long sleepTime = timeout; for (TTimer timer: timers) { - if (timer.getNextTick().getTime() < now.getTime()) { + long nextTickTime = timer.getNextTick().getTime(); + if (nextTickTime < nowTime) { return 0; } - if ((timer.getNextTick().getTime() > now.getTime()) - && ((timer.getNextTick().getTime() - now.getTime()) < sleepTime) - ) { - sleepTime = timer.getNextTick().getTime() - now.getTime(); + + long timeDifference = nextTickTime - nowTime; + if (timeDifference < sleepTime) { + sleepTime = timeDifference; } } assert (sleepTime >= 0); - return (int)sleepTime; + assert (sleepTime <= timeout); + return sleepTime; } /** @@ -931,20 +1019,22 @@ public class TApplication { * @param window the window to remove */ public final void closeWindow(final TWindow window) { - int z = window.getZ(); - window.setZ(-1); - Collections.sort(windows); - windows.remove(0); - TWindow activeWindow = null; - for (TWindow w: windows) { - if (w.getZ() > z) { - w.setZ(w.getZ() - 1); - if (w.getZ() == 0) { - w.setActive(true); - assert (activeWindow == null); - activeWindow = w; - } else { - w.setActive(false); + synchronized (windows) { + int z = window.getZ(); + window.setZ(-1); + Collections.sort(windows); + windows.remove(0); + TWindow activeWindow = null; + for (TWindow w: windows) { + if (w.getZ() > z) { + w.setZ(w.getZ() - 1); + if (w.getZ() == 0) { + w.setActive(true); + assert (activeWindow == null); + activeWindow = w; + } else { + w.setActive(false); + } } } } @@ -963,10 +1053,10 @@ public class TApplication { // window is closed. secondaryEventReceiver = null; - // Wake all threads: primary thread will be consuming events - // again, and secondary thread will exit. - synchronized (this) { - this.notifyAll(); + // Wake the secondary thread, it will wake the primary as it + // exits. + synchronized (secondaryEventHandler) { + secondaryEventHandler.notify(); } } } @@ -983,35 +1073,39 @@ public class TApplication { return; } - // Swap z/active between active window and the next in the list - int activeWindowI = -1; - for (int i = 0; i < windows.size(); i++) { - if (windows.get(i).getActive()) { - activeWindowI = i; - break; + synchronized (windows) { + + // Swap z/active between active window and the next in the list + int activeWindowI = -1; + for (int i = 0; i < windows.size(); i++) { + if (windows.get(i).getActive()) { + activeWindowI = i; + break; + } } - } - assert (activeWindowI >= 0); + assert (activeWindowI >= 0); - // Do not switch if a window is modal - if (windows.get(activeWindowI).isModal()) { - return; - } + // Do not switch if a window is modal + if (windows.get(activeWindowI).isModal()) { + return; + } - int nextWindowI; - if (forward) { - nextWindowI = (activeWindowI + 1) % windows.size(); - } else { - if (activeWindowI == 0) { - nextWindowI = windows.size() - 1; + int nextWindowI; + if (forward) { + nextWindowI = (activeWindowI + 1) % windows.size(); } else { - nextWindowI = activeWindowI - 1; + if (activeWindowI == 0) { + nextWindowI = windows.size() - 1; + } else { + nextWindowI = activeWindowI - 1; + } } - } - windows.get(activeWindowI).setActive(false); - windows.get(activeWindowI).setZ(windows.get(nextWindowI).getZ()); - windows.get(nextWindowI).setZ(0); - windows.get(nextWindowI).setActive(true); + windows.get(activeWindowI).setActive(false); + windows.get(activeWindowI).setZ(windows.get(nextWindowI).getZ()); + windows.get(nextWindowI).setZ(0); + windows.get(nextWindowI).setActive(true); + + } // synchronized (windows) // Refresh repaint = true; @@ -1023,17 +1117,19 @@ public class TApplication { * @param window new window to add */ public final void addWindow(final TWindow window) { - // Do not allow a modal window to spawn a non-modal window - if ((windows.size() > 0) && (windows.get(0).isModal())) { - assert (window.isModal()); - } - for (TWindow w: windows) { - w.setActive(false); - w.setZ(w.getZ() + 1); + synchronized (windows) { + // Do not allow a modal window to spawn a non-modal window + if ((windows.size() > 0) && (windows.get(0).isModal())) { + assert (window.isModal()); + } + for (TWindow w: windows) { + w.setActive(false); + w.setZ(w.getZ() + 1); + } + windows.add(window); + window.setActive(true); + window.setZ(0); } - windows.add(window); - window.setActive(true); - window.setZ(0); } /** @@ -1160,29 +1256,31 @@ public class TApplication { return; } - Collections.sort(windows); - if (windows.get(0).isModal()) { - // Modal windows don't switch - return; - } + synchronized (windows) { + Collections.sort(windows); + if (windows.get(0).isModal()) { + // Modal windows don't switch + return; + } - for (TWindow window: windows) { - assert (!window.isModal()); - if (window.mouseWouldHit(mouse)) { - if (window == windows.get(0)) { - // Clicked on the same window, nothing to do + for (TWindow window: windows) { + assert (!window.isModal()); + if (window.mouseWouldHit(mouse)) { + if (window == windows.get(0)) { + // Clicked on the same window, nothing to do + return; + } + + // We will be switching to another window + assert (windows.get(0).getActive()); + assert (!window.getActive()); + windows.get(0).setActive(false); + windows.get(0).setZ(window.getZ()); + window.setZ(0); + window.setActive(true); + repaint = true; return; } - - // We will be switching to another window - assert (windows.get(0).getActive()); - assert (!window.getActive()); - windows.get(0).setActive(false); - windows.get(0).setZ(window.getZ()); - window.setZ(0); - window.setActive(true); - repaint = true; - return; } } @@ -1490,8 +1588,11 @@ public class TApplication { if (activeMenu != null) { return; } - for (TWindow window: windows) { - closeWindow(window); + + synchronized (windows) { + for (TWindow window: windows) { + closeWindow(window); + } } } @@ -1500,52 +1601,54 @@ public class TApplication { * almost the same results as Turbo Pascal 7.0's IDE. */ private void tileWindows() { - // Don't do anything if we are in the menu - if (activeMenu != null) { - return; - } - int z = windows.size(); - if (z == 0) { - return; - } - int a = 0; - int b = 0; - a = (int)(Math.sqrt(z)); - int c = 0; - while (c < a) { - b = (z - c) / a; - if (((a * b) + c) == z) { - break; + synchronized (windows) { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; } - c++; - } - assert (a > 0); - assert (b > 0); - assert (c < a); - int newWidth = (getScreen().getWidth() / a); - int newHeight1 = ((getScreen().getHeight() - 1) / b); - int newHeight2 = ((getScreen().getHeight() - 1) / (b + c)); - - List sorted = new LinkedList(windows); - Collections.sort(sorted); - Collections.reverse(sorted); - for (int i = 0; i < sorted.size(); i++) { - int logicalX = i / b; - int logicalY = i % b; - if (i >= ((a - 1) * b)) { - logicalX = a - 1; - logicalY = i - ((a - 1) * b); + int z = windows.size(); + if (z == 0) { + return; } + int a = 0; + int b = 0; + a = (int)(Math.sqrt(z)); + int c = 0; + while (c < a) { + b = (z - c) / a; + if (((a * b) + c) == z) { + break; + } + c++; + } + assert (a > 0); + assert (b > 0); + assert (c < a); + int newWidth = (getScreen().getWidth() / a); + int newHeight1 = ((getScreen().getHeight() - 1) / b); + int newHeight2 = ((getScreen().getHeight() - 1) / (b + c)); + + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (int i = 0; i < sorted.size(); i++) { + int logicalX = i / b; + int logicalY = i % b; + if (i >= ((a - 1) * b)) { + logicalX = a - 1; + logicalY = i - ((a - 1) * b); + } - TWindow w = sorted.get(i); - w.setX(logicalX * newWidth); - w.setWidth(newWidth); - if (i >= ((a - 1) * b)) { - w.setY((logicalY * newHeight2) + 1); - w.setHeight(newHeight2); - } else { - w.setY((logicalY * newHeight1) + 1); - w.setHeight(newHeight1); + TWindow w = sorted.get(i); + w.setX(logicalX * newWidth); + w.setWidth(newWidth); + if (i >= ((a - 1) * b)) { + w.setY((logicalY * newHeight2) + 1); + w.setHeight(newHeight2); + } else { + w.setY((logicalY * newHeight1) + 1); + w.setHeight(newHeight1); + } } } } @@ -1554,25 +1657,27 @@ public class TApplication { * Re-layout the open windows as overlapping cascaded windows. */ private void cascadeWindows() { - // Don't do anything if we are in the menu - if (activeMenu != null) { - return; - } - int x = 0; - int y = 1; - List sorted = new LinkedList(windows); - Collections.sort(sorted); - Collections.reverse(sorted); - for (TWindow window: sorted) { - window.setX(x); - window.setY(y); - x++; - y++; - if (x > getScreen().getWidth()) { - x = 0; + synchronized (windows) { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; } - if (y >= getScreen().getHeight()) { - y = 1; + int x = 0; + int y = 1; + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (TWindow window: sorted) { + window.setX(x); + window.setY(y); + x++; + y++; + if (x > getScreen().getWidth()) { + x = 0; + } + if (y >= getScreen().getHeight()) { + y = 1; + } } } }