// ---------------------------------------------------------------------------- // // // OpenSteer -- Steering Behaviors for Autonomous Characters // // Copyright (c) 2002-2005, Sony Computer Entertainment America // Original author: Craig Reynolds // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // // // ---------------------------------------------------------------------------- // // // discrete time simulation clock for OpenSteerDemo // // Keeps track of real clock time and simulation time. Encapsulates OS's // time API. Can be put in either "as fast as possible" variable time step // mode (where simulation time steps are based on real time elapsed between // updates), or in fixed "target FPS" mode where the simulation steps are // constrained to start on 1/FPS boundaries (e.g. on a 60 hertz video game // console). Also handles the notion of "pausing" simulation time. // // Usage: allocate a clock, set its "paused" or "targetFPS" parameters, // then call updateGlobalSimulationClock before each simulation step. // // 10-04-04 bk: put everything into the OpenSteer namespace // 09-24-02 cwr: major overhaul // 06-26-02 cwr: created // // // ---------------------------------------------------------------------------- #include "OpenSteer/Clock.h" // ---------------------------------------------------------------------------- // XXX This is a bit ad hoc. Need to revisit conditionalization on operating // XXX system. As of 5-5-03, this module knows about Win32 (code thanks to // XXX Leaf Garland and Bruce Mitchener) and Linux/Unix (Craig's original // XXX version). It tests for Xbox and Win32 and assumes Linux/Unix // XXX otherwise. #if defined (_XBOX) #include #elif defined (_WIN32) #include #else #include #endif // ---------------------------------------------------------------------------- // Constructor OpenSteer::Clock::Clock (void) { // default is "real time, variable frame rate" and not paused setFixedFrameRate (0); setPausedState (false); setAnimationMode (false); setVariableFrameRateMode (true); // real "wall clock" time since launch totalRealTime = 0; // time simulation has run totalSimulationTime = 0; // time spent paused totalPausedTime = 0; // sum of (non-realtime driven) advances to simulation time totalAdvanceTime = 0; // interval since last simulation time elapsedSimulationTime = 0; // interval since last clock update time elapsedRealTime = 0; // interval since last clock update, // exclusive of time spent waiting for frame boundary when targetFPS>0 elapsedNonWaitRealTime = 0; // "manually" advance clock by this amount on next update newAdvanceTime = 0; // "Calendar time" when this clock was first updated #ifdef _WIN32 basePerformanceCounter = 0; // from QueryPerformanceCounter on Windows #else baseRealTimeSec = 0; // from gettimeofday on Linux and Mac OS X baseRealTimeUsec = 0; #endif // clock keeps track of "smoothed" running average of recent frame rates. // When a fixed frame rate is used, a running average of "CPU load" is // kept (aka "non-wait time", the percentage of each frame time (time // step) that the CPU is busy). smoothedFPS = 0; smoothedUsage = 0; } // ---------------------------------------------------------------------------- // update this clock, called once per simulation step ("frame") to: // // track passage of real time // manage passage of simulation time (modified by Paused state) // measure time elapsed between time updates ("frame rate") // optionally: "wait" for next realtime frame boundary void OpenSteer::Clock::update (void) { // keep track of average frame rate and average usage percentage updateSmoothedRegisters (); // wait for next frame time (when targetFPS>0) // XXX should this be at the end of the update function? frameRateSync (); // save previous real time to measure elapsed time const float previousRealTime = totalRealTime; // real "wall clock" time since this application was launched totalRealTime = realTimeSinceFirstClockUpdate (); // time since last clock update elapsedRealTime = totalRealTime - previousRealTime; // accumulate paused time if (paused) totalPausedTime += elapsedRealTime; // save previous simulation time to measure elapsed time const float previousSimulationTime = totalSimulationTime; // update total simulation time if (getAnimationMode ()) { // for "animation mode" use fixed frame time, ignore real time const float frameDuration = 1.0f / getFixedFrameRate (); totalSimulationTime += paused ? newAdvanceTime : frameDuration; if (!paused) newAdvanceTime += frameDuration - elapsedRealTime; } else { // new simulation time is total run time minus time spent paused totalSimulationTime = (totalRealTime + totalAdvanceTime - totalPausedTime); } // update total "manual advance" time totalAdvanceTime += newAdvanceTime; // how much time has elapsed since the last simulation step? elapsedSimulationTime = (paused ? newAdvanceTime : (totalSimulationTime - previousSimulationTime)); // reset advance amount newAdvanceTime = 0; } // ---------------------------------------------------------------------------- // "wait" until next frame time (actually spin around this tight loop) // // // (xxx there are probably a smarter ways to do this (using events or // thread waits (eg usleep)) but they are likely to be unportable. xxx) void OpenSteer::Clock::frameRateSync (void) { // when in real time fixed frame rate mode // (not animation mode and not variable frame rate mode) if ((! getAnimationMode ()) && (! getVariableFrameRateMode ())) { // find next (real time) frame start time const float targetStepSize = 1.0f / getFixedFrameRate (); const float now = realTimeSinceFirstClockUpdate (); const int lastFrameCount = (int) (now / targetStepSize); const float nextFrameTime = (lastFrameCount + 1) * targetStepSize; // record usage ("busy time", "non-wait time") for OpenSteerDemo app elapsedNonWaitRealTime = now - totalRealTime; // wait until next frame time do {} while (realTimeSinceFirstClockUpdate () < nextFrameTime); } } // ---------------------------------------------------------------------------- // force simulation time ahead, ignoring passage of real time. // Used for OpenSteerDemo's "single step forward" and animation mode float OpenSteer::Clock::advanceSimulationTimeOneFrame (void) { // decide on what frame time is (use fixed rate, average for variable rate) const float fps = (getVariableFrameRateMode () ? getSmoothedFPS () : getFixedFrameRate ()); const float frameTime = 1 / fps; // bump advance time advanceSimulationTime (frameTime); // return the time value used (for OpenSteerDemo) return frameTime; } void OpenSteer::Clock::advanceSimulationTime (const float seconds) { if (seconds < 0) { /// @todo - throw? how to handle error conditions? Not by crashing an app! std::cerr << "negative arg to advanceSimulationTime - results will not be valid"; } else newAdvanceTime += seconds; } namespace { // ---------------------------------------------------------------------------- // Returns the number of seconds of real time (represented as a float) since // the clock was first updated. // // XXX Need to revisit conditionalization on operating system. float clockErrorExit (void) { /// @todo - throw? how to handle error conditions? Not by crashing an app! std::cerr << "Problem reading system clock - results will not be valid"; return 0.0f; } } // anonymous namespace float OpenSteer::Clock::realTimeSinceFirstClockUpdate (void) #ifdef _WIN32 { // get time from Windows LONGLONG counter, frequency; bool clockOK = (QueryPerformanceCounter ((LARGE_INTEGER *)&counter) && QueryPerformanceFrequency ((LARGE_INTEGER *)&frequency)); if (!clockOK) return clockErrorExit (); // ensure the base counter is recorded once after launch if (basePerformanceCounter == 0) basePerformanceCounter = counter; // real "wall clock" time since launch const LONGLONG counterDifference = counter - basePerformanceCounter; return ((float) counterDifference) / ((float)frequency); } #else { // get time from Linux (Unix, Mac OS X, ...) timeval t; if (gettimeofday (&t, 0) != 0) return clockErrorExit (); // ensure the base time is recorded once after launch if (baseRealTimeSec == 0) { baseRealTimeSec = t.tv_sec; baseRealTimeUsec = t.tv_usec; } // real "wall clock" time since launch return (( t.tv_sec - baseRealTimeSec) + ((t.tv_usec - baseRealTimeUsec) / 1000000.0f)); } #endif // ----------------------------------------------------------------------------