/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ /* * $Logfile: /Freespace2/code/Io/Timer.cpp $ * $Revision: 2.10 $ * $Date: 2005-04-11 10:15:11 $ * $Author: taylor $ * * Include file for timer stuff * * $Log: not supported by cvs2svn $ * Revision 2.9 2005/04/11 05:50:35 taylor * some limits.h fixes to make GCC happier * revert timer asm change since it doesn't even get used with Linux and couldn't have been the slowdown problem * * Revision 2.8 2005/03/27 08:57:52 taylor * will hopefully fix the slowdown from mission to mission (thanks Alpha0) * * Revision 2.7 2005/03/03 16:18:19 taylor * lockup fixes, and it's Linux friendly too :) * * Revision 2.6 2005/02/04 10:12:30 taylor * merge with Linux/OSX tree - p0204 * * Revision 2.5 2004/07/26 20:47:33 Kazan * remove MCD complete * * Revision 2.4 2004/07/12 16:32:51 Kazan * MCD - define _MCD_CHECK to use memory tracking * * Revision 2.3 2003/03/02 05:46:28 penguin * Added gcc-compatible asm blocks * - penguin * * Revision 2.2 2002/08/01 01:41:06 penguin * The big include file move * * Revision 2.1 2002/07/07 19:55:59 penguin * Back-port to MSVC * * Revision 2.0 2002/06/03 04:02:24 penguin * Warpcore CVS sync * * Revision 1.4 2002/05/24 16:46:47 mharris * Fixed stupid error I made in timer_get_fixed_seconds() * * Revision 1.3 2002/05/17 06:45:53 mharris * More porting tweaks. It links! but segfaults... * * Revision 1.2 2002/05/09 13:50:50 mharris * temporarily ifdef out asm * * Revision 1.1 2002/05/02 18:03:08 mharris * Initial checkin - converted filenames and includes to lower case * * * 4 5/26/99 3:19p Dave * Fixed release build errors. * * 3 5/17/99 6:03p Dave * Added new timing code. Put in code to allow camera speed zooming. * * 2 10/07/98 10:53a Dave * Initial checkin. * * 1 10/07/98 10:49a Dave * * 14 4/13/98 10:16a John * Switched gettime back to timer_get_milliseconds, which is now thread * safe. * * 13 4/13/98 10:11a John * Made timer functions thread safe. Made timer_init be called in all * projects. * * 12 4/13/98 8:09a John * Made timer_get functions thread safe. * * 11 12/02/97 3:56p John * fixed a bug with timestamp_until, created one with it rolling over. * * 10 9/11/97 7:12p Hoffoss * Added more functionality to training sexp handling code. * * 9 8/29/97 4:49p Allender * added mprintf to check for wrap problems with timestamp ticker * * 8 8/28/97 1:38p Allender * from John: changes to timer func to detect early rollever * * 7 7/29/97 5:30p Lawrance * move gettime() from keyboard module to timer module * * 6 7/16/97 4:42p Mike * Make afterburner shake viewer, not ship. * Shake for limited time. * Add timestamp_until() function to timer library. * * 5 2/17/97 5:18p John * Added a bunch of RCS headers to a bunch of old files that don't have * them. * * $NoKeywords: $ */ #ifdef _WIN32 #include #endif #include #include "globalincs/pstypes.h" #include "io/timer.h" #include "graphics/2d.h" #include "globalincs/alphacolors.h" #define THREADED // to use the proper set of macros #include "osapi/osapi.h" // for multi-thread macros #ifndef NDEBUG #define USE_TIMING #endif #if defined _WIN32 static longlong Timer_last_value, Timer_base; static uint Timer_freq=0; #endif static int Timer_inited = 0; static CRITICAL_SECTION Timer_lock; void timer_close() { if ( Timer_inited ) { Timer_inited = 0; DELETE_CRITICAL_SECTION( Timer_lock ); } } void timer_init() { if ( !Timer_inited ) { #if defined _WIN32 LARGE_INTEGER tmp; QueryPerformanceFrequency(&tmp); Assert( tmp.HighPart == 0 ); Timer_freq = tmp.LowPart; QueryPerformanceCounter((LARGE_INTEGER *)&Timer_base); QueryPerformanceCounter((LARGE_INTEGER *)&Timer_last_value); #endif INITIALIZE_CRITICAL_SECTION( Timer_lock ); Timer_inited = 1; atexit(timer_close); } } // Fills Time_now with the ticks since program start #ifdef _WIN32 static void timer_get(LARGE_INTEGER * out) { ENTER_CRITICAL_SECTION( Timer_lock ); longlong time_tmp; longlong Time_now; QueryPerformanceCounter((LARGE_INTEGER *)&time_tmp); if ( time_tmp < Timer_last_value ) { // The clock has rolled! Timer_base = time_tmp; mprintf(( "TIMER ROLLED!\n" )); // Hack: I'm not accounting for the time before roll occured, // since I'm not sure at what value this timer is going to roll at. Time_now = time_tmp; } Time_now = time_tmp - Timer_base; Timer_last_value = time_tmp; out->QuadPart = Time_now; LEAVE_CRITICAL_SECTION( Timer_lock ); } #endif // _WIN32 fix timer_get_fixed_seconds() { if (!Timer_inited) { Int3(); // Make sure you call timer_init before anything that uses timer functions! return 0; } #if defined(_WIN32) int tmp; LARGE_INTEGER temp_large; timer_get(&temp_large); #if defined(_MSC_VER) // Timing in fixed point (16.16) seconds. // Can be used for up to 1000 hours _asm mov edx, temp_large.HighPart _asm mov eax, temp_large.LowPart _asm shld edx, eax, 16 ; Keep 32+11 bits _asm shl eax, 16 // edx:eax = number of 1.19Mhz pulses elapsed. _asm mov ebx, Timer_freq // Make sure we won't divide overflow. Make time wrap at about 9 hours sub_again: _asm sub edx, ebx ; subtract until negative... _asm jns sub_again ; ...to prevent divide overflow... _asm add edx, ebx ; ...then add in to get correct value. _asm div ebx //eax = fixed point seconds elapsed... _asm mov tmp, eax #elif defined(__GNUC__) // Timing in fixed point (16.16) seconds. // Can be used for up to 1000 hours asm( "mov %1, %%edx;" // temp_large.HighPart "mov %2, %%eax;" // temp_large.LowPart "shld $16,%%eax,%%edx;" // Keep 32+11 bits "shl $16,%%eax;" // edx:eax = number of 1.19Mhz pulses elapsed. "mov %3,%%ebx;" // Timer_freq // Make sure we won't divide overflow. Make time wrap at about 9 hours "sub_again:" "sub %%ebx,%%edx;" // subtract until negative... "jns sub_again;" // ...to prevent divide overflow..." "add %%ebx,%%edx;" // ...then add in to get correct value." "div %%ebx;" //eax = fixed point seconds elapsed... "mov %%eax,%0;" // tmp : "=g" (tmp) : "g" (temp_large.HighPart), "g" (temp_large.LowPart), "g" (Timer_freq) : "edx", "eax", "ebx" ); #else #error unknown Win32 compiler #endif return tmp; #elif defined(SCP_UNIX) __extension__ long long a = SDL_GetTicks(); a *= 65536; return (fix)(a / 1000); #else #error unknown architecture/compiler #endif } fix timer_get_fixed_secondsX() { return timer_get_fixed_seconds(); } fix timer_get_approx_seconds() { return timer_get_fixed_seconds(); } int timer_get_milliseconds() { if (!Timer_inited) { Int3(); // Make sure you call timer_init before anything that uses timer functions! return 0; } #if defined(_WIN32) int tmp; LARGE_INTEGER temp_large; timer_get(&temp_large); temp_large.QuadPart *= (longlong)1000; #if defined(_MSC_VER) // Timing in milliseconds. _asm mov edx, temp_large.HighPart _asm mov eax, temp_large.LowPart //_asm shld edx, eax, 16 ; Keep 32+11 bits //_asm shl eax, 16 // edx:eax = number of 1.19Mhz pulses elapsed. _asm mov ebx, Timer_freq // Make sure we won't divide overflow. Make time wrap at about 9 hours sub_again: _asm sub edx, ebx ; subtract until negative... _asm jns sub_again ; ...to prevent divide overflow... _asm add edx, ebx ; ...then add in to get correct value. _asm div ebx //eax = milliseconds elapsed... _asm mov tmp, eax #elif defined(__GNUC__) // Timing in milliseconds. asm( "mov %1,%%edx;\n" // temp_large.HighPart "mov %2,%%eax;\n" // temp_large.LowPart // edx:eax = number of 1.19Mhz pulses elapsed. "mov %3,%%ebx;\n" // Timer_freq // Make sure we won't divide overflow. Make time wrap at about 9 hours "sub_again2:" "sub %%ebx,%%edx;\n" // subtract until negative... "jns sub_again2;\n" // ...to prevent divide overflow... "add %%ebx,%%edx;\n" // ...then add in to get correct value. "div %%ebx;\n" //eax = milliseconds elapsed... "mov %%eax,%0;\n" // tmp : "=g" (tmp) : "g" (temp_large.HighPart), "g" (temp_large.LowPart), "g" (Timer_freq) : "edx", "eax", "ebx"); #else #error unknown Win32 compiler #endif return tmp; #elif defined(SCP_UNIX) return SDL_GetTicks(); #else #error unknown architecture/compiler #endif } int timer_get_microseconds() { if (!Timer_inited) { Int3(); // Make sure you call timer_init before anything that uses timer functions! return 0; } #if defined(_WIN32) int tmp; LARGE_INTEGER temp_large; timer_get(&temp_large); temp_large.QuadPart *= (longlong)1000000; #if defined(_MSC_VER) // Timing in milliseconds. _asm mov edx, temp_large.HighPart _asm mov eax, temp_large.LowPart //_asm shld edx, eax, 16 ; Keep 32+11 bits //_asm shl eax, 16 // edx:eax = number of 1.19Mhz pulses elapsed. _asm mov ebx, Timer_freq // Make sure we won't divide overflow. Make time wrap at about 9 hours sub_again: _asm sub edx, ebx ; subtract until negative... _asm jns sub_again ; ...to prevent divide overflow... _asm add edx, ebx ; ...then add in to get correct value. _asm div ebx //eax = milliseconds elapsed... _asm mov tmp, eax #elif defined(__GNUC__) // Timing in milliseconds. asm( "mov %1,%%edx;\n" // temp_large.HighPart "mov %2,%%eax;\n" // temp_large.LowPart // edx:eax = number of 1.19Mhz pulses elapsed. "mov %3,%%ebx;\n" // Timer_freq // Make sure we won't divide overflow. Make time wrap at about 9 hours "sub_again3:" "sub %%ebx,%%edx;\n" // subtract until negative... "jns sub_again3;\n" // ...to prevent divide overflow... "add %%ebx,%%edx;\n" // ...then add in to get correct value. "div %%ebx;\n" //eax = milliseconds elapsed... "mov %%eax,%0;\n" // tmp : "=g" (tmp) : "g" (temp_large.HighPart), "g" (temp_large.LowPart), "g" (Timer_freq) : "edx", "eax", "ebx"); #else #error unknown Win32 compiler #endif return tmp; #elif defined(SCP_UNIX) return SDL_GetTicks() * 1000; #else #error unknown architecture/compiler #endif } // 0 means invalid, // 1 means always return true // 2 and above actually check the time int timestamp_ticker = 2; void timestamp_reset() { timestamp_ticker = 2; } // Restrict all time values between 0 and MAX_TIME // so we don't have to use UINTs to calculate rollover. // For debugging & testing, you could set this to // something like 1 minute (6000). #define MAX_TIME (INT_MAX/2) void timestamp_inc(float frametime) { timestamp_ticker += (int)(frametime*TIMESTAMP_FREQUENCY); if ( timestamp_ticker > MAX_TIME ) { timestamp_ticker = 2; // Roll! } if (timestamp_ticker < 2 ) { mprintf(("Whoa!!! timestamp_ticker < 2 -- resetting to 2!!!\n")); timestamp_ticker = 2; } } int timestamp(int delta_ms ) { int t2; if (delta_ms < 0 ) return 0; if (delta_ms == 0 ) return 1; t2 = timestamp_ticker + delta_ms; if ( t2 > MAX_TIME ) { // wrap!!! t2 = delta_ms - (MAX_TIME-timestamp_ticker); } if (t2 < 2 ) t2 = 2; // hack?? return t2; } // Returns milliseconds until timestamp will elapse. // Negative value gives milliseconds ago that timestamp elapsed. int timestamp_until(int stamp) { // JAS: FIX // HACK!! This doesn't handle rollover! // (Will it ever happen?) return stamp - timestamp_ticker; /* uint delta; delta = stamp - timestamp_ticker; if (delta > UINT_MAX/2) delta = UINT_MAX - delta + 1; else if (delta < - ( (int) (UINT_MAX/2))) delta = UINT_MAX + delta + 1; return delta; */ } // alternate timestamp functions. The way these work is you call xtimestamp() to get the // current counter value, and then call int timestamp() { return timestamp_ticker; } int timestamp_has_time_elapsed(int stamp, int time) { int t; if (time <= 0) return 1; t = stamp + time; if (t <= timestamp_ticker) return 1; // if we are unlucky enough to have it wrap on us, this will assume time has elapsed. return 0; } // timing functions ------------------------------------------------------------------------------- #define MAX_TIMING_EVENTS 15 // timing struct #ifdef USE_TIMING typedef struct timing { char name[64]; int microseconds_total; int start; int ref_count; } timing; timing Timing_frame; timing Timing_events[MAX_TIMING_EVENTS]; int Timing_event_count = 0; #endif // lookup a timing event int timing_event_lookup(char *event_name) { #ifndef USE_TIMING return -1; #else int idx; // sanity if(event_name == NULL){ return -1; } // look through all events for(idx=0; idx