diff --git a/code/dropship.cpp b/code/dropship.cpp index 7325650db..9088a45c4 100644 --- a/code/dropship.cpp +++ b/code/dropship.cpp @@ -30,6 +30,7 @@ #include "dsurface.h" #include "font.h" #include "globals.h" +#include "hostclock.h" #include "house.h" #include "infatype.h" #include "keyboard.h" @@ -247,7 +248,7 @@ struct CrossDissolveEffect FromSurface(from_surface), ToSurface(to_surface), Alpha(0), - StartTime(::timeGetTime()), + StartTime(Host_Milliseconds()), FromBlitter(from_blitter), ToBlitter(to_blitter), WasDrawn(false) @@ -557,7 +558,7 @@ void Dropship_Screen(void) DynamicVectorClass button_fades; - unsigned int money_display_time = ::timeGetTime(); + unsigned int money_display_time = Host_Milliseconds(); unsigned int screen_start_time = money_display_time; int loadout_anim_frame = 0; unsigned int last_input_time = 0; @@ -613,7 +614,7 @@ void Dropship_Screen(void) KeyNumType input = button_list->Input(); if (!recent_click && (input & KN_BUTTON) != 0) { input = (KeyNumType)(input & ~KN_BUTTON); - last_input_time = ::timeGetTime(); + last_input_time = Host_Milliseconds(); if (input <= _cameo_count && selected_count < dropship_count * SLOT_PER_DROPSHIP) { int candidate_index = cameo_top + input - 1; @@ -650,13 +651,13 @@ void Dropship_Screen(void) int light_row = (input - 1) / 2; if (light_frame[light_row] == (unsigned int)-1) { light_frame[light_row] = 0; - light_start[light_row] = ::timeGetTime(); + light_start[light_row] = Host_Milliseconds(); } force_light_redraw = true; for (j = 0; ; ++j) { if (j >= button_fades.Count()) { - ButtonFadeEffect *fade = new ButtonFadeEffect(::timeGetTime(), candidate_index, 127.0f, -1.0f, false); + ButtonFadeEffect *fade = new ButtonFadeEffect(Host_Milliseconds(), candidate_index, 127.0f, -1.0f, false); if (usage_index != -1 && Scen->AllowableUnitCounts[usage_index] >= Scen->AllowableUnitMaximums[usage_index]) { force_info_redraw = true; fade->StopAtLow = true; @@ -709,7 +710,7 @@ void Dropship_Screen(void) if (Scen->AllowableUnitCounts[usage_index] >= Scen->AllowableUnitMaximums[usage_index]) { for (j = 0; j < candidates.Count(); ++j) { if (candidates[j] == selections[remove_index]) { - ButtonFadeEffect *fade = new ButtonFadeEffect(::timeGetTime(), j, 127.0f, -1.0f, false); + ButtonFadeEffect *fade = new ButtonFadeEffect(Host_Milliseconds(), j, 127.0f, -1.0f, false); fade->Direction = 1.0f; fade->Alpha = 63.0f; button_fades.Add(fade); @@ -780,7 +781,7 @@ void Dropship_Screen(void) for (i = 0; i < ARRAY_SIZE(_green_light_ys); ++i) { if (light_frame[i] != (unsigned int)-1) { int frame = light_frame[i]; - int next_frame = (timeGetTime() - light_start[i]) / _light_rate; + int next_frame = (Host_Milliseconds() - light_start[i]) / _light_rate; if (next_frame != frame || force_light_redraw) { int light_y = y + _green_light_ys[i]; int light_x = x + _light_x; @@ -816,14 +817,14 @@ void Dropship_Screen(void) ButtonFadeEffect *effect = button_fades[i]; int alpha; if (effect->Direction < 0.0f) { - alpha = 127 - (_fade_rate * timeGetTime() - _fade_rate * effect->StartTime) / _fade_scale; + alpha = 127 - (_fade_rate * Host_Milliseconds() - _fade_rate * effect->StartTime) / _fade_scale; if (alpha < _fade_low) { alpha = _fade_low; effect->Direction = 1.0f; - effect->StartTime = timeGetTime(); + effect->StartTime = Host_Milliseconds(); } } else { - alpha = (_fade_rate * timeGetTime() - _fade_rate * effect->StartTime) / _fade_scale + _fade_low; + alpha = (_fade_rate * Host_Milliseconds() - _fade_rate * effect->StartTime) / _fade_scale + _fade_low; if (alpha > 127) { alpha = 127; } @@ -867,7 +868,7 @@ void Dropship_Screen(void) --j; } - int alpha = std::min(255ul, (_dissolve_rate * timeGetTime() - _dissolve_rate * effect->StartTime) / _dissolve_scale); + int alpha = std::min(255ul, (_dissolve_rate * Host_Milliseconds() - _dissolve_rate * effect->StartTime) / _dissolve_scale); if (alpha != effect->Alpha || overlap_drawn) { effect->Alpha = alpha; @@ -899,17 +900,17 @@ void Dropship_Screen(void) } } - recent_click = (::timeGetTime() - last_input_time) < _click_delay; + recent_click = (Host_Milliseconds() - last_input_time) < _click_delay; int loadout_count = loadout_shape->Get_Count(); - int next_loadout_frame = ((::timeGetTime() - screen_start_time) / _loadout_rate) % loadout_count; + int next_loadout_frame = ((Host_Milliseconds() - screen_start_time) / _loadout_rate) % loadout_count; if (next_loadout_frame != loadout_anim_frame) { loadout_anim_frame = next_loadout_frame; Draw_Shape(*HiddenSurface, *drawer_dropship, loadout_shape, loadout_anim_frame, Point2D(0, 0), Rect(Point2D(x, y) + Point2D(_loadout_x, _loadout_y), loadout_shape->Get_Width(), loadout_shape->Get_Height()), SHAPE_NORMAL); redraw = true; } - unsigned int now = ::timeGetTime(); + unsigned int now = Host_Milliseconds(); if ((unsigned int)money != money_display) { unsigned int elapsed = now - money_display_time; if (elapsed >= _money_rate) { @@ -933,7 +934,7 @@ void Dropship_Screen(void) } if (pilot_frame >= 0) { - int next_pilot_frame = (::timeGetTime() - pilot_start_time) / _pilot_rate; + int next_pilot_frame = (Host_Milliseconds() - pilot_start_time) / _pilot_rate; if (next_pilot_frame != pilot_frame) { if (next_pilot_frame < pilotlight_shape->Get_Count()) { pilot_frame = next_pilot_frame; @@ -950,7 +951,7 @@ void Dropship_Screen(void) if (pilot_timer == 0) { if (Scen->RandomNumber(0, INT_MAX - 1) / (double)(INT_MAX - 1) < _pilot_chance) { pilot_frame = 0; - pilot_start_time = ::timeGetTime(); + pilot_start_time = Host_Milliseconds(); Draw_Shape(*HiddenSurface, *drawer_dropship, pilotlight_shape, 0, Point2D(0, 0), Rect(x + _pilot_x, y + _pilot_y, pilotlight_shape->Get_Width(), pilotlight_shape->Get_Height()), SHAPE_NORMAL); redraw = true; } diff --git a/code/gametime.cpp b/code/gametime.cpp index a4ff06d15..37298e69c 100644 --- a/code/gametime.cpp +++ b/code/gametime.cpp @@ -37,6 +37,7 @@ #include "gametime.h" +#include "hostclock.h" #include "win.h" //========================================================================== @@ -59,7 +60,7 @@ GameTimeClass Game_Time; *=========================================================================*/ GameTimeClass::GameTimeClass( void ) { - game_start_time = timeGetTime(); + game_start_time = Host_Milliseconds(); } @@ -81,7 +82,7 @@ unsigned int GameTimeClass::Get_Time( void ) unsigned int curr_windows_time; unsigned int game_time; - curr_windows_time = timeGetTime(); + curr_windows_time = Host_Milliseconds(); if ( curr_windows_time <= game_start_time ) { // Handles the case if the windows time wraps while playing the game. game_time = MAX_ULONG - game_start_time + curr_windows_time; diff --git a/code/hostclock.h b/code/hostclock.h new file mode 100644 index 000000000..07ddae781 --- /dev/null +++ b/code/hostclock.h @@ -0,0 +1,24 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include +#include + +// The engine's coarse clock, in milliseconds from a clock that only ever moves forward. +// Every caller measures an interval with it and none depends on where it starts. The +// reading wraps roughly every forty nine days, so compare differences and not the +// readings themselves. +inline uint32_t Host_Milliseconds(void) +{ + return((uint32_t)std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); +} + diff --git a/code/init.cpp b/code/init.cpp index 1e67a2a09..d841c81df 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -113,6 +113,7 @@ #include "gamedlg.h" #include "getcpu.h" #include "globals.h" +#include "hostclock.h" #include "houstype.h" #include "incdec.h" #include "infatype.h" @@ -1888,7 +1889,7 @@ void Init_Random(void) Seed = CustomSeed; } else { CryptRandom.Get(&Seed, sizeof(Seed)); - Seed = GetTickCount(); + Seed = Host_Milliseconds(); //srand(time(NULL)); //Seed = rand(); } diff --git a/code/mainloop.cpp b/code/mainloop.cpp index d0383c2ed..7447bd665 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -36,6 +36,7 @@ #include "fog.h" #include "globals.h" #include "goptions.h" +#include "hostclock.h" #include "ipxmgr.h" #include "language\language.h" #include "logic.h" @@ -227,7 +228,7 @@ bool Main_Loop(void) // // Initialize our AI processing timer // - Session.ProcessTimer = timeGetTime();/// TickCount; + Session.ProcessTimer = Host_Milliseconds();/// TickCount; if (Session.TrapCheckHeap) { Debug_Trap_Check_Heap = true; @@ -346,7 +347,7 @@ bool Main_Loop(void) // // Measure how long it took to process the AI // - Session.ProcessTicks += std::min(1000, (timeGetTime() - Session.ProcessTimer)); // (TickCount - Session.ProcessTimer) + Session.ProcessTicks += std::min(1000, (Host_Milliseconds() - Session.ProcessTimer)); // (TickCount - Session.ProcessTimer) Session.ProcessFrames++; /* diff --git a/code/milsectmr.cpp b/code/milsectmr.cpp index ccc29ab15..b437a9ac3 100644 --- a/code/milsectmr.cpp +++ b/code/milsectmr.cpp @@ -13,6 +13,7 @@ #include "dbgprint.h" #include "getcpu.h" +#include "hostclock.h" #include "mpu.h" #include "win.h" @@ -29,8 +30,9 @@ /// /// Creates the millisecond timer and works out how to drive it. /// This routine will ask the processor for its clock rate so that the cycle counter can be -/// scaled into milliseconds. Machines that will not report a rate fall back to the Windows -/// multimedia timer, whose resolution is raised to one millisecond for the life of the timer. +/// scaled into milliseconds. Machines that will not report a rate fall back to the host +/// clock instead. The resolution raised here no longer sharpens that reading, but the +/// request is process wide and the game's waits are rounded up to whatever is in force. /// MillisecondTimerClass::MillisecondTimerClass(void) { @@ -39,7 +41,10 @@ MillisecondTimerClass::MillisecondTimerClass(void) unsigned int low = Get_CPU_Rate(high); if (low == 0 && high == 0) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeBeginPeriod(PERIOD_RESOLUTION); +#endif } else { double dl = low; @@ -61,7 +66,10 @@ MillisecondTimerClass::MillisecondTimerClass(void) MillisecondTimerClass::~MillisecondTimerClass(void) { if (Frequency != 1.0) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeEndPeriod(PERIOD_RESOLUTION); +#endif } } @@ -70,8 +78,7 @@ MillisecondTimerClass::~MillisecondTimerClass(void) /// Fetches the current time, expressed in milliseconds. /// This routine is used every time the timer is read. The processor's own cycle counter /// supplies the value when the machine is new enough to have one, since it is both cheaper -/// and finer grained than the system timer. Otherwise the Windows multimedia timer is -/// consulted instead. +/// and finer grained than the host clock. Otherwise the host clock is read instead. /// /// Returns with the current time in milliseconds. MillisecondTimerClass::operator double () const @@ -94,5 +101,5 @@ MillisecondTimerClass::operator double () const return(LI_TO_DBL(dh, dl) / Frequency); } - return(timeGetTime()); + return(Host_Milliseconds()); } diff --git a/code/milsectmr.h b/code/milsectmr.h index 3228f85d2..45dc6b0bc 100644 --- a/code/milsectmr.h +++ b/code/milsectmr.h @@ -22,7 +22,7 @@ class MillisecondTimerClass /* * This is the number of processor clock cycles that pass in one millisecond, and * the raw cycle count is divided by it to yield a time. If it is 1.0, then the - * processor would not report its rate and the multimedia timer is read instead. + * processor would not report its rate and the host clock is read instead. */ double Frequency; }; diff --git a/code/mstimer.cpp b/code/mstimer.cpp index fceb0fc3a..02e68c307 100644 --- a/code/mstimer.cpp +++ b/code/mstimer.cpp @@ -11,17 +11,22 @@ #include "mstimer.h" +#include "hostclock.h" #include "win.h" /// -/// Asks Windows for one millisecond timer resolution. -/// This routine is called when the timer is created so that the readings it hands out -/// are fine grained enough for the game to pace itself by. +/// Requests one millisecond timer resolution for as long as this object exists. +/// The reading itself no longer needs it, since hostclock.h answers that from a clock of +/// its own. The request is process wide, though, and every wait the game paces itself with +/// is rounded up to whatever resolution is in force. /// MillisecondSystemTimerClass::MillisecondSystemTimerClass(void) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeBeginPeriod(1); +#endif } @@ -32,7 +37,10 @@ MillisecondSystemTimerClass::MillisecondSystemTimerClass(void) /// MillisecondSystemTimerClass::~MillisecondSystemTimerClass(void) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeEndPeriod(1); +#endif } @@ -41,10 +49,10 @@ MillisecondSystemTimerClass::~MillisecondSystemTimerClass(void) /// This is the sampling routine that the timer templates call whenever they need to /// know how much time has passed. /// -/// Returns with the number of milliseconds elapsed since Windows started. +/// Returns with the host clock's millisecond reading. int MillisecondSystemTimerClass::operator () (void) const { - return(timeGetTime()); + return(Host_Milliseconds()); } @@ -52,8 +60,8 @@ int MillisecondSystemTimerClass::operator () (void) const /// Converts the timer into its current millisecond reading. /// This routine lets the timer object be used wherever a plain time value is expected. /// -/// Returns with the number of milliseconds elapsed since Windows started. +/// Returns with the host clock's millisecond reading. MillisecondSystemTimerClass::operator int (void) const { - return(timeGetTime()); + return(Host_Milliseconds()); } diff --git a/code/session.cpp b/code/session.cpp index da274270b..30afc28ff 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -58,6 +58,7 @@ #include "dbgprint.h" #include "gamedirs.h" // for Search_Files. #include "globals.h" +#include "hostclock.h" #include "ipxmgr.h" #include "language\language.h" #include "msgloop.h" @@ -937,7 +938,7 @@ unsigned int SessionClass::Compute_Unique_ID(void) //------------------------------------------------------------------------ // time(&tm); // id = (unsigned long)tm; - id = timeGetTime(); + id = Host_Milliseconds(); //------------------------------------------------------------------------ // Now add in the free space on the hard drive diff --git a/code/stimer.cpp b/code/stimer.cpp index 9574ee3db..1d4dd13dc 100644 --- a/code/stimer.cpp +++ b/code/stimer.cpp @@ -33,6 +33,7 @@ #include "stimer.h" +#include "hostclock.h" #include "win.h" #ifdef _MSC_VER @@ -48,13 +49,13 @@ /// /// Fetches the current system timer value. /// This routine is the clock source that the timer templates are built upon. It scales -/// the Windows multimedia clock down so that timers tick in game sized units rather -/// than in milliseconds. +/// the host clock down so that timers tick in game sized units rather than in +/// milliseconds. /// /// Returns with the current system time, expressed in timer ticks. int SystemTimerClass::operator () (void) const { - return(timeGetTime()/16); + return(Host_Milliseconds()/16); } @@ -66,5 +67,5 @@ int SystemTimerClass::operator () (void) const /// Returns with the current system time, expressed in timer ticks. SystemTimerClass::operator int (void) const { - return(timeGetTime()/16); + return(Host_Milliseconds()/16); } diff --git a/code/video.cpp b/code/video.cpp index e25f6fa86..5330e4977 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -21,6 +21,7 @@ #include "dsurface.h" #include "globals.h" #include "goptions.h" +#include "hostclock.h" #include "misc.h" #include "surface.h" #include "wincursor.h" @@ -299,7 +300,7 @@ void Video_Present(void) _Presenting = false; _FrameIsDirty = false; - _LastPresentTime = timeGetTime(); + _LastPresentTime = Host_Milliseconds(); } @@ -315,7 +316,7 @@ void Video_Present_If_Dirty(void) return; } - unsigned int now = timeGetTime(); + unsigned int now = Host_Milliseconds(); if ((now - _LastPresentTime) < _PresentInterval) { return; }