82934cf4230ebc0746ad11ab18f8ab87aaf55702
[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
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                 if (Log.on) Log.log(this, "    lastfunc            == " + lastfunc);
91                 if (Log.on) Log.log(this, "    lastmessage         == " + lastmessage);
92                 if (Log.on) Log.log(this, t);
93                 if (Log.on) Log.log(this, "resuming MessageQueue loop");
94             }
95         }
96     }
97
98     /** Adds an event to the queue */
99     public static void add(Message e) {
100
101         // Even though we don't synchronize around these two
102         // statements, it is not a race condition. In the worst case,
103         // nonThreadEventsInQueue undercounts -- it never
104         // overcounts.
105
106         events.append(e);
107         if (!(e instanceof Thread)) nonThreadEventsInQueue++;
108     }
109
110     /** a simple thread that logs a warning if too much time is spent in any given message -- helpful for debugging infinite loops */
111     private static class MessageQueueWatcher extends Thread {
112         long t = System.currentTimeMillis();
113         Message m = null;
114         public MessageQueueWatcher() { start(); }
115         public void run() {
116             while(true) {
117                 if ((m != null && m == MessageQueue.currentlyPerforming) || MessageQueue.working) {
118                     String what, where;
119                     if (m != null && m instanceof ThreadMessage) {
120                         where = org.xwt.js.JS.Thread.fromJavaThread((ThreadMessage)m).getSourceName();
121                         what = "background thread";
122                     } else if (m != null) {
123                         where = org.xwt.js.JS.Thread.fromJavaThread(MessageQueue.singleton).getSourceName();
124                         what = "event trap";
125                     } else {
126                         where = org.xwt.js.JS.Thread.fromJavaThread(MessageQueue.singleton).getSourceName();
127                         what = "script";
128                     }
129                     long howlong = (System.currentTimeMillis() - t) / 1000;
130                     if (howlong >= 5)
131                         if (Log.on) Log.log(this, "note: executing same " + what + " for " + howlong + "s" + " at " + where);
132                 } else {
133                     m = MessageQueue.currentlyPerforming;
134                     t = System.currentTimeMillis();
135                 }
136                 try { Thread.sleep(1000); } catch (Exception e) { }
137             }
138         }
139     }
140     
141 }
142
143
144