2002/06/25 04:54:40
[org.ibex.core.git] / src / org / xwt / MessageQueue.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt;
3
4 import java.util.*;
5 import org.xwt.util.*;
6 import org.mozilla.javascript.*;
7
8 /** 
9  *  A singleton class (one instance per JVM) that implements a queue
10  *  for XWT events and threads; this is the main action-scheduling
11  *  loop for the engine.
12  *
13  *  This <i>is</i> the foreground thread -- it
14  *  dequeues Messages when they arrive in the queue. Using this
15  *  thread ensures that the messages are executed in a synchronous,
16  *  in-order fashion.
17  */
18 public class MessageQueue extends Thread {
19
20     /** a do-nothing message enqueued to trigger Surfaces to refresh themselves */
21     private static Message refreshMessage = new Message() { public void perform() { } };
22
23     /** enqueues a do-nothing message to get the Surfaces to refresh themselves */
24     public static void refresh() { add(refreshMessage); }
25
26     /** true iff latency-sensitive UI work is being done; signals the networking code to yield */
27     public static volatile boolean working = false;
28
29     private MessageQueue() { start(); }
30
31     /** pending events */
32     private static Queue events = new Queue(50);
33
34     /** the number of objects in the queue that are not subclasses of ThreadMessage */
35     private static volatile int nonThreadEventsInQueue = 0;
36
37     /** the message currently being performed */    
38     static Message currentlyPerforming = null;
39
40     private static MessageQueue singleton = new MessageQueue();
41     private static MessageQueueWatcher watcher = new MessageQueueWatcher();
42
43     // HACK for debugging purposes
44     Function lastfunc = null;
45     Message lastmessage = null;
46
47     /**
48      *  The message loop. Note that non-ThreadMessage Messages get
49      *  priority, and that the queue is emptied in passes -- first we
50      *  look at how many events are in the queue, then perform that
51      *  many events, then render. This has the effect of throttling
52      *  render requests to the appropriate frequency -- when many
53      *  messages are in the queue, refreshes happen less frequently.
54      */
55     public void run() {
56         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
57         while(true) {
58             try {
59                 int size = events.size();
60                 for(int i=0; i<Math.max(1, size); i++) {
61                     Message e = (Message)events.remove();
62
63                     // if there are non-ThreadMessage events, perform them first
64                     // we check against Thread instead of ThreadMessage so we don't get link failures in the Shoehorn
65                     if (!(e instanceof Thread)) nonThreadEventsInQueue--;
66                     else if (nonThreadEventsInQueue > 0) {
67                         add(e);
68                         i--;
69                         continue;
70                     }
71                     if (!(e instanceof Thread)) working = true;
72                     currentlyPerforming = e;
73
74                     // for debugging purposes
75                     lastmessage = e;
76                     if (e != null && e instanceof ThreadMessage) lastfunc = ((ThreadMessage)e).f;
77
78                     e.perform();
79                     currentlyPerforming = null;
80                     working = false;
81                 }
82                 working = true;
83                 for(int i=0; i<Surface.allSurfaces.size(); i++)
84                     ((Surface)Surface.allSurfaces.elementAt(i)).render();
85                 working = false;
86
87             } catch (Throwable t) {
88                 if (Log.on) Log.log(this, "caught throwable in MessageQueue.run(); this should never happen");
89                 if (Log.on) Log.log(this, "    currentlyPerforming == " + currentlyPerforming);
90                 if (Log.on) Log.log(this, "    working             == " + working);
91                 if (Log.on) Log.log(this, "    lastfunc            == " + lastfunc);
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) {
119                     Context cx;
120                     String what;
121                     if (m instanceof ThreadMessage) {
122                         ThreadMessage tm = (ThreadMessage)m;
123                         cx = Context.getContextForThread(tm);
124                         what = "background thread";
125                     } else {
126                         cx = Context.getContextForThread(MessageQueue.singleton);
127                         what = Main.initializationComplete ? "trap" : "script";
128                     }
129                     if (Log.on) Log.log(this, "WARNING: executing same " + what + " for " + (System.currentTimeMillis() - t) / 1000 + "s" +
130                                         " at " + cx.interpreterSourceFile + ":" + cx.interpreterLine);
131                 } else {
132                     m = MessageQueue.currentlyPerforming;
133                     t = System.currentTimeMillis();
134                 }
135                 try { Thread.sleep(Main.initializationComplete ? 1000 : 15000); } catch (Exception e) { }
136             }
137         }
138     }
139     
140 }
141
142
143