1 /* queue.c - a queue of events to happen
2 *
3 * Copyright 1999, 2000 Jochen Voss */
4
5 static const char rcsid[] = "$Id: queue.c 4839 2003-04-13 16:50:02Z voss $";
6
7 #ifdef HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10
11 #ifdef _XOPEN_SOURCE
12 #define _XOPEN_SOURCE_EXTENDED 1
13 #endif
14
15 #include <stdlib.h>
16 #include <string.h>
17 #include <fcntl.h>
18 #include <sys/time.h>
19 #include <sys/types.h>
20 #ifdef HAVE_SYS_SELECT_H
21 #include <sys/select.h>
22 #endif
23 #include <unistd.h>
24 #include <math.h>
25 #ifdef HAVE_ERRNO_H
26 #include <errno.h>
27 #else
28 extern int errno;
29 #endif
30 #include <assert.h>
31
32 #if defined(__hp9000s800)
33 #include <stdarg.h>
34 #endif
35
36 #include "moon-buggy.h"
37
38
39 /* The queue of events */
40 struct event {
41 struct event *next;
42 game_time t; /* time, when the event should happen */
43 callback_fn callback; /* function to call */
44 void *client_data; /* argument to pass */
45 };
46 static struct event *queue;
47
48 /**********************************************************************
49 * convert between game time and real time
50 */
51
52 typedef double real_time;
53
54 /* The type `game_time' is relative to `time_base'. */
55 static double time_base;
56
57 static real_time
to_real(game_time t)58 to_real (game_time t)
59 {
60 return t + time_base;
61 }
62
63 static game_time
to_game(real_time t)64 to_game (real_time t)
65 {
66 return t - time_base;
67 }
68
69 game_time
current_time(void)70 current_time (void)
71 {
72 return to_game (vclock ());
73 }
74
75 /**********************************************************************
76 * wait for timeouts or keyboard input
77 */
78
79 static int
my_select(struct timeval * timeout)80 my_select (struct timeval *timeout)
81 /* Wait until input is ready on stdin or a timeout is reached.
82 * TIMEOUT has the same meaning, as is has for the `select' system
83 * call. The return value is 0 if we return because of a timeout,
84 * positive if input is ready, and negative if a signal occured. In
85 * the latter case we must calculate a new TIMEOUT value and call
86 * `my_select' again. */
87 {
88 fd_set rfds;
89 int res;
90
91 /* Watch stdin (fd 0) to see when it has input. */
92 FD_ZERO (&rfds);
93 FD_SET (0, &rfds);
94
95 if (handle_signals ()) return -1;
96 res = select (FD_SETSIZE, &rfds, NULL, NULL, timeout);
97 if (res < 0) {
98 if (errno == EINTR) {
99 handle_signals ();
100 } else {
101 fatal ("Select failed: %s", strerror (errno));
102 }
103 }
104 return res;
105 }
106
107 static int
key_ready(void)108 key_ready (void)
109 /* Return a positive value iff keyboard input is ready. */
110 {
111 int res;
112
113 do {
114 struct timeval ancient_time;
115
116 ancient_time.tv_sec = 0;
117 ancient_time.tv_usec = 0;
118 res = my_select (&ancient_time);
119 } while (res < 0);
120 return res;
121 }
122
123 static void
wait_for_key(void)124 wait_for_key (void)
125 {
126 int res;
127
128 do {
129 res = my_select (NULL);
130 } while (res < 0);
131 }
132
133 static int
wait_until(game_time t,real_time * t_return)134 wait_until (game_time t, real_time *t_return)
135 /* Wait for time *T or the next key press, whichever comes first.
136 * Return a positive value, if a key was pressed, and 0 else.
137 * Set *T_RETURN to the return time. */
138 {
139 double start;
140 int res;
141
142 do {
143 double dt, sec, usec;
144 struct timeval tv;
145
146 start = vclock ();
147 dt = to_real(t) - start;
148 if (dt <= 0) {
149 *t_return = start;
150 return key_ready ();
151 }
152
153 usec = 1e6 * modf (dt, &sec) + 0.5;
154 tv.tv_sec = sec + 0.5;
155 tv.tv_usec = usec + 0.5;
156 res = my_select (&tv);
157 } while (res < 0);
158 *t_return = vclock ();
159
160 return res;
161 }
162
163 static void
drain_input(void)164 drain_input (void)
165 /* Discard all data from the input queue. */
166 {
167 char buffer [16];
168 int oldflags;
169
170 oldflags = fcntl (0, F_GETFL, 0);
171 if (oldflags < 0) {
172 fatal ("Cannot get file status flags (%s)", strerror (errno));
173 }
174 fcntl (0, F_SETFL, oldflags|O_NONBLOCK);
175 while (read (0, buffer, 16) == 16)
176 ;
177 fcntl (0, F_SETFL, oldflags);
178 }
179
180 void
clock_reset(void)181 clock_reset (void)
182 /* Adjust the clock to make 0 the current time. */
183 {
184 time_base = vclock ();
185 }
186
187 static void
dummy_h(game_time t,void * client_data)188 dummy_h (game_time t, void *client_data)
189 /* This function is a possible callback argument to `add_event'.
190 * It does nothing. */
191 {
192 return;
193 }
194
195 void
clock_freeze(void)196 clock_freeze (void)
197 /* Prepare to freeze the game's clock.
198 * This function should be called before the game is resumed.
199 * Afterwards you should use `clock_thaw ()' to restart the
200 * game in the current state. If the next event is less then 0.1
201 * seconds in the future, we add some gap to allow for 0.1 seconds
202 * pause after the restart. */
203 {
204 game_time t;
205
206 remove_event (dummy_h);
207 t = current_time ();
208 if (queue && t >= queue->t - 0.1) t = queue->t - 0.1;
209 add_event (t, dummy_h, NULL);
210 }
211
212 void
clock_thaw(void)213 clock_thaw (void)
214 /* Adjust the clock to make the next event occur immediately.
215 * The queue must contain at least one element. */
216 {
217 assert (queue);
218 time_base = vclock () - queue->t;
219 }
220
221 void
clear_queue(void)222 clear_queue (void)
223 /* Remove all events from the queue. */
224 {
225 struct event *ev;
226
227 ev = queue, queue = NULL;
228 while (ev) {
229 struct event *old = ev;
230 ev = old->next;
231 free (old);
232 }
233 drain_input ();
234 }
235
236 void
add_event(game_time t,callback_fn callback,void * client_data)237 add_event (game_time t, callback_fn callback, void *client_data)
238 /* Add a new event for time T to the queue.
239 * The event calls function CALLBACK with argument CLIENT_DATA. */
240 {
241 struct event **evp;
242 struct event *ev;
243
244 evp = &queue;
245 while (*evp && (*evp)->t <= t) evp = &((*evp)->next);
246
247 ev = xmalloc (sizeof (struct event));
248 ev->next = *evp;
249 ev->t = t;
250 ev->callback = callback;
251 ev->client_data = client_data;
252
253 *evp = ev;
254 }
255
256 void
remove_event(callback_fn callback)257 remove_event (callback_fn callback)
258 /* Remove all events from the queue, which would call CALLBACK. */
259 {
260 struct event **evp;
261
262 evp = &queue;
263 while (*evp) {
264 struct event *ev = *evp;
265 if (ev->callback == callback) {
266 *evp = (*evp)->next;
267 free (ev);
268 } else {
269 evp = &((*evp)->next);
270 }
271 }
272 }
273
274 void
remove_client_data(void * client_data)275 remove_client_data (void *client_data)
276 /* Remove all events from the queue, which refer to CLIENT_DATA. */
277 {
278 struct event **evp;
279
280 evp = &queue;
281 while (*evp) {
282 struct event *ev = *evp;
283 if (ev->client_data == client_data) {
284 *evp = (*evp)->next;
285 free (ev);
286 } else {
287 evp = &((*evp)->next);
288 }
289 }
290 }
291
292 /**********************************************************************
293 * the main loop
294 */
295
296 static int exit_flag;
297
298 void
quit_main_loop(void)299 quit_main_loop (void)
300 {
301 exit_flag = 1;
302 }
303
304 void
main_loop(void)305 main_loop (void)
306 {
307 clock_reset ();
308 exit_flag = 0;
309
310 while (! exit_flag) {
311 int retval;
312 double t;
313
314 mode_update ();
315
316 if (queue) {
317 retval = wait_until (queue->t, &t);
318 } else {
319 wait_for_key ();
320 t = vclock ();
321 retval = 1;
322 }
323
324 if (retval>0) {
325 int meaning = read_key ();
326 if (meaning != -1) {
327 if (! mode_keypress (to_game (t), meaning)) beep ();
328 }
329 }
330
331 while (queue && queue->t <= current_time ()) {
332 struct event *ev = queue;
333 queue = queue->next;
334
335 ev->callback (ev->t, ev->client_data);
336 free (ev);
337 }
338 }
339 }
340
341 /**********************************************************************
342 * some handlers for the main loop above
343 */
344
345 void
print_hint_h(game_time t,void * client_data)346 print_hint_h (game_time t, void *client_data)
347 /* This function is a possible callback argument to `add_event'.
348 * It causes the level hint (const char *)CLIENT_DATA to be
349 * displayed for 4 seconds. */
350 {
351 print_hint (client_data);
352 remove_event (clear_hint_h);
353 add_event (t+4, clear_hint_h, NULL);
354 }
355
356 void
clear_hint_h(game_time t,void * client_data)357 clear_hint_h (game_time t, void *client_data)
358 /* This function is a possible callback argument to `add_event'.
359 * It causes the screen's hint area to be cleared.
360 * The arguments T and CLIENT_DATA are ignored. */
361 {
362 wmove (moon, LINES-11, 0);
363 wclrtoeol (moon);
364 wnoutrefresh (moon);
365 }
366