1    // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
2    package org.xwt;
3    
4    import java.util.*;
5    import org.xwt.util.*;
6    
7    /** 
8     *  A singleton class (one instance per JVM) that implements a queue
9     *  for XWT events and threads; this is the main action-scheduling
10    *  loop for the engine.
11    *
12    *  This <i>is</i> the foreground thread -- it
13    *  dequeues Messages when they arrive in the queue. Using this
14    *  thread ensures that the messages are executed in a synchronous,
15    *  in-order fashion.
16    */
17   public class MessageQueue extends Thread {
18   
19       /** a do-nothing message enqueued to trigger Surfaces to refresh themselves */
20       private static Message refreshMessage = new Message() { public void perform() { } };
21   
22       /** enqueues a do-nothing message to get the Surfaces to refresh themselves */
23       public static void refresh() { add(refreshMessage); }
24   
25       /** true iff latency-sensitive UI work is being done; signals the networking code to yield */
26       public static volatile boolean working = false;
27   
28       private MessageQueue() { start(); }
29   
30       /** pending events */
31       private static Queue events = new Queue(50);
32   
33       /** the number of objects in the queue that are not subclasses of ThreadMessage */
34       public static volatile int nonThreadEventsInQueue = 0;
35   
36       /** the message currently being performed */    
37       static Message currentlyPerforming = null;
38   
39       private static MessageQueue singleton = new MessageQueue();
40       private static MessageQueueWatcher watcher = new MessageQueueWatcher();
41   
42       // HACK for debugging purposes
43       Object lastfunc = null;
44       Message lastmessage = null;
45   
46       /**
47        *  The message loop. Note that non-ThreadMessage Messages get
48        *  priority, and that the queue is emptied in passes -- first we
49        *  look at how many events are in the queue, then perform that
50        *  many events, then render. This has the effect of throttling
51        *  render requests to the appropriate frequency -- when many
52        *  messages are in the queue, refreshes happen less frequently.
53        */
54       public void run() {
55           Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
56           while(true) {
57               try {
58                   int size = events.size();
59                   for(int i=0; i<Math.max(1, size); i++) {
60                       Message e = (Message)events.remove();
61   
62                       // if there are non-ThreadMessage events, perform them first
63                       // we check against Thread instead of ThreadMessage so we don't get link failures in the Shoehorn
64                       if (!(e instanceof Thread)) nonThreadEventsInQueue--;
65                       else if (nonThreadEventsInQueue > 0) {
66                           add(e);
67                           i--;
68                           continue;
69                       }
70                       if (!(e instanceof Thread)) working = true;
71                       currentlyPerforming = e;
72   
73                       // for debugging purposes
74                       lastmessage = e;
75                       if (e != null && e instanceof ThreadMessage) lastfunc = ((ThreadMessage)e).f;
76   
77                       e.perform();
78                       currentlyPerforming = null;
79                       working = false;
80                   }
81                   working = true;
82                   for(int i=0; i<Surface.allSurfaces.size(); i++)
83                       ((Surface)Surface.allSurfaces.elementAt(i)).render();
84                   working = false;
85   
86               } catch (Throwable t) {
87                   if (Log.on) Log.log(this, "caught throwable in MessageQueue.run(); this should never happen");
88                   if (Log.on) Log.log(this, "    currentlyPerforming == " + currentlyPerforming);
89                   if (Log.on) Log.log(this, "    working             == " + working);
90                   // FIXME - this currently calls compiledfunction.toString which gives more info than we need
91                   // if (Log.on) Log.log(this, "    lastfunc            == " + lastfunc.getDescription());
92                   if (Log.on) Log.log(this, "    lastmessage         == " + lastmessage);
93                   if (Log.on) Log.log(this, t);
94                   if (Log.on) Log.log(this, "resuming MessageQueue loop");
95               }
96           }
97       }
98   
99       /** Adds an event to the queue */
100      public static void add(Message e) {
101  
102          // Even though we don't synchronize around these two
103          // statements, it is not a race condition. In the worst case,
104          // nonThreadEventsInQueue undercounts -- it never
105          // overcounts.
106  
107          events.append(e);
108          if (!(e instanceof Thread)) nonThreadEventsInQueue++;
109      }
110  
111      /** a simple thread that logs a warning if too much time is spent in any given message -- helpful for debugging infinite loops */
112      private static class MessageQueueWatcher extends Thread {
113          long t = System.currentTimeMillis();
114          Message m = null;
115          public MessageQueueWatcher() { start(); }
116          public void run() {
117              while(true) {
118                  if ((m != null && m == MessageQueue.currentlyPerforming) || MessageQueue.working) {
119                      String what, where;
120                      if (m != null && m instanceof ThreadMessage) {
121                          where = org.xwt.js.JS.Thread.fromJavaThread((ThreadMessage)m).getSourceName();
122                          what = "background thread";
123                      } else if (m != null) {
124                          where = org.xwt.js.JS.Thread.fromJavaThread(MessageQueue.singleton).getSourceName();
125                          what = "event trap";
126                      } else {
127                          where = org.xwt.js.JS.Thread.fromJavaThread(MessageQueue.singleton).getSourceName();
128                          what = "script";
129                      }
130                      long howlong = (System.currentTimeMillis() - t) / 1000;
131                      if (howlong >= 5)
132                          if (Log.on) Log.log(this, "note: executing same " + what + " for " + howlong + "s" + " at " + where);
133                  } else {
134                      m = MessageQueue.currentlyPerforming;
135                      t = System.currentTimeMillis();
136                  }
137                  try { Thread.sleep(1000); } catch (Exception e) { }
138              }
139          }
140      }
141      
142  }
143  
144  
145  
146