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