Preliminary, but functional, autotoolsification
[proto.git] / src / sim-app.cpp
blob03d574be1c65c53c67edfb1f34416952a2bd059f
1 /* Simulator application
2 Copyright (C) 2005-2008, Jonathan Bachrach, Jacob Beal, and contributors
3 listed in the AUTHORS file in the MIT Proto distribution's top directory.
5 This file is part of MIT Proto, and is distributed under the terms of
6 the GNU General Public License, with a linking exception, as described
7 in the file LICENSE in the MIT Proto distribution's top directory. */
9 // This file describes how to load all of the needed modules, manage
10 // the evolution of time, and dispatch events.
12 #include "config.h"
13 #include "spatialcomputer.h"
14 #include "utils.h" // also pulls in math
15 #include "compiler.h" // should also end up pulling in these two #defines
16 #include "visualizer.h"
17 #include "motelink.h"
19 void shutdown_app(void);
21 Compiler* compiler = NULL;
22 SpatialComputer* computer = NULL;
23 MoteLink* motelink = NULL;
24 Visualizer* vis = NULL;
26 /*****************************************************************************
27 * TIMING AND UPDATE LOOP *
28 *****************************************************************************/
29 double stop_time = INFINITY; // by default, the simulator runs forever
30 BOOL is_sim_throttling = FALSE; // when true, use time_ratio
31 BOOL is_stepping = FALSE; // is time advancement frozen?
32 BOOL is_step = FALSE; // if in stepping mode, take a single step
33 double time_ratio = 1.0; // sim_time/real_time
34 double step_size = 0.01; // default is 100 fps
35 double sim_time = 0.0; // elapsed simulated time
36 double last_real = 0.0; // last real time
37 double last_sim_time = 0.0; // previous advancement of time
38 double last_inflection_sim = 0.0; // simulator time of last ratio change
39 double last_inflection_real = 0.0; // real time of last ratio change
40 BOOL evolution_lagging = FALSE; // is the simulator keeping up or not?
41 #define FPS_DECAY 0.95
42 double fps=1.0; // frames-per-second measurement
43 BOOL show_time=FALSE;
45 // obtains the time in seconds (in a system-dependent manner)
46 #ifdef __WIN32__
47 #include <windows.h>
48 double get_real_secs () {
49 DWORD tv = timeGetTime();
50 return (double)(tv / 1000.0);
52 #else
53 #include <sys/time.h>
54 double get_real_secs () {
55 struct timeval t;
56 gettimeofday(&t, NULL);
57 return (double)(t.tv_sec + t.tv_usec / 1000000.0);
59 #endif
61 // evolve all top-level items
62 void advance_time() {
63 BOOL changed = FALSE;
64 changed |= compiler->evolve(sim_time);
65 changed |= (vis && vis->evolve(sim_time));
66 changed |= computer->evolve(sim_time);
67 changed |= (motelink && motelink->evolve(sim_time));
68 #ifdef WANT_GLUT
69 if(changed && vis!=NULL) glutPostRedisplay(); // redraw only if needed
70 #endif // WANT_GLUT
73 // this routine is called either by GLUT or directly. It manages time evolution
74 void idle () {
75 if(sim_time>stop_time) shutdown_app(); // quit when time runs out
76 if(is_step || !is_stepping) {
77 is_step=FALSE;
78 double new_real = get_real_secs();
79 if(is_sim_throttling) {
80 // time math avoids incremental deltas to limit error accumulation
81 double real_delta = new_real - last_inflection_real;
82 double new_time = real_delta/time_ratio + last_inflection_sim;
83 if(new_time-last_sim_time > step_size/2) {
84 flo tfps = 1/(new_real-last_real); last_real = new_real;
85 fps = (1-FPS_DECAY)*tfps + FPS_DECAY*fps;
86 sim_time = MAX(new_time,last_sim_time+step_size); // step_size=min step
87 evolution_lagging = (sim_time-last_sim_time > step_size*10);
88 if(evolution_lagging) { // maximum step is 10x normal step
89 sim_time = last_sim_time+(step_size*10);
91 advance_time(); last_sim_time=sim_time;
93 } else {
94 flo tfps = 1/(new_real-last_real); last_real = new_real;
95 fps = (1-FPS_DECAY)*tfps + FPS_DECAY*fps;
96 sim_time+=step_size; evolution_lagging=false;
97 advance_time(); last_sim_time=sim_time;
104 /*****************************************************************************
105 * EVENT HANDLING AND DISPATCH *
106 *****************************************************************************/
107 #define CLICK_FUZZ 5 // # pixels mouse can move before click turns into drag
108 MouseEvent mouse;
109 KeyEvent key;
111 void select_region(flo min_x, flo min_y, flo max_x, flo max_y) {
112 #ifdef WANT_GLUT
113 Rect rgn(min_x,max_x,min_y,max_y);
114 int n = computer->devices.size();
115 vis->start_select_3D(&rgn,n);
116 computer->render_selection();
117 vis->end_select_3D(n,&computer->selection);
118 computer->update_selection();
119 #endif // WANT_GLUT
122 BOOL selecting = FALSE;
123 static double drag_anchor[3], drag_current[3];
125 BOOL app_handle_mouse(MouseEvent *mouse) {
126 #ifdef WANT_GLUT
127 if(!mouse->shift) { // left click selects, right click prints too
128 if(mouse->button==GLUT_LEFT_BUTTON || mouse->button==GLUT_RIGHT_BUTTON) {
129 switch(mouse->state) {
130 case 0: // click
131 select_region(mouse->x-1,mouse->y-1,mouse->x+1,mouse->y+1);
132 if(mouse->button==GLUT_RIGHT_BUTTON) {
133 computer->dump_selection(stdout,1); // print what's been clicked on
135 return TRUE;
138 } else { // shift left drag selects a region
139 if(mouse->button==GLUT_LEFT_BUTTON) {
140 switch(mouse->state) {
141 case 1: // drag start
142 selecting = TRUE;
143 drag_anchor[0]=drag_current[0]=mouse->x;
144 drag_anchor[1]=drag_current[1]=mouse->y;
145 drag_anchor[2]=drag_current[2]=0;
146 return TRUE;
147 case 2: // drag continue
148 drag_current[0]=mouse->x; drag_current[1]=mouse->y;
149 return TRUE;
150 case 3: // drag end
151 drag_current[0]=mouse->x; drag_current[1]=mouse->y;
152 select_region(MIN(drag_anchor[0],drag_current[0]),
153 MIN(drag_anchor[1],drag_current[1]),
154 MAX(drag_anchor[0],drag_current[0]),
155 MAX(drag_anchor[1],drag_current[1]));
156 selecting = FALSE;
157 return TRUE;
159 } else if(mouse->button==GLUT_RIGHT_BUTTON) { // shift-right-drag = move
160 switch(mouse->state) {
161 case 1: // drag start
162 vis->click_3d(mouse->x,mouse->y,drag_anchor);
163 //post("MM: %f, %f, %f\n",drag_anchor[0],drag_anchor[1],drag_anchor[2]);
164 return TRUE;
165 case 2: // drag continue
166 case 3: // draw end
167 vis->click_3d(mouse->x,mouse->y,drag_current);
168 flo dp[3]; for(int i=0;i<3;i++) dp[i]=drag_current[i]-drag_anchor[i];
169 computer->drag_selection(dp);
170 for(int i=0;i<3;i++) drag_anchor[i]=drag_current[i];
171 return TRUE;
175 return FALSE;
176 #endif // WANT_GLUT
179 // A mouse event is either consumed by the visualizer or computer
180 // The visualizer uses window coordinates, the computer uses 3D coordinates
181 // To allow the computer to do modal behavior (e.g. keystroke triggered
182 // selections), we'll check it first
183 void dispatch_mouse_event() {
184 #ifdef WANT_GLUT
185 BOOL handled =
186 app_handle_mouse(&mouse) || computer->handle_mouse(&mouse)
187 || vis->handle_mouse(&mouse);
188 if(handled) glutPostRedisplay();
189 // If a drag isn't handled at the start, it won't generate more dispatches
190 if(mouse.state==1) mouse.state=(handled?2:-1);
191 #endif // WANT_GLUT
194 #define TIME_RATIO_STEP 1.1892 // 2^1/4
195 #define MINIMUM_RATIO 0.001 // no slower than 1ms sim per real second
196 #define MAXIMUM_RATIO 1000 // no faster than 1000 seconds sim per real second
197 BOOL app_handle_key(KeyEvent *key) {
198 if(key->normal) {
199 if(key->ctrl) {
200 switch(key->key) {
201 case 19: // Ctrl-S = slow down
202 if(time_ratio < MAXIMUM_RATIO) {
203 time_ratio *= TIME_RATIO_STEP; step_size /= TIME_RATIO_STEP;
205 last_inflection_sim=sim_time;
206 last_inflection_real=get_real_secs();
207 return TRUE;
208 case 1: // Ctrl-A = speed up
209 if(time_ratio > MINIMUM_RATIO) {
210 time_ratio /= TIME_RATIO_STEP; step_size *= TIME_RATIO_STEP;
212 last_inflection_sim=sim_time;
213 last_inflection_real=get_real_secs();
214 return TRUE;
215 case 4: // Ctrl-D = real-time
216 step_size *= time_ratio; time_ratio = 1;
217 last_inflection_sim=sim_time;
218 last_inflection_real=get_real_secs();
219 return TRUE;
221 } else {
222 switch(key->key) {
223 case 'q': shutdown_app();
224 case 's':
225 is_stepping = 1;
226 is_step = 1;
227 return TRUE;
228 case 'x':
229 is_stepping = 0;
230 is_step = 0;
231 last_inflection_sim=sim_time;
232 last_inflection_real=get_real_secs();
233 return TRUE;
234 case 'T':
235 show_time=!show_time;
236 return TRUE;
237 case 'X':
238 is_sim_throttling = !is_sim_throttling;
239 last_inflection_sim=sim_time;
240 last_inflection_real=get_real_secs();
241 return TRUE;
242 case 'l':
243 int len; uint8_t* s = compiler->compile(compiler->last_script,&len);
244 computer->load_script_at_selection(s,len);
245 return TRUE;
249 return FALSE;
252 // A key event may go to any of the top-level objects
253 void dispatch_key_event() {
254 #ifdef WANT_GLUT
255 BOOL handled =
256 app_handle_key(&key) ||
257 computer->handle_key(&key) ||
258 vis->handle_key(&key) || // visualizer always there for GLUT events
259 compiler->handle_key(&key) ||
260 (motelink!=NULL && motelink->handle_key(&key));
261 if(handled) glutPostRedisplay();
262 #endif // WANT_GLUT
265 // Resizes the window, then tells the viewer to restart itself.
266 #ifdef WANT_GLUT
267 void resize (int new_w, int new_h) { vis->resize(new_w,new_h); }
268 #endif // WANT_GLUT
270 // give each top-level object a chance to display itself
271 // Only the computer lives in 3D space: the others all live in 2D window coords
272 void render () {
273 #ifdef WANT_GLUT
274 vis->prepare_frame();
275 vis->view_3D(); // enter the 3D view for the computer
276 computer->visualize();
277 vis->end_3D();
279 // local drawing
280 if(show_time) {
281 char text[100];
282 glPushMatrix(); glPushAttrib(GL_CURRENT_BIT);
283 palette->use_color(TIME_DISPLAY);
284 sprintf(text, "%.2f", sim_time);
285 glTranslatef( -vis->width/2+50, -vis->height/2+50, -0.1);
286 draw_text_justified(TD_BOTTOM, 100, 100, text);
287 glPopAttrib(); glPopMatrix();
289 glPushMatrix(); glPushAttrib(GL_CURRENT_BIT);
290 palette->use_color(FPS_DISPLAY);
291 sprintf(text, "%.2f", fps);
292 glTranslatef( vis->width/2-50, -vis->height/2+50, -0.1);
293 draw_text_justified(TD_BOTTOM, 100, 100, text);
294 glPopAttrib(); glPopMatrix();
296 if(evolution_lagging) {
297 glPushMatrix(); glPushAttrib(GL_CURRENT_BIT);
298 palette->use_color(LAG_WARNING);
299 glTranslatef( 0, -vis->height/2+50, -0.1);
300 draw_text_justified(TD_BOTTOM, 100, 100, "LAG WARNING");
301 glPopAttrib(); glPopMatrix();
303 if(selecting) {
304 glPushMatrix();
305 palette->use_color(DRAG_SELECTION);
306 glTranslatef((drag_anchor[0]+drag_current[0]-vis->width)/2,
307 -(drag_anchor[1]+drag_current[1]-vis->height)/2, -0.05);
308 draw_quad(fabs(drag_anchor[0]-drag_current[0]),
309 fabs(drag_anchor[1]-drag_current[1]));
310 glPopMatrix();
312 // rest of drawing
313 vis->visualize(); compiler->visualize();
314 if(motelink!=NULL) motelink->visualize();
316 vis->complete_frame();
317 #endif // WANT_GLUT
319 // there should be something that blinks when the simulator can't keep up
320 // with the time demands of its throttle [evolution_lagging==TRUE]
323 // Callback for button events
324 // Clicking and dragging will be separated as per the Apple HIG. To whit:
325 // 1. A click is when the button is pressed and released in the same spot
326 // 2. Dragging is when the mouse is moved while the button is down
327 // Multiple-button events are ignored---only the start-state matters
328 void on_mouse_button ( int button, int state, int x, int y ) {
329 #ifdef WANT_GLUT
330 if (state == GLUT_DOWN && mouse.button==-1) { // no concurrent button ops
331 mouse.x=x; mouse.y=y; // note the starting location
332 mouse.shift = glutGetModifiers() & GLUT_ACTIVE_SHIFT;
333 mouse.button = button;
334 mouse.state=0; // assume click until known to be a drag
335 } else if (state == GLUT_UP) { // on button release, reset
336 if(mouse.state==0) {
337 dispatch_mouse_event(); // clicks do not move from start location
338 } else if(mouse.state==2) {
339 mouse.state=3; mouse.x=x; mouse.y=y; // final drag location
340 dispatch_mouse_event(); }
341 mouse.button=-1;
343 #endif // WANT_GLUT
346 // Callback for mouse motion events (button up or down)
347 // Converts a click event into a drag event following significant motion
348 void on_mouse_motion( int x, int y ) {
349 if(mouse.button==-1) { // mouse is up
350 mouse.x=x; mouse.y=y;
351 } else {
352 if(mouse.state==0) { // is it still a click?
353 // mouse location does not change until click becomes drag
354 if(MAX(abs(x-mouse.x),abs(y-mouse.y)) > CLICK_FUZZ) {
355 mouse.state=1;
356 dispatch_mouse_event(); // start point is old position
358 } else if(mouse.state==2) { // it's a live drag
359 mouse.x=x; mouse.y=y;
360 dispatch_mouse_event();
365 // X and Y are mouse locations, and thus ignored
366 void keyboard_handler( unsigned char key_id, int x, int y ) {
367 #ifdef WANT_GLUT
368 key.normal=TRUE; key.key = key_id;
369 key.ctrl = glutGetModifiers() & GLUT_ACTIVE_CTRL;
370 dispatch_key_event();
371 #endif // WANT_GLUT
373 void special_handler( int key_id, int x, int y ) {
374 #ifdef WANT_GLUT
375 key.normal=FALSE; key.special = key_id;
376 key.ctrl = glutGetModifiers() & GLUT_ACTIVE_CTRL;
377 dispatch_key_event();
378 #endif // WANT_GLUT
381 /*****************************************************************************
382 * STARTING AND STOPPING APPLICATION *
383 *****************************************************************************/
384 // destroy in the opposite order from creation
385 void shutdown_app() {
386 if(motelink) delete motelink;
387 #ifdef WANT_GLUT
388 if(vis) delete vis;
389 #endif // WANT_GLUT
390 delete computer;
391 delete compiler;
392 exit(0);
395 #ifndef WANT_GLUT
396 #define DEFAULT_HEADLESS TRUE
397 #else
398 #define DEFAULT_HEADLESS FALSE
399 #endif
401 // handle command-line arguments for top-level application
402 void process_app_args(Args *args) {
403 // should the simulator start paused?
404 is_stepping = args->extract_switch("-step");
405 // maximum time for simulation (useful for headless execution)
406 if(args->extract_switch("-stop-after")) stop_time = args->pop_number();
407 // throttle when told explicitly, or when hooked to real motes
408 if(args->extract_switch("-throttle") || args->find_switch("-motelink")) {
409 is_sim_throttling=true;
410 last_inflection_real=get_real_secs(); // need to know when it starts
412 show_time = args->extract_switch("-T");
413 // set the ratio between simulated and real time
414 if(args->extract_switch("-ratio")) time_ratio = args->pop_number();
415 // minimum amount of time to advance in each simulation step
416 step_size =((args->extract_switch("-s"))?args->pop_number():0.01/time_ratio);
419 int main (int argc, char *argv[]) {
420 post("PROTO v%d (%d OPS)\n (Developed by MIT Space-Time Programming Group 2005-2008)\n", PROTO_VERSION, CORE_CMD_OPS);
421 Args *args = new Args(argc,argv); // set up the arg parser
423 // initialize randomness [JAH: fmod added for OS X bug]
424 unsigned int seed = (unsigned int)
425 (args->extract_switch("-seed") ? args->pop_number()
426 : fmod(get_real_secs()*1000, RAND_MAX));
427 post("Using random seed %d\n", seed);
428 srand(seed);
430 process_app_args(args);
431 compiler = new Compiler(args); // first the compiler
432 compiler->set_platform("sim");
433 computer = new SpatialComputer(args); // then the computer
434 BOOL headless = args->extract_switch("-headless") || DEFAULT_HEADLESS;
435 if(!headless) {
436 vis = new Visualizer(args); // start visualizer
437 vis->set_bounds(computer->volume); // connect to computer
439 // next the forwarder for the motes, if desired
440 if(args->extract_switch("-motelink")) motelink = new MoteLink(args);
441 // load the script
442 int len;
443 if(args->argc==1) {
444 uint8_t* s = compiler->compile("(app)",&len);
445 computer->load_script(s,len);
446 } else {
447 uint8_t* s = compiler->compile(args->argv[args->argc-1],&len);
448 computer->load_script(s,len);
449 if(args->argc>2) {
450 post("WARNING: %d unhandled arguments:",args->argc-2);
451 for(int i=2;i<args->argc;i++) post(" '%s'",args->argv[i-1]);
452 post("\n");
455 // and start!
456 if(headless) {
457 if(stop_time==INFINITY)
458 uerror("Headless runs must set an end time with -stop-after N");
459 while(1) idle();
460 } else {
461 #ifdef WANT_GLUT
462 // set up callbacks for user interface and idle
463 glutMouseFunc(on_mouse_button);
464 glutMotionFunc(on_mouse_motion);
465 glutPassiveMotionFunc(on_mouse_motion);
466 glutDisplayFunc(render);
467 glutReshapeFunc(resize);
468 glutIdleFunc(idle);
469 glutKeyboardFunc(keyboard_handler);
470 glutSpecialFunc(special_handler);
471 // finally, hand off control to the glut event-loop
472 glutMainLoop();
473 #endif
477 /* Things not yet imported from the 1st generation simulator:
478 Medium parameters:
479 else if (strcmp(arg, "-M") == 0)
480 is_show_medium = 1;
481 else if (strcmp(arg, "-d") == 0)
482 default_sim->is_medium_decay = 1;
483 if (strcmp(arg, "-stalk") == 0)
484 default_sim->is_stalk = 1;
485 else if (strcmp(arg, "-slime") == 0)
486 default_sim->is_slime = 1;
487 else if (strcmp(arg, "-mr") == 0)
488 default_sim->medium_range = atof(check_cmd_key_value(i++, argc, argv));
490 Folding parameters:
491 else if (strcmp(arg, "-regress-every") == 0)
492 default_sim->regress_every = atoi(check_cmd_key_value(i++, argc, argv));
493 else if (strcmp(arg, "-folds") == 0)
494 default_sim->is_folds = 1;
496 Other parameters:
497 } else if (strcmp(arg, "-e") == 0) {
498 int s = 0;
499 Obj *obj;
500 obj = eval_obj(read_object(check_cmd_key_value(i++, argc, argv), &s));
501 check_isa(obj, sim_class);
502 sim = root_sim = (SIM*)obj;
503 } else if (strcmp(arg, "-nc") == 0)
504 default_sim->n_channels = atoi(check_cmd_key_value(i++, argc, argv));
505 else if (strcmp(arg, "-2") == 0) {
506 // MIN_X *= 2;
507 // MAX_X *= 2;
508 is_double_view = 1;
509 } else if (strcmp(arg, "-sf") == 0){
510 load_eval_obj(check_cmd_key_value(i++, argc, argv));
514 Medium keys:
515 case 'Y':
516 sim->is_show_matter = !sim->is_show_matter;
517 break;
518 case 'M':
519 is_show_medium = !is_show_medium;
520 break;
522 Link keys:
523 #ifdef IS_MOTE
524 case 'D': // implemented for sim nodes bug not link yet [11/29]
525 toggle_mote_config (CONFIG_DEBUG, 0);
526 break;
527 case 'F':
528 toggle_mote_config (CONFIG_LED, CLOCK_LED);
529 break;
530 #endif
532 Other/unknown keys:
533 case 'I':
534 is_show_rid = !is_show_rid;
535 break;
536 case 'k':
537 do_sim(root_sim, &toggle_show_txt, NULL, NULL);
538 break;
539 case ' ':
540 case '>':
541 seq_inc = 1;
542 break;
543 case '\b':
544 case '<':
545 seq_inc = -1;
546 break;
547 case '+': val++; break;
548 case '-': val--; break;
549 default: post("Unknown key %c\n", key);