-// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
package org.xwt;
+import java.util.*;
+import org.xwt.util.*;
+
+
/** A simple interface that must be supported by any object inserted into the MessageQueue. */
public interface Message {
/** Invoked when the Message is dequeued. */
public void perform();
+
+ /**
+ * A singleton class (one instance per JVM) that implements a queue
+ * for XWT events and threads; this is the main action-scheduling
+ * loop for the engine.
+ *
+ * This <i>is</i> the foreground thread -- it
+ * dequeues Messages when they arrive in the queue. Using this
+ * thread ensures that the messages are executed in a synchronous,
+ * in-order fashion.
+ */
+ public static class Q extends Thread {
+
+ /** a do-nothing message enqueued to trigger Surfaces to refresh themselves */
+ private static Message refreshMessage = new Message() { public void perform() { } };
+
+ /** enqueues a do-nothing message to get the Surfaces to refresh themselves */
+ public static void refresh() { add(refreshMessage); }
+
+ /** true iff latency-sensitive UI work is being done; signals the networking code to yield */
+ public static volatile boolean working = false;
+
+ private Q() { }
+
+ /** invoked by Main */
+ public static void startQ() { singleton.start(); }
+
+ /** pending events */
+ private static Queue events = new Queue(50);
+
+ /** the number of objects in the queue that are not subclasses of ThreadMessage */
+ public static volatile int nonThreadEventsInQueue = 0;
+
+ /** the message currently being performed */
+ static Message currentlyPerforming = null;
+
+ private static Q singleton = new Q();
+ private static Watcher watcher = new Watcher();
+
+ /**
+ * The message loop. Note that non-ThreadMessage Messages get
+ * priority, and that the queue is emptied in passes -- first we
+ * look at how many events are in the queue, then perform that
+ * many events, then render. This has the effect of throttling
+ * render requests to the appropriate frequency -- when many
+ * messages are in the queue, refreshes happen less frequently.
+ */
+ public void run() {
+ Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
+ while(true) {
+ try {
+ int size = events.size();
+ for(int i=0; i<Math.max(1, size); i++) {
+ Message e = (Message)events.remove();
+
+ // if there are non-ThreadMessage events, perform them first
+ // we check against Thread instead of ThreadMessage so we don't get link failures in the Shoehorn
+ if (!(e instanceof Thread)) nonThreadEventsInQueue--;
+ else if (nonThreadEventsInQueue > 0) {
+ add(e);
+ i--;
+ continue;
+ }
+ if (!(e instanceof Thread)) working = true;
+ currentlyPerforming = e;
+ e.perform();
+ currentlyPerforming = null;
+ working = false;
+ }
+ working = true;
+ for(int i=0; i<Surface.allSurfaces.size(); i++)
+ ((Surface)Surface.allSurfaces.elementAt(i)).render();
+ working = false;
+
+ } catch (Throwable t) {
+ if (Log.on) Log.log(this, "caught throwable in Q.run(); this should never happen");
+ if (Log.on) Log.log(this, " currentlyPerforming == " + currentlyPerforming);
+ if (Log.on) Log.log(this, " working == " + working);
+ if (Log.on) Log.log(this, t);
+ if (Log.on) Log.log(this, "resuming Q loop");
+ }
+ }
+ }
+
+ /** Adds an event to the queue */
+ public static void add(Message e) {
+ // Even though we don't synchronize around these two statements, it is not a race condition. In the worst case,
+ // nonThreadEventsInQueue undercounts -- it never overcounts.
+ events.append(e);
+ if (!(e instanceof Thread)) nonThreadEventsInQueue++;
+ }
+
+ /** a simple thread that logs a warning if too much time is spent in any given message -- helpful for debugging infinite loops */
+ private static class Watcher extends Thread {
+ long t = System.currentTimeMillis();
+ Message m = null;
+ public Watcher() { start(); }
+ public void run() {
+ while(true) {
+ if ((m != null && m == Q.currentlyPerforming) || Q.working) {
+ String what, where;
+ if (m != null && m instanceof ThreadMessage) {
+ where = org.xwt.js.JS.Thread.fromJavaThread((ThreadMessage)m).getSourceName();
+ what = "background thread";
+ } else if (m != null) {
+ where = org.xwt.js.JS.Thread.fromJavaThread(Q.singleton).getSourceName();
+ what = "event trap";
+ } else {
+ where = org.xwt.js.JS.Thread.fromJavaThread(Q.singleton).getSourceName();
+ what = "script";
+ }
+ long howlong = (System.currentTimeMillis() - t) / 1000;
+ if (howlong >= 5)
+ if (Log.on) Log.log(this, "note: executing same " + what + " for " + howlong + "s" + " at " + where);
+ } else {
+ m = Q.currentlyPerforming;
+ t = System.currentTimeMillis();
+ }
+ try { Thread.sleep(1000); } catch (Exception e) { }
+ }
+ }
+ }
+
+ }
+
}