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