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.
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"
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
45 // obtains the time in seconds (in a system-dependent manner)
48 double get_real_secs () {
49 DWORD tv
= timeGetTime();
50 return (double)(tv
/ 1000.0);
54 double get_real_secs () {
56 gettimeofday(&t
, NULL
);
57 return (double)(t
.tv_sec
+ t
.tv_usec
/ 1000000.0);
61 // evolve all top-level items
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
));
69 if(changed
&& vis
!=NULL
) glutPostRedisplay(); // redraw only if needed
73 // this routine is called either by GLUT or directly. It manages time evolution
75 if(sim_time
>stop_time
) shutdown_app(); // quit when time runs out
76 if(is_step
|| !is_stepping
) {
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
;
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
111 void select_region(flo min_x
, flo min_y
, flo max_x
, flo max_y
) {
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();
122 BOOL selecting
= FALSE
;
123 static double drag_anchor
[3], drag_current
[3];
125 BOOL
app_handle_mouse(MouseEvent
*mouse
) {
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
) {
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
138 } else { // shift left drag selects a region
139 if(mouse
->button
==GLUT_LEFT_BUTTON
) {
140 switch(mouse
->state
) {
141 case 1: // drag start
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;
147 case 2: // drag continue
148 drag_current
[0]=mouse
->x
; drag_current
[1]=mouse
->y
;
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]));
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]);
165 case 2: // drag continue
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
];
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() {
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);
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
) {
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();
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();
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();
223 case 'q': shutdown_app();
231 last_inflection_sim
=sim_time
;
232 last_inflection_real
=get_real_secs();
235 show_time
=!show_time
;
238 is_sim_throttling
= !is_sim_throttling
;
239 last_inflection_sim
=sim_time
;
240 last_inflection_real
=get_real_secs();
243 int len
; uint8_t* s
= compiler
->compile(compiler
->last_script
,&len
);
244 computer
->load_script_at_selection(s
,len
);
252 // A key event may go to any of the top-level objects
253 void dispatch_key_event() {
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();
265 // Resizes the window, then tells the viewer to restart itself.
267 void resize (int new_w
, int new_h
) { vis
->resize(new_w
,new_h
); }
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
274 vis
->prepare_frame();
275 vis
->view_3D(); // enter the 3D view for the computer
276 computer
->visualize();
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();
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]));
313 vis
->visualize(); compiler
->visualize();
314 if(motelink
!=NULL
) motelink
->visualize();
316 vis
->complete_frame();
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
) {
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
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(); }
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
;
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
) {
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
) {
368 key
.normal
=TRUE
; key
.key
= key_id
;
369 key
.ctrl
= glutGetModifiers() & GLUT_ACTIVE_CTRL
;
370 dispatch_key_event();
373 void special_handler( int key_id
, int x
, int y
) {
375 key
.normal
=FALSE
; key
.special
= key_id
;
376 key
.ctrl
= glutGetModifiers() & GLUT_ACTIVE_CTRL
;
377 dispatch_key_event();
381 /*****************************************************************************
382 * STARTING AND STOPPING APPLICATION *
383 *****************************************************************************/
384 // destroy in the opposite order from creation
385 void shutdown_app() {
386 if(motelink
) delete motelink
;
396 #define DEFAULT_HEADLESS TRUE
398 #define DEFAULT_HEADLESS FALSE
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
);
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
;
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
);
444 uint8_t* s
= compiler
->compile("(app)",&len
);
445 computer
->load_script(s
,len
);
447 uint8_t* s
= compiler
->compile(args
->argv
[args
->argc
-1],&len
);
448 computer
->load_script(s
,len
);
450 post("WARNING: %d unhandled arguments:",args
->argc
-2);
451 for(int i
=2;i
<args
->argc
;i
++) post(" '%s'",args
->argv
[i
-1]);
457 if(stop_time
==INFINITY
)
458 uerror("Headless runs must set an end time with -stop-after N");
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
);
469 glutKeyboardFunc(keyboard_handler
);
470 glutSpecialFunc(special_handler
);
471 // finally, hand off control to the glut event-loop
477 /* Things not yet imported from the 1st generation simulator:
479 else if (strcmp(arg, "-M") == 0)
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));
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;
497 } else if (strcmp(arg, "-e") == 0) {
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) {
509 } else if (strcmp(arg, "-sf") == 0){
510 load_eval_obj(check_cmd_key_value(i++, argc, argv));
516 sim->is_show_matter = !sim->is_show_matter;
519 is_show_medium = !is_show_medium;
524 case 'D': // implemented for sim nodes bug not link yet [11/29]
525 toggle_mote_config (CONFIG_DEBUG, 0);
528 toggle_mote_config (CONFIG_LED, CLOCK_LED);
534 is_show_rid = !is_show_rid;
537 do_sim(root_sim, &toggle_show_txt, NULL, NULL);
547 case '+': val++; break;
548 case '-': val--; break;
549 default: post("Unknown key %c\n", key);