1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoTimerSensor SoTimerSensor.h Inventor/sensors/SoTimerSensor.h
35   \brief The SoTimerSensor class is a sensor which will trigger at given intervals.
36 
37   \ingroup sensors
38 
39   Use sensors of this class when you want a job repeated at a certain
40   interval, without explicitly needing to reschedule the sensor
41   (i.e. SoTimerSensor automatically re-schedules itself after it has
42   been triggered).
43 
44   SoTimerSensor instances is commonly used to trigger animation
45   updates at a constant framerate.
46 */
47 
48 #include <Inventor/sensors/SoTimerSensor.h>
49 #include <Inventor/SoDB.h>
50 #include <cassert>
51 
52 #if COIN_DEBUG
53 #include <Inventor/errors/SoDebugError.h>
54 #endif // COIN_DEBUG
55 
56 #define DEBUG_TIMERSENSOR_TRACE 0
57 
58 /*!
59   Default constructor. Sets up an interval of 1/30th of a second.
60  */
SoTimerSensor(void)61 SoTimerSensor::SoTimerSensor(void)
62 {
63   this->interval.setValue(1.0f/30.0f);
64   this->setbasetime = FALSE;
65   this->istriggering = FALSE;
66 }
67 
68 /*!
69   Constructor taking as parameters the sensor callback function and
70   the userdata which will be passed the callback.
71 
72   \sa setFunction(), setData()
73  */
SoTimerSensor(SoSensorCB * func,void * data)74 SoTimerSensor::SoTimerSensor(SoSensorCB * func, void * data)
75   : inherited(func, data)
76 {
77   this->interval.setValue(1.0f/30.0f);
78   this->setbasetime = FALSE;
79   this->istriggering = FALSE;
80 }
81 
82 /*!
83   Destructor.
84 */
~SoTimerSensor(void)85 SoTimerSensor::~SoTimerSensor(void)
86 {
87   if (this->isScheduled()) this->unschedule();
88 }
89 
90 /*!
91   Set the \a base trigger time.
92 
93   If you use this method, the trigger times will be on intervals from
94   the given value.
95 
96   Without an explicitly set base time, the next trigger invocation
97   after a trigger has happened will be on the current time plus the
98   interval time.  Note that this will of course cause the timer to
99   drift.
100 
101   \sa getBaseTime()
102  */
103 void
setBaseTime(const SbTime & baseref)104 SoTimerSensor::setBaseTime(const SbTime & baseref)
105 {
106   //FIXME: There is some humbug when setting baseTime to 0. Probably a
107   //floating point precision thing. Investigate. wiesener 20100810
108   this->base = baseref;
109   this->setbasetime = TRUE;
110 }
111 
112 /*!
113   Returns the base trigger time.
114 
115   \sa setBaseTime()
116  */
117 const SbTime &
getBaseTime(void) const118 SoTimerSensor::getBaseTime(void) const
119 {
120   return this->base;
121 }
122 
123 /*!
124   Sets the time interval between each time the sensor triggers.
125 
126   \sa getInterval()
127  */
128 void
setInterval(const SbTime & intervalref)129 SoTimerSensor::setInterval(const SbTime & intervalref)
130 {
131   this->interval = intervalref;
132 }
133 
134 /*!
135   Returns the timer trigger interval.
136 
137   \sa setInterval()
138  */
139 const SbTime &
getInterval(void) const140 SoTimerSensor::getInterval(void) const
141 {
142   return this->interval;
143 }
144 
145 /*!
146   Set new trigger time based on the given schedule time,
147   and schedules the sensor for triggering.
148 */
149 void
reschedule(const SbTime & schedtime)150 SoTimerSensor::reschedule(const SbTime & schedtime)
151 {
152   this->scheduled = FALSE;
153   this->istriggering = FALSE;
154 
155   if (!this->setbasetime) {
156     this->base = schedtime;
157     this->setTriggerTime(this->base + this->interval);
158 #if DEBUG_TIMERSENSOR_TRACE // debug
159     SoDebugError::postInfo("SoTimerSensor::reschedule",
160                            "(setbasetime) base: %lf, new trigger time: %lf",
161                            this->base.getValue(), this->getTriggerTime().getValue());
162 #endif // debug
163   }
164   else {
165     int intervals = (int)((schedtime - this->base)/this->interval) + 1;
166 
167     if ( intervals < 0 )
168       intervals = 0;
169 
170     this->setTriggerTime(this->base + intervals * this->interval);
171 
172 #if DEBUG_TIMERSENSOR_TRACE
173     SoDebugError::postInfo("SoTimerSensor::reschedule",
174                            "base: %lf, new trigger time: %lf",
175                            this->base.getValue(), this->getTriggerTime().getValue());
176 #endif
177   }
178 
179   // don't call this node's schedule as it calls this method
180   inherited::schedule();
181 }
182 
183 /*!
184   Overrides the virtual schedule() method to be able to set up the
185   base time, if this was not done by the user.
186 
187   If no base time was set, base time will then equal the current time.
188 
189   \sa unschedule(), isScheduled()
190  */
191 void
schedule(void)192 SoTimerSensor::schedule(void)
193 {
194 #if DEBUG_TIMERSENSOR_TRACE // debug
195   SoDebugError::postInfo("SoTimerSensor::schedule", "");
196 #endif // debug
197 
198 #if COIN_DEBUG
199   if (this->isScheduled()) {
200     SoDebugError::postWarning("SoTimerSensor::schedule",
201                               "was already scheduled!");
202     return;
203   }
204 #endif // COIN_DEBUG
205 
206   // need to handle the case where the callback has unscheduled
207   // the timer, and then scheduled it again. Since we are
208   // triggering, we can't just reschedule, but need to add
209   // it to SoSensorManager's list of rescheduled timers.
210   if (this->istriggering) {
211     // shouldn't get here if we're scheduled, but test anyway
212     if (!this->scheduled) {
213       SoDB::getSensorManager()->rescheduleTimer(this);
214     }
215   }
216   else {
217     this->reschedule(SbTime::getTimeOfDay());
218   }
219 }
220 
221 /*!
222   Overrides the virtual unschedule() method to handle unschedule()
223   calls during triggering.
224  */
225 void
unschedule(void)226 SoTimerSensor::unschedule(void)
227 {
228 #if DEBUG_TIMERSENSOR_TRACE // debug
229   SoDebugError::postInfo("SoTimerSensor::unschedule", "%p start", this);
230 #endif // debug
231 
232 #if COIN_DEBUG
233   if (!this->isScheduled()) {
234     SoDebugError::postWarning("SoTimerSensor::unschedule",
235                               "%p not scheduled (istriggering=%s)",
236                               this, this->istriggering ? "TRUE" : "FALSE");
237     return;
238   }
239 #endif // COIN_DEBUG
240 
241   if (this->istriggering) {
242     SoDB::getSensorManager()->removeRescheduledTimer(this);
243     this->scheduled = FALSE;
244     this->istriggering = FALSE;
245   }
246   else inherited::unschedule();
247 
248 #if DEBUG_TIMERSENSOR_TRACE // debug
249   SoDebugError::postInfo("SoTimerSensor::unschedule", "%p done", this);
250 #endif // debug
251 }
252 
253 /*!
254   Overides the virtual trigger() method to be able to reschedule
255   ourselves after we've been triggered.
256 */
257 void
trigger(void)258 SoTimerSensor::trigger(void)
259 {
260 #if DEBUG_TIMERSENSOR_TRACE // debug
261   SoDebugError::postInfo("SoTimerSensor::trigger", "%p start", this);
262 #endif // debug
263 
264   this->istriggering = TRUE;
265   // This will cause SoSceneManager to reschedule this timer after
266   // the current queue has been processed.
267   SoDB::getSensorManager()->rescheduleTimer(this);
268 
269   // don't call SoTimerQueueSensor::trigger() as it will clear
270   // the scheduled flag.
271   SoSensor::trigger();
272 
273 #if DEBUG_TIMERSENSOR_TRACE // debug
274   SoDebugError::postInfo("SoTimerSensor::trigger", "%p done", this);
275 #endif // debug
276 }
277