/* * Copyright (C) 1997-2005, R3vis Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA, or visit http://www.gnu.org/copyleft/lgpl.html. * * Original Contributor: * Wes Bethel, R3vis Corporation, Marin County, California * Additional Contributor(s): * * The OpenRM project is located at http://openrm.sourceforge.net/. */ /* * $Id: rmtime.c,v 1.13 2005/06/06 02:04:29 wes Exp $ * Version: $Name: OpenRM-1-6-0-RC5 $ * $Revision: 1.13 $ * $Log: rmtime.c,v $ * Revision 1.13 2005/06/06 02:04:29 wes * Lots of small additions to clean up compiler warnings. * * Revision 1.12 2005/03/19 17:17:19 wes * Win32 clock init stuff, and deinit upon rmFinish (in response to report * of 8-byte memory leak). * * Revision 1.11 2005/02/19 16:22:50 wes * Distro sync and consolidation. * * Revision 1.10 2005/01/23 17:08:42 wes * Minor tweaks to improve accuracy. * * Revision 1.9 2004/03/18 15:49:56 wes * Fixed buglet in private_rmTimeSpinDelay that resulted in inaccurate * spins for time values greater than, say, tens of ms. * * Revision 1.8 2004/02/23 03:04:01 wes * *** empty log message *** * * Revision 1.7 2004/01/16 16:51:33 wes * (1) Updated copyright line to 2004; (2) changed name of routine from * rmTimeDifferenceMilliseconds() to rmTimeDifferenceMS(); (3) rewrote * the spinlock routine, tested on Win32 & Unix. * * Revision 1.6 2003/12/06 03:26:06 wes * Documentation tweaks to RMtime routines, updates to RMtime routines for * Windows. * * Revision 1.5 2003/12/01 02:13:05 wes * Additions to support constant frame-rate rendering on both Unix and Win32. * * Revision 1.4 2003/11/21 17:07:52 wes * For RM_X platforms, use a spinlock to implement high-resolution sleeps. * * Revision 1.3 2003/11/16 16:20:06 wes * Added routine rmTimeSleep. * * Revision 1.2 2003/11/05 15:24:29 wes * Minor updates. * */ #include #include "rmprivat.h" #ifdef RM_X #include /* unix-side uses gettimeofday() */ #endif #ifdef RM_WIN LARGE_INTEGER *static_clockFrequency=NULL; static double static_usecsPerTick=0.0; #endif static void private_rmTimeSpinDelay(const RMtime *w, RMtime *prev); /* * ---------------------------------------------------- * @Name rmTimeCurrent @pstart RMenum rmTimeCurrent (RMtime *result) @pend @astart RMtime *result - a handle to a caller-supplied RMtime struct. @aend @dstart Time measurement in RM is performed by querying the time with rmTimeCurrent(), by computing time difference with rmTimeDifference(), or computing the elapsed time in milliseconds with rmTimeDifferenceMS(). rmTimeCurrent() will initialize an RMtime object, which is essentially the same as a "struct timeval" on Unix systems, with the current time. The caller supplies a handle to an RMtime object, and it will be set to contain the current time. Time zones are not relevant, as the purpose of RM's timing facilities is limited to taking millisecond-resolution measurements of time. RM_CHILL is returned upon success, or RM_WHACKED upon failure. rmTimeDifference() takes two RMtimeVal objects that have been set with rmTimeCurrent(), computes the difference in time between the two, and places the difference into a third RMtime object. RM_CHILL is returned upon success, or RM_WHACKED upon failure. rmTimeDifferenceMS() takes two RMtime objects that have been set with rmTimeCurrent(), and returns the difference in milliseconds between the two. rmTimeSet() allows you to set the secs/usecs fields of the RMtime object to specific values. rmTimeGet() will tell you what those values are. rmTimeSleep() implements a spinlock high-resolution delay function that will block the caller for the amount of time (secs, usecs) specified in the input RMtime object. rmTimeDecodeMS() will return a double value representing the number of milliseconds represented by the values in the input RMtime object. rmTimeEncodeMS() will accept a double value interpreted as milliseconds and encode that information into an RMtime object. rmTimeNew() and rmTimeDelete() are used to create and destroy RMtime objects, respectively. @dend * ---------------------------------------------------- */ RMenum rmTimeCurrent(RMtime *r) { #ifdef RM_X struct timeval t1; if (RM_ASSERT(r,"rmTimeCurrent error: the input RMtime object is NULL") == RM_WHACKED) return RM_WHACKED; gettimeofday(&t1, NULL); r->sec = t1.tv_sec; r->usec = t1.tv_usec; #else /* assume RM_WIN */ LARGE_INTEGER ticks; long fracSecs; /* * in order to be completely thread safe, we need to protect the following * if block with a mutex to prevent simultaneous access by multiple threads */ if (RM_ASSERT(r,"rmTimeCurrent error: the input RMtime object is NULL") == RM_WHACKED) return RM_WHACKED; if (static_clockFrequency == NULL) { static_clockFrequency = (LARGE_INTEGER *)malloc(sizeof(LARGE_INTEGER)); if (QueryPerformanceFrequency(static_clockFrequency) == 0) { rmWarning(" The Win32 implementation you are using does not provide a high resolution clock. This means that it is not possible to use any of the rmTime*() routines on your implementation of Windows."); return RM_WHACKED; } static_usecsPerTick = 1000000.0/(double)(static_clockFrequency->QuadPart); } if (QueryPerformanceCounter(&ticks) == 0) { rmWarning(" The Win32 implementation you are using does not provide a high resolution clock. This means that it is not possible to use any of the rmTime*() routines on your implementation of Windows."); return RM_WHACKED; } r->sec = ticks.QuadPart / static_clockFrequency->QuadPart; fracSecs = ticks.QuadPart % static_clockFrequency->QuadPart; r->usec = (long)((double)fracSecs * static_usecsPerTick); #endif return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeDifference @pstart RMenum rmTimeDifference (const RMtime *start, const RMtime *end, RMtime *result) @pend @astart const RMtime *start, *end - handles to caller-supplied RMtime structs that contain valid time values. RMtime *result - a handle to a caler-supplied RMtime struct. This value will be computed as the difference between "start" and "end." @aend @dstart Time measurement in RM is performed by querying the time with rmTimeCurrent(), by computing time difference with rmTimeDifference(), or computing the elapsed time in milliseconds with rmTimeDifferenceMS(). rmTimeDifference() takes as input two RMtime objects, start and end, and computes the difference between them. The result is placed into the "result" RMtime object. Limitations: it is assumed that time in "end" is later than the value in "start." If this assumption doesn't hold, then the computed difference may not be accurate. @dend * ---------------------------------------------------- */ RMenum rmTimeDifference(const RMtime *s, const RMtime *e, RMtime *d) { /* compute d = e - s */ long secs, usecs; if ((RM_ASSERT(s,"rmTimeDifference() error: the start RMtime is NULL")==RM_WHACKED) || (RM_ASSERT(e,"rmTimeDifference() error: the end RMtimeVal is NULL")==RM_WHACKED) || (RM_ASSERT(d,"rmTimeDifference() error: the result RMtimeVal is NULL") == RM_WHACKED)) { return RM_WHACKED; } secs = e->sec - s->sec; usecs = e->usec - s->usec; if (usecs < 0) { usecs += 1000000; secs -= 1; } d->sec = secs; d->usec = usecs; return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeDifferenceMS @pstart double rmTimeDifferenceMS (const RMtime *start, const RMtime *end) @pend @astart const RMtime *start, *end - handles to caller-supplied RMtime structs that contain valid time values. @aend @dstart Time measurement in RM is performed by querying the time with rmTimeCurrent(), by computing time differences with rmTimeDifference(), and by reporting the time difference in milliseconds with rmTimeDifferenceMS(). rmTimeDifferenceMS() takes two RMtimeVal objects that have been set with rmTimeCurrent(), computes the difference in time between the two, and returns a double precision value indicating the number of milliseconds difference between the two RMtime objects. It is assumed that the time value in "end" is greater than or equal to the time value in "start." If this assumption does not hold, the computed and returned difference may be inaccurate. Upon success, a non-negative integer is returned. Upon failure, -1 is returned. If the time value in "end" is less than the time value in "start", the returned value is not guaranteed to be accurate. It is the application's responsibility to ensure the timevalue in "end" is greater than or equal to the time value in "start". @dend * ---------------------------------------------------- */ double rmTimeDifferenceMS(const RMtime *s, const RMtime *e) { /* return d = e - s where d is msec */ long secs, usecs; double msecs; if ((RM_ASSERT(s,"rmTimeDifferenceMS() error: the start RMtimeVal is NULL") == RM_WHACKED) || (RM_ASSERT(e, "rmTimeDifferenceMS() error: the end RMtimeVal is NULL") == RM_WHACKED)) return -1; secs = e->sec - s->sec; usecs = e->usec - s->usec; if (usecs < 0) { usecs += 1000000; secs -= 1; } msecs = (double)(secs)*1000.0; msecs += ((double)(usecs) * 0.001); return msecs; } /* * ---------------------------------------------------- * @Name rmTimeNew @pstart RMtime * rmTimeNew(void) @pend @astart No arguments. @aend @dstart rmTimeNew() will create an RMtime object, and return the handle of the new RMtime object to the caller upon success. Upon failure, NULL is returned. No special processing is performed inside rmTimeNew(). It is safe for applications to allocate RMtime objects off the stack using normal variable declaration if so desired. @dend * ---------------------------------------------------- */ RMtime * rmTimeNew(void) { RMtime *r = (RMtime *)malloc(sizeof(RMtime)); if (r == NULL) { rmError("rmTimeNew() malloc failure."); return NULL; } rmTimeSet(r, (long)0, (long)0); return r; } /* * ---------------------------------------------------- * @Name rmTimeDelete @pstart RMenum rmTimeDelete(RMtime *toDelete) @pend @astart RMtime *toDelete - handle to an RMtime object that will be deleted. @aend @dstart This routine will free resources associated with the input RMtime object "toDelete." Upon success, RM_CHILL is returned. Upon failure, RM_WHACKED is returned. @dend * ---------------------------------------------------- */ RMenum rmTimeDelete(RMtime *d) { if (RM_ASSERT(d,"rmTimeDelete() error: the input RMtime is NULL.")) return RM_WHACKED; free((void *)d); return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeSet @pstart RMenum rmTimeSet(RMtime *toModify, long secs, long usecs) @pend @astart long secs, usecs: input arguments specifing number of seconds and microseconds, respectively. @aend @dstart rmTimeSet is used to assign known values to the RMtime object "toModify." The RMtime object contains two fields - seconds and microseconds. When set by rmTimeCurrent(), those values represent the amount of time elapsed since the current epoch. (Note that since we use longs, OpenRM is Y2038 safe!!) Upon success, RM_CHILL is returned. Upon failure, RM_WHACKED is returned. Limitations: OpenRM doesn't perform any sanity checking on the input values for secs/usecs. It is possible to assign garbage to an RMtime object. @dend * ---------------------------------------------------- */ RMenum rmTimeSet(RMtime *r, long secs, long usecs) { if (RM_ASSERT(r,"rmTimeSet() error: the input RMtime is NULL.") == RM_WHACKED) return RM_WHACKED; r->sec = secs; r->usec = usecs; return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeGet @pstart RMenum rmTimeGet(const RMtime *toQuery, long *returnSecs, long *returnUSecs) @pend @astart const RMtime *toQuery - an input RMtime object. long *returnSecs, long *returnUSecs - pointers to longs. @aend @dstart rmTimeGet() will copy the seconds/microseconds fields from the input RMtime object "toQuery" into the memory pointed to by "returnSecs" and "returnUSecs." If you just want to know the number of milliseconds contained in an RMtime representation, use the routine rmTimeDecodsMS(). RM_CHILL is returned upon success, or RM_WHACKED upon failure. @dend * ---------------------------------------------------- */ RMenum rmTimeGet(const RMtime *r, long *returnSecs, long *returnUSecs) { if (RM_ASSERT(r,"rmTimeGet() error: the input RMtime object is NULL.") == RM_WHACKED) return RM_WHACKED; if (returnSecs != NULL) *returnSecs = r->sec; if (returnUSecs != NULL) *returnUSecs = r->usec; return RM_CHILL; } /* this is routine that was used internally for testing. it permits the caller to know how much error there was from a sleep operation. it works OK, but isn't really needed for our purposes. We might add it back into the public API at some point if someone asks for it. */ RMenum rmTimeSleepDrift(const RMtime *tSleep, RMtime *drift) { private_rmTimeSpinDelay(tSleep, drift); return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeSleep @pstart RMenum rmTimeSleep(const RMtime *toSleep) @pend @astart RMtime *toSleep - input RMtime object that specifies the length of the sleep period. @aend @dstart rmTimeSleep() will block execution of the caller for the amount of time specified in the toSleep argument. This routine will block execution for a period of time that is at least as long as the time specified in the toSleep object. In our preliminary testing, we have found the error to be quite small - less than a microsecond on current hardware. Internally, a high-resolution spinlock is used to implement precision naps. There is a large body of knowledge on the subject of problems with precision sleeps. The brute-force solution is to use a spinlock, which is what we do here. The two primary advantages of the spinlock are: (1) they are of a very high resolution, and (2) the time required to wake up from a nap is less likely to be interruped or influenced by the OS doing something else, like checking to see if the printer is awake. Since we use a spinlock, your CPU meter will be pegged at 100% during rmTimeSleep naps. This routine returns RM_CHILL upon success, or RM_WHACKED upon failure. @dend * ---------------------------------------------------- */ RMenum rmTimeSleep(const RMtime *tSleep) { if (RM_ASSERT(tSleep,"rmTimeSleep() error: the input RMtime object is NULL.") == RM_WHACKED) return RM_WHACKED; private_rmTimeSpinDelay(tSleep, NULL); return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeEncodeMS @pstart RMenum rmTimeEncodeMS(RMtime *toModify, double ms) @pend @astart RMtime *toModify - input RMtime object to be modified. double ms - double precision value interpreted as milliseconds. @aend @dstart rmTimeEncodeMS() will take an input double precision value that is interpreted as milliseconds, and encode the number of milliseconds (that may be fractional) into the RMtime object. RM_CHILL is returned upon success, and RM_WHACKED is returned upon failure. @dend * ---------------------------------------------------- */ RMenum rmTimeEncodeMS(RMtime *t, double ms) { long sec, usec; if (RM_ASSERT(t,"rmTimeEncodeMS() error: the input RMtime object is NULL.") == RM_WHACKED) return RM_WHACKED; sec = (long)(ms) / 1000; usec = (long)(ms * 1000.0F) % 1000000; rmTimeSet(t, sec, usec); return RM_CHILL; } /* * ---------------------------------------------------- * @Name rmTimeDecodeMS @pstart RMenum rmTimeDecodeMS(const RMtime *toQuery, double *resultMS) @pend @astart const RMtime *toQuery - an input RMtime object. double *resultMS - a pointer to a double precision value; the results of this routine will be returned in this caller-supplied memory. @aend @dstart rmTimeDecodeMS will compute the number of milliseconds represented by the contents of the input RMtime object toQuery, and return the results into caller supplied memory. RM_CHILL is returned upon success, and RM_WHACKED is returned upon failure. @dend * ---------------------------------------------------- */ RMenum rmTimeDecodeMS(const RMtime *src, double *resultMS) { double r; long sec, usec; if (RM_ASSERT(src,"rmDecodeMS() error: the input RMtime object is NULL.") == RM_WHACKED) return RM_WHACKED; rmTimeGet(src, &sec, &usec); r = (double)(sec * 1000); r += (double)(usec / 1000); *resultMS = r; return RM_CHILL; } /* internal/private - this is the spindelay loop. */ static void private_rmTimeSpinDelay(const RMtime *w, RMtime *drift) { /* * the X (non-Windoze) version of the timer interfaces directly * to gettimeofday(), and uses only integer arithmetic. the * Win32 version uses floating point math. * * Early testing showed that the code in the Win32 section, which is * painfully correct and easy to read, runs more slowly in some * circumstances than the RM_X code that uses gettimeofday() and * integer math. Oh well. * * Testing of the Win32 code shows that the Win32 clock is of * higher resolution and accuracy than that affored by gettimeofday(). * Sad, but true. * * Future work: * 1. strive for even better accuracy * 2. Update the windoze code to use only integer math. */ #ifdef RM_X struct timeval start,now,d; gettimeofday(&start,0); do { gettimeofday(&now,0); d.tv_sec = now.tv_sec - start.tv_sec; d.tv_usec = now.tv_usec - start.tv_usec; if (d.tv_usec < 0) { d.tv_usec += 1000000; d.tv_sec--; } } while((d.tv_secsec) || ((d.tv_sec==w->sec && d.tv_usecusec))); if (drift != NULL) { long usec = d.tv_usec - w->usec; if (usec < 0) rmWarning("private_rmTimeSpinDelay() : usec < 0 \n"); /* usec++; */ /* fudge factor */ usec--; rmTimeSet(drift, 0, usec); } #else RMtime start, now, d; double waitMS; double startMS, nowMS, deltaMS; rmTimeDecodeMS(w,&waitMS); rmTimeCurrent(&start); rmTimeDecodeMS(&start, &startMS); rmTimeCurrent(&now); rmTimeDecodeMS(&now, &nowMS); deltaMS = nowMS - startMS; for (; deltaMS < waitMS ; ) { rmTimeCurrent(&now); rmTimeDecodeMS(&now, &nowMS); deltaMS = nowMS - startMS; } /* * how late are we? * d = now-start, which is the amount of elapsed time * so d-w would the the amount we are over budget. We assume * that d >= w, and we also assume that the seconds component of * d and w are equal. */ if (drift != NULL) { double driftMS = deltaMS - waitMS; rmTimeEncodeMS(drift, driftMS); } #endif } RMenum private_initTimer(void) { #ifdef RM_WIN if (static_clockFrequency == NULL) { static_clockFrequency = (LARGE_INTEGER *)malloc(sizeof(LARGE_INTEGER)); if (QueryPerformanceFrequency(static_clockFrequency) == 0) { rmWarning(" The Win32 implementation you are using does not provide a high resolution clock. This means that it is not possible to use any of the rmTime*() routines on your implementation of Windows."); return RM_WHACKED; } static_usecsPerTick = 1000000.0/(double)(static_clockFrequency->QuadPart); } #endif /* only windows needs a timer init */ return RM_CHILL; } void private_deInitTimer(void) { #ifdef RM_WIN if (static_clockFrequency != NULL) free((void *)static_clockFrequency); #endif } /* EOF */