iMSTK
Interactive Medical Simulation Toolkit
Docs/SimManager_Modules.md
1 # SimulationManager & Modules
2 
3 ## Usage
4 
5 Central to iMSTK is the SimulationManager and modules. The SimulationManager defines the main processing (game) loop using modules. Modules implement update functions and the SimulationManager manages the update scheduling of modules. Modules are separate from each other and can be thought of as separate logical threads that are scheduled for update by the SimulationManager.
6 
7 The 3 major modules in iMSTK:
8 * **SceneManager**: Manages and updates a scene, this includes all objects in the scene and physics. See more on `Scene` later.
9 * **Viewer**: Manages and updates rendering of a scene.
10 * **DeviceManager**: Read & writes from a device. Such as writing forces, and reading position or orientation.
11 
12 A common setup would look like:
13 
14 ```cpp
15 auto simManager = std::make_shared<SimulationManager>();
16 simManager->addModule(viewer);
17 simManager->addModule(sceneManager);
18 simManager->addModule(hapticManager);
19 simManager->setDesiredDt(0.01);
20 simManager->start(); // async
21 ```
22 
23 iMSTK's SimulationManager provides a substep approach to scheduling renders and scene updates. It updates the scene numerous times per render, varying at a rate to keep up with real time. The desired timestep allows one to vary/trade the amount of render vs scene updates being done. The smallest timstep is ideal for physics whilst maintaining rendering rates suitable for display. If vsync is used, a target visual rate can be achieved whilst SceneManager updates at the rate given.
24 
25 - Example: If it takes 1s to render+scene update and desired_dt=0.1. Ten updates will be done next iteration.
26 
27 <p align="center">
28  <img src="media/pipeline.png" alt="iMSTK sequentially substepping"/>
29 </p>
30 
31 To supply and setup a Viewer and SceneManager the following is often used:
32 ```cpp
33 // Setup a scene manager to advance the scene
34 auto sceneManager = std::make_shared<SceneManager>();
35 sceneManager->setActiveScene(scene);
36 
37 // Setup a viewer to render the scene
38 auto viewer = std::make_shared<VTKViewer>();
39 viewer->setActiveScene(scene);
40 ```
41 
42 Modules are very independent of each other. To use as a simulation backend (such as in Unity, Unreal, or Slicer) one can use:
43 
44 ```cpp
45 auto sceneManager = std::make_shared<SceneManager>();
46 sceneManager->setActiveScene(scene);
47 sceneManager->init();
48 while(running)
49 {
50  // sceneManager->setDt(<time to advance scene by>);
51  sceneManager->update();
52 }
53 ```
54 
55 Even further one can forego the SceneManager entirely:
56 ```cpp
57 auto scene = std::make_shared<Scene>("MyScene");
58 // ... Setup Scene ...
59 scene->initialize();
60 while (running)
61 {
62  scene->advance(<your timestep here>);
63 }
64 ```
65 
66 This only updates the scene. Similarly VTKViewer can operate independently. Or even multiple VTKViewer's for one scene.
67 
68 ## Control
69 
70 As shown one can start the `SimulationManager` using `SimulationManager::start`. This call won't return until it is done.
71 
72 To stop the entire SimulationManager submit a thread safe request to change it:
73 
74 ```cpp
75 driver->requestStatus(ModuleDriverStopped);
76 ```
77 
78 Alternatively stop or pause an individual module:
79 ```cpp
80 sceneManager->pause();
81 sceneManager->resume();
82 ```
83 
84 The thread will continue to run but glance over this particular module.
85 
86 Some simulators need a pause function, many don't ever pause and just run from the start of the app until closing, others implmenent their own concepts of state.
87 
88 ## Initialization
89 
90 When using the `SimulationManager` it may be useful to plugin to callback after everything is initialized. To do this one can connect a function callback to `SimulationManager::starting`.
91 
92 ```cpp
93 connect<Event>(driver, &SimulationManager::starting,
94  [&](Event*)
95  {
96  // .. Do logic ...
97  });
98 ```
99 
100 ## Timestep and Scheduling
101 
102 Consider a particle moving in one direction/dimensions on two separate machines/hardwares with differing processor speeds using the below function.
103 
104 ```cpp
105 void update(double dt)
106 {
107  position += velocity * dt
108 }
109 ```
110 
111 With a constant velocity=5m/s. Consider a slow and fast computer.
112 
113 - Fast Computer: Calls this function 6 times over 10 real seconds.
114 - Slow Computer: Calls this function 3 times over 10 real seconds.
115 
116 The two computers have produced differing displacements of the particle despite the same amount of real time passing.
117 
118 - Fast Computer: Particle moved 6x5=30 meters.
119 - Slow Computer: Particle moved 5x3=15 meters.
120 
121 One naive but possible solution is to use timestep (dt) here. Use dt=1 on the fast computer and dt=2 on the slow computer. Assuming both computers processors were consistent identical displacements are produced. In actuality processors rarely run at deterministic speeds at this fine of a level. Instead, a common heuristic is to use the time the last update took as a prediction for the time the current update will take.
122 
123 ```cpp
124 Timer timer;
125 timer.start();
126 double dt = 0.0;
127 while (running)
128 {
129  update(dt);
130  dt = timer.getElapsedTime(); // Also resets timer
131 }
132 ```
133 
134 This is the basic real time timestep. If the computer slows down there are larger dts. If it speeds up there are smaller dts. Even more computation is introduced, such as rendering after the update. The time that lost is still accounted for. This timestep may make some simulation engineers nervous though. This is for two reasons:
135 1. They do not know what dt will be. Perhaps there is a lot of work done and dt gets quite large to catch up. This could easily cause a simulation to explode on a slow machine, and be fine on a fast one. Min or max dt's might be used. This sort of issues make portability and distribution very difficult.
136 2. The results are always indeterministic. If the simulation is run twice on the same system. It could produce very slightly differing answers.
137 
138 The solution is substepping. It is fairly common. The idea is to "use a fixed timestep but vary the amount of update calls per render".
139 
140 ```cpp
141 Timer timer;
142 timer.start();
143 while(running)
144 {
145  timer.getElapsedTime();
146  N = ... computed via time passed ...
147  for (int i = 0; i < N; i++)
148  {
149  update(FIXED_DT);
150  }
151  render();
152 }
153 ```
154 
155 This, for example, can allow 1000 simulation updates per render. The trick is that N varies with the machine. A faster computer may perform more updates per render. Where a slow computer may perform less. But both computers use the same unchanging timestep. Resulting in portable, non exploding, deterministic code.
156 
157 To compute N setup a time accumulator/bank. Add time as it passes to this accumulator. And dispense it when updates are completed. This keeps up with real time.
158 
159 ```cpp
160 Timer timer;
161 timer.start();
162 double timeBank = 0.0;
163 while(running)
164 {
165  timeBank += timer.getElapsedTime();
166  N = static_cast<int>(timeBank / FIXED_DT); // Floored
167  timeBank -= N * FIXED_DT;
168  for (int i = 0; i < N; i++)
169  {
170  update(FIXED_DT);
171  }
172  render();
173 }
174 ```
175 
176 There will often be a remainder. If 5s have passed and FIXED_DT=2s. There is a 1s remainder. This can cause a problem if the system is running consistently giving a remainder of 1s. Resulting in something like 2 updates, 3 updates, 2 updates, ... so forth. Sometimes this can be noticable, there are a few solutions.
177 1. Don't deal with it at all and hope your updates are small/many enough that the small stutter is not noticable.
178 2. Divide out the remainder time over the N frames. However, this produces a non-determinsitic timestep again.
179 
180 By default iMSTK does 2 favouring smooth fast simulation. However, one can disable division:
181 
182 ```cpp
183 simManager->setUseRemainderTimeDivide(false);
184 ```
185 
186 To set the desired/fixed dt:
187 
188 ```cpp
189 driver->setDesiredDt(0.003);
190 ```
191 
192 A timestep of 0.01 is pretty standard. 0.016 will give 16ms updates for 60fps. If using haptics one may need a fast update rate. 1000hz (dt=0.001) is the gold standard. But looser haptics are possible at ~500hz.
193 
194 ## Note on Real Time Systems
195 
196 iMSTK uses VTK which by default runs event based rendering (events invoke render calls). iMSTK does not use VTKs event based rendering. Event based rendering is more suitable for UI applications to avoid rendering when nothing has changed on screen, saving battery, freeing up the processor for other tasks, allowing better multitasking, also good when render calls aren't consistent.
197 
198 Real-time system scheduling is a large area of research. Most games, consistently push frames and can more easily quantify how much work different subsystems of the game will take. Physics, rendering, input, animation all are updated in a given pipeline that is cyclic executive, touched only once in sequence repetively, providing absolute determinism to when the next one will occur with no scheduling overhead (managing priorities, alternation schemes, no pre-emption other than OS's, etc). All that being said, nothing stops one from putting iMSTK into any event loop (VTKs included).