2003/09/23 08:24:59
[org.ibex.core.git] / src / org / xwt / Message.java
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() { start(); }
37
38         /** pending events */
39         private static Queue events = new Queue(50);
40
41         /** the number of objects in the queue that are not subclasses of ThreadMessage */
42         public static volatile int nonThreadEventsInQueue = 0;
43
44         /** the message currently being performed */    
45         static Message currentlyPerforming = null;
46
47         private static Q singleton = new Q();
48         private static Watcher watcher = new Watcher();
49
50         /**
51          *  The message loop. Note that non-ThreadMessage Messages get
52          *  priority, and that the queue is emptied in passes -- first we
53          *  look at how many events are in the queue, then perform that
54          *  many events, then render. This has the effect of throttling
55          *  render requests to the appropriate frequency -- when many
56          *  messages are in the queue, refreshes happen less frequently.
57          */
58         public void run() {
59             Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
60             while(true) {
61                 try {
62                     int size = events.size();
63                     for(int i=0; i<Math.max(1, size); i++) {
64                         Message e = (Message)events.remove();
65
66                         // if there are non-ThreadMessage events, perform them first
67                         // we check against Thread instead of ThreadMessage so we don't get link failures in the Shoehorn
68                         if (!(e instanceof Thread)) nonThreadEventsInQueue--;
69                         else if (nonThreadEventsInQueue > 0) {
70                             add(e);
71                             i--;
72                             continue;
73                         }
74                         if (!(e instanceof Thread)) working = true;
75                         currentlyPerforming = e;
76                         e.perform();
77                         currentlyPerforming = null;
78                         working = false;
79                     }
80                     working = true;
81                     for(int i=0; i<Surface.allSurfaces.size(); i++)
82                         ((Surface)Surface.allSurfaces.elementAt(i)).render();
83                     working = false;
84
85                 } catch (Throwable t) {
86                     if (Log.on) Log.log(this, "caught throwable in Q.run(); this should never happen");
87                     if (Log.on) Log.log(this, "    currentlyPerforming == " + currentlyPerforming);
88                     if (Log.on) Log.log(this, "    working             == " + working);
89                     // FIXME - this currently calls compiledfunction.toString which gives more info than we need
90                     if (Log.on) Log.log(this, t);
91                     if (Log.on) Log.log(this, "resuming Q loop");
92                 }
93             }
94         }
95
96         /** Adds an event to the queue */
97         public static void add(Message e) {
98             // Even though we don't synchronize around these two statements, it is not a race condition. In the worst case,
99             // nonThreadEventsInQueue undercounts -- it never overcounts.
100             events.append(e);
101             if (!(e instanceof Thread)) nonThreadEventsInQueue++;
102         }
103
104         /** a simple thread that logs a warning if too much time is spent in any given message -- helpful for debugging infinite loops */
105         private static class Watcher extends Thread {
106             long t = System.currentTimeMillis();
107             Message m = null;
108             public Watcher() { start(); }
109             public void run() {
110                 while(true) {
111                     if ((m != null && m == Q.currentlyPerforming) || Q.working) {
112                         String what, where;
113                         if (m != null && m instanceof ThreadMessage) {
114                             where = org.xwt.js.JS.Thread.fromJavaThread((ThreadMessage)m).getSourceName();
115                             what = "background thread";
116                         } else if (m != null) {
117                             where = org.xwt.js.JS.Thread.fromJavaThread(Q.singleton).getSourceName();
118                             what = "event trap";
119                         } else {
120                             where = org.xwt.js.JS.Thread.fromJavaThread(Q.singleton).getSourceName();
121                             what = "script";
122                         }
123                         long howlong = (System.currentTimeMillis() - t) / 1000;
124                         if (howlong >= 5)
125                             if (Log.on) Log.log(this, "note: executing same " + what + " for " + howlong + "s" + " at " + where);
126                     } else {
127                         m = Q.currentlyPerforming;
128                         t = System.currentTimeMillis();
129                     }
130                     try { Thread.sleep(1000); } catch (Exception e) { }
131                 }
132             }
133         }
134     
135     }
136
137 }
138