diff --git a/code/redalert/ccptr.h b/code/redalert/ccptr.h index 2b5141d..02a8c3d 100644 --- a/code/redalert/ccptr.h +++ b/code/redalert/ccptr.h @@ -49,16 +49,16 @@ class CCPtr CCPtr(NoInitClass const & ) {}; CCPtr(T * ptr); - operator T * (void) const { + __forceinline operator T * (void) const { if (ID == -1) return(NULL); assert(Heap != NULL && ID < Heap->Length()); return((T*) (*Heap)[ID]); } - T & operator * (void) const { + __forceinline T & operator * (void) const { assert(Heap != NULL && ID < Heap->Length()); return(*(T*)(*Heap)[ID]); } - T * operator -> (void) const { + __forceinline T * operator -> (void) const { if (ID == -1) return(NULL); assert(Heap != NULL && ID < Heap->Length()); return((T*) (*Heap)[ID]); diff --git a/code/redalert/conquer.cpp b/code/redalert/conquer.cpp index eeb7080..aded40b 100644 --- a/code/redalert/conquer.cpp +++ b/code/redalert/conquer.cpp @@ -146,6 +146,13 @@ bool bNoMovies = false; KeyNumType g_globalKeyNumType = KN_NONE; int g_globalKeyFlags = 0; +int gameFrame = 0; +int gameTimeResidual = 0; + +float com_engineHz_latched = 90.0f; // Latched version of cvar, updated between map loads +int64_t com_engineHz_numerator = 100LL * 1000LL; +int64_t com_engineHz_denominator = 100LL * 60LL; + /**************************************** ** Function prototypes for this module ** *****************************************/ @@ -2101,60 +2108,110 @@ FacingType KN_To_Facing(int input) return(FACING_NONE); } - -/*********************************************************************************************** - * Sync_Delay -- Forces the game into a 15 FPS rate. * - * * - * This routine will wait until the timer for the current frame has expired before * - * returning. It is called at the end of every game loop in order to force the game loop * - * to run at a fixed rate. * - * * - * INPUT: none * - * * - * OUTPUT: none * - * * - * WARNINGS: This routine will delay an amount of time according to the game speed setting. * - * * - * HISTORY: * - * 01/04/1995 JLB : Created. * - * 03/06/1995 JLB : Fixed. * - *=============================================================================================*/ -static void Sync_Delay(void) +void Main_Delay() { - /* - ** Accumulate the number of 'spare' ticks that are frittered away here. - */ - SpareTicks += FrameTimer; + //-------------------------------------------- + // Determine how many game tics we are going to run, + // now that the previous frame is completely finished. + // + // It is important that any waiting on the GPU be done + // before this, or there will be a bad stuttering when + // dropping frames for performance management. + //-------------------------------------------- - /* - ** Delay until the frame timer expires. This forces the game loop to be regulated to a - ** speed controlled by the game options slider. - */ - while (FrameTimer) { - Color_Cycle(); - Call_Back(); + // input: + // thisFrameTime + // com_noSleep + // com_engineHz + // com_fixedTic + // com_deltaTimeClamp + // IsMultiplayer + // + // in/out state: + // gameFrame + // gameTimeResidual + // lastFrameTime + // syncNextFrame + // + // Output: + // numGameFrames - if (SpecialDialog == SDLG_NONE) { -#ifdef WIN32 - WWMouse->Erase_Mouse(&HidPage, TRUE); -#endif //WIN32 - KeyNumType input = KN_NONE; - int x, y; - Map.Input(input, x, y); - if (input) { - Keyboard_Process(input); + // How many game frames to run + int numGameFrames = 0; + + const int deltaTimeClamp = 50; + + for (;;) { + const int thisFrameTime = Sys_Milliseconds(); + static int lastFrameTime = thisFrameTime; // initialized only the first time + const int deltaMilliseconds = thisFrameTime - lastFrameTime; + lastFrameTime = thisFrameTime; + + // if there was a large gap in time since the last frame, or the frame + // rate is very very low, limit the number of frames we will run + const int clampedDeltaMilliseconds = min(deltaMilliseconds, deltaTimeClamp); + + gameTimeResidual += clampedDeltaMilliseconds; // *timescale.GetFloat(); + + // don't run any frames when paused + //if (pauseGame) { + // gameFrame++; + // gameTimeResidual = 0; + // break; + //} + + // debug cvar to force multiple game tics + //if (com_fixedTic.GetInteger() > 0) { + // numGameFrames = com_fixedTic.GetInteger(); + // gameFrame += numGameFrames; + // gameTimeResidual = 0; + // break; + //} + + //if (syncNextGameFrame) { + // // don't sleep at all + // syncNextGameFrame = false; + // gameFrame++; + // numGameFrames++; + // gameTimeResidual = 0; + // break; + //} + + for (;; ) { + // How much time to wait before running the next frame, + // based on com_engineHz + const int frameDelay = FRAME_TO_MSEC(gameFrame + 1) - FRAME_TO_MSEC(gameFrame); + if (gameTimeResidual < frameDelay) { + break; } - Map.Render(); - } - } - animFrameNum+=0.5f; // This is garbage and needs to be fixed with proper delta time bits!!! - if (!FrameTimer) { - Color_Cycle(); - Call_Back(); + gameTimeResidual -= frameDelay; + gameFrame++; + numGameFrames++; + // if there is enough residual left, we may run additional frames + } + + if (numGameFrames > 0) { + // ready to actually run them + break; + } + + // if we are vsyncing, we always want to run at least one game + // frame and never sleep, which might happen due to scheduling issues + // if we were just looking at real time. + //if (com_noSleep.GetBool()) { + // numGameFrames = 1; + // gameFrame += numGameFrames; + // gameTimeResidual = 0; + // break; + //} + + // not enough time has passed to run a frame, as might happen if + // we don't have vsync on, or the monitor is running at 120hz while + // com_engineHz is 60, so sleep a bit and check again + Sleep(0); } } - /*********************************************************************************************** * Main_Loop -- This is the main game loop (as a single loop). * * * @@ -2483,7 +2540,11 @@ Mono_Set_Cursor(0,0); Scenario_MapScriptFrame(); - Sync_Delay(); + Main_Delay(); + + SpareTicks += FrameTimer; + animFrameNum += 0.5f; // This is garbage and needs to be fixed with proper delta time bits!!! + Call_Back(); return(!GameActive); } diff --git a/code/redalert/function.h b/code/redalert/function.h index d22e0b1..c1fb063 100644 --- a/code/redalert/function.h +++ b/code/redalert/function.h @@ -1207,4 +1207,37 @@ extern int g_globalKeyFlags; extern float animFrameNum; extern bool g_loadingPlague; + +extern float com_engineHz_latched; +extern int64_t com_engineHz_numerator; +extern int64_t com_engineHz_denominator; + +__forceinline float Get_com_engineHz_latched(void) { + return com_engineHz_latched; +} + +__forceinline int64_t Get_com_engineHz_numerator(void) { + return com_engineHz_numerator; +} + +__forceinline int64_t Get_com_engineHz_denominator(void) { + return com_engineHz_denominator; +} + +// Returns the msec the frame starts on +__forceinline int FRAME_TO_MSEC(int64_t frame) { + return (int)((frame * Get_com_engineHz_numerator()) / Get_com_engineHz_denominator()); +} +// Rounds DOWN to the nearest frame +__forceinline int MSEC_TO_FRAME_FLOOR(int msec) { + return (int)((((int64_t)msec * Get_com_engineHz_denominator()) + (Get_com_engineHz_denominator() - 1)) / Get_com_engineHz_numerator()); +} +// Rounds UP to the nearest frame +__forceinline int MSEC_TO_FRAME_CEIL(int msec) { + return (int)((((int64_t)msec * Get_com_engineHz_denominator()) + (Get_com_engineHz_numerator() - 1)) / Get_com_engineHz_numerator()); +} +// Aligns msec so it starts on a frame bondary +__forceinline int MSEC_ALIGN_TO_FRAME(int msec) { + return FRAME_TO_MSEC(MSEC_TO_FRAME_CEIL(msec)); +} #endif \ No newline at end of file