refactor game speed to provide a constant frame rate

This commit is contained in:
Tim Felgentreff 2022-08-24 20:21:32 +02:00
parent 3ba7987ffe
commit 15986db050
14 changed files with 80 additions and 99 deletions

View file

@ -1205,21 +1205,20 @@ Sets the resolution of the video display, valid options are 640x480, 800x600, 10
<a name="SetVideoSyncSpeed"></a>
<h3>SetVideoSyncSpeed(speed)</h3>
Sets the video sync speed for the game. 100 is equivilent to 30 frames/second. 200
is equivilent to 60 frames/second.
Sets the video render speed for the game. 30 means 30 frames/second. 60 means 60 frames/second. Default is the native monitor refresh rate.
<dl>
<dt>speed</dt>
<dd>The speed to set the game to as described, 100 is default</dd>
<dd>The speed to set the game to as described, default is the monitor refresh rate.</dd>
<dt><i>RETURNS</i></dt>
<dd>Nothing</dd>
</dl>
<h4>Exmaple</h4>
<h4>Example</h4>
<pre>
-- Set Speed to 60 frames/second
SetVideoSyncSpeed(200)
SetVideoSyncSpeed(60)
</pre>
<a name="ShowEnergySelectedOnly"></a>

View file

@ -2105,7 +2105,7 @@ void EditorMainLoop()
bool start = true;
while (Editor.Running) {
if (FrameCounter % FRAMES_PER_SECOND == 0) {
if (FrameCounter % CYCLES_PER_SECOND == 0) {
if (UpdateMinimap) {
UI.Minimap.Update();
UpdateMinimap = false;

View file

@ -635,14 +635,14 @@ bool GetGamePaused()
}
/**
** Set the game speed
** Set the game speed in range 0 .. 100
**
** @param speed New game speed.
*/
void SetGameSpeed(int speed)
{
if (GameCycle == 0 || FastForwardCycle < GameCycle) {
VideoSyncSpeed = speed * 100 / CYCLES_PER_SECOND;
CyclesPerSecond = (static_cast<double>(speed) / 100) * CYCLES_PER_SECOND + static_cast<double>(CYCLES_PER_SECOND) / 3;
SetVideoSync();
}
}
@ -654,7 +654,7 @@ void SetGameSpeed(int speed)
*/
int GetGameSpeed()
{
return CYCLES_PER_SECOND * VideoSyncSpeed / 100;
return ((static_cast<double>(CyclesPerSecond) - static_cast<double>(CYCLES_PER_SECOND) / 3) / CYCLES_PER_SECOND) * 100;
}
/*----------------------------------------------------------------------------

View file

@ -207,8 +207,8 @@ extern bool GamePaused;
extern bool GameObserve;
/// Flag telling if the game is in establishing mode
extern bool GameEstablishing;
/// Flag telling not to advance to the next game cycle
extern char SkipGameCycle;
/// Counter of how many game cycles to skip for each rendered frame
extern double SkipGameCycle;
/// Invincibility cheat
extern bool GodMode;
/// Whether the map is the only thing displayed or not

View file

@ -65,8 +65,6 @@ constexpr unsigned short UpgradeMax = 2048; /// How many upgrades
constexpr unsigned char MAX_RACES = 8;
constexpr unsigned char PlayerNumNeutral = PlayerMax - 1; /// this is the neutral player slot
/// Frames per second to display (original 30-40)
constexpr unsigned char FRAMES_PER_SECOND = 30; // 1/30s
/// Game cycles per second to simulate (original 30-40)
constexpr unsigned char CYCLES_PER_SECOND = 30; // 1/30s 0.33ms

View file

@ -405,15 +405,12 @@ public:
extern CVideo Video;
/**
** Video synchronization speed. Synchronization time in percent.
** If =0, video framerate is not synchronized. 100 is exact
** CYCLES_PER_SECOND (30). Game will try to redraw screen within
** intervals of VideoSyncSpeed, not more, not less.
** Target CyclesPerSecond that are simulated. The default is CYCLES_PER_SECOND.
** @see CYCLES_PER_SECOND
*/
extern int VideoSyncSpeed;
extern int CyclesPerSecond;
extern int SkipFrames;
extern double SkipCycles;
/// Fullscreen or windowed set from commandline.
extern char VideoForceFullScreen;
@ -421,6 +418,9 @@ extern char VideoForceFullScreen;
/// Next frame ticks
extern double NextFrameTicks;
/// Target refresh rate for renderer
extern int RefreshRate;
/// Counts frames
extern unsigned long FrameCounter;

View file

@ -370,7 +370,7 @@ void CMinimap::Update()
{
static int red_phase;
int red_phase_changed = red_phase != (int)((FrameCounter / FRAMES_PER_SECOND) & 1);
int red_phase_changed = red_phase != (int)((FrameCounter / CYCLES_PER_SECOND) & 1);
if (red_phase_changed) {
red_phase = !red_phase;
}

View file

@ -1148,11 +1148,10 @@ static void CheckPlayerThatTimeOut(int hostIndex)
if (!lastFrame) {
return;
}
const int framesPerSecond = FRAMES_PER_SECOND * VideoSyncSpeed / 100;
const int secs = (FrameCounter - lastFrame) / framesPerSecond;
const int secs = (FrameCounter - lastFrame) / CyclesPerSecond;
// FIXME: display a menu while we wait
const int timeoutInS = CNetworkParameter::Instance.timeoutInS;
if (3 <= secs && secs < timeoutInS && FrameCounter % framesPerSecond == 0) {
if (3 <= secs && secs < timeoutInS && FrameCounter % CyclesPerSecond == 0) {
SetMessage(_("Waiting for player \"%s\": %d:%02d"), Hosts[hostIndex].PlyName,
(timeoutInS - secs) / 60, (timeoutInS - secs) % 60);
}

View file

@ -110,15 +110,15 @@ void DoScrollArea(int state, bool fast, bool isKeyboard)
vp = UI.SelectedViewport;
if (fast) {
stepx = (int)(speed * vp->MapWidth / 2 * PixelTileSize.x * FRAMES_PER_SECOND / 4);
stepy = (int)(speed * vp->MapHeight / 2 * PixelTileSize.y * FRAMES_PER_SECOND / 4);
stepx = (int)(speed * vp->MapWidth / 2 * PixelTileSize.x * CYCLES_PER_SECOND / 4);
stepy = (int)(speed * vp->MapHeight / 2 * PixelTileSize.y * CYCLES_PER_SECOND / 4);
} else {// dynamic: let these variables increase up to fast..
// FIXME: pixels per second should be configurable
stepx = (int)(speed * PixelTileSize.x * FRAMES_PER_SECOND / 4);
stepy = (int)(speed * PixelTileSize.y * FRAMES_PER_SECOND / 4);
stepx = (int)(speed * PixelTileSize.x * CYCLES_PER_SECOND / 4);
stepy = (int)(speed * PixelTileSize.y * CYCLES_PER_SECOND / 4);
}
if ((state & (ScrollLeft | ScrollRight)) && (state & (ScrollLeft | ScrollRight)) != (ScrollLeft | ScrollRight)) {
stepx = stepx * 100 * 100 / VideoSyncSpeed / FRAMES_PER_SECOND / (SkipFrames + 1);
stepx = stepx * 3;
remx += stepx - (stepx / 100) * 100;
stepx /= 100;
if (remx > 100) {
@ -129,7 +129,7 @@ void DoScrollArea(int state, bool fast, bool isKeyboard)
stepx = 0;
}
if ((state & (ScrollUp | ScrollDown)) && (state & (ScrollUp | ScrollDown)) != (ScrollUp | ScrollDown)) {
stepy = stepy * 100 * 100 / VideoSyncSpeed / FRAMES_PER_SECOND / (SkipFrames + 1);
stepy = stepy * 3;
remy += stepy - (stepy / 100) * 100;
stepy /= 100;
if (remy > 100) {
@ -255,7 +255,7 @@ static void GameLogicLoop()
//
// Game logic part
//
if (!GamePaused && NetworkInSync && !SkipGameCycle) {
if (!GamePaused && NetworkInSync && SkipGameCycle < 1) {
SinglePlayerReplayEachCycle();
++GameCycle;
MultiPlayerReplayEachCycle();
@ -333,11 +333,6 @@ static void GameLogicLoop()
#endif
}
//#define REALVIDEO
#ifdef REALVIDEO
static int RealVideoSyncSpeed;
#endif
static void DisplayLoop()
{
/* update only if viewmode changed */
@ -359,12 +354,6 @@ static void DisplayLoop()
ColorCycle();
#ifdef REALVIDEO
if (FastForwardCycle > GameCycle && RealVideoSyncSpeed != VideoSyncSpeed) {
RealVideoSyncSpeed = VideoSyncSpeed;
VideoSyncSpeed = 3000;
}
#endif
if (FastForwardCycle <= GameCycle || GameCycle <= 10 || !(GameCycle & CallPeriod::cEvery256th)) {
//FIXME: this might be better placed somewhere at front of the
// program, as we now still have a game on the background and
@ -375,11 +364,6 @@ static void DisplayLoop()
UpdateDisplay();
RealizeVideoMemory();
}
#ifdef REALVIDEO
if (FastForwardCycle == GameCycle) {
VideoSyncSpeed = RealVideoSyncSpeed;
}
#endif
}
static void SingleGameLoop()
@ -416,10 +400,6 @@ void GameMainLoop()
CParticleManager::init();
#ifdef REALVIDEO
RealVideoSyncSpeed = VideoSyncSpeed;
#endif
CclCommand("if (GameStarting ~= nil) then GameStarting() end");
long ticks = SDL_GetTicks();
@ -440,11 +420,6 @@ void GameMainLoop()
return;
}
#ifdef REALVIDEO
if (FastForwardCycle > GameCycle) {
VideoSyncSpeed = RealVideoSyncSpeed;
}
#endif
NetworkQuitGame();
EndReplayLog();

View file

@ -590,7 +590,7 @@ void ParseCommandLine(int argc, char **argv, Parameters &parameters)
AiSleepCycles = atoi(optarg);
continue;
case 'S':
VideoSyncSpeed = atoi(optarg);
RefreshRate = atoi(optarg);
continue;
case 'u':
if (!strcmp(optarg, "userhome")) {

View file

@ -84,7 +84,7 @@ bool GameRunning; /// Current running state
bool GamePaused; /// Current pause state
bool GameObserve; /// Observe mode
bool GameEstablishing; /// Game establishing mode
char SkipGameCycle; /// Skip the next game cycle
double SkipGameCycle; /// Skip the next n game cycles
char BigMapMode; /// Show only the map
enum _iface_state_ InterfaceState; /// Current interface state
bool GodMode; /// Invincibility cheat
@ -349,7 +349,7 @@ static void UiIncreaseGameSpeed()
if (FastForwardCycle >= GameCycle) {
return;
}
VideoSyncSpeed += 10;
CyclesPerSecond++;
SetVideoSync();
UI.StatusLine.Set(_("Faster"));
}
@ -362,12 +362,8 @@ static void UiDecreaseGameSpeed()
if (FastForwardCycle >= GameCycle) {
return;
}
if (VideoSyncSpeed <= 10) {
if (VideoSyncSpeed > 1) {
--VideoSyncSpeed;
}
} else {
VideoSyncSpeed -= 10;
if (CyclesPerSecond > 1) {
--CyclesPerSecond;
}
SetVideoSync();
UI.StatusLine.Set(_("Slower"));
@ -381,7 +377,7 @@ static void UiSetDefaultGameSpeed()
if (FastForwardCycle >= GameCycle) {
return;
}
VideoSyncSpeed = 100;
CyclesPerSecond = CYCLES_PER_SECOND;
SetVideoSync();
UI.StatusLine.Set(_("Set default game speed"));
}

View file

@ -1151,7 +1151,7 @@ static void InfoPanel_draw_no_selection()
y += 16;
label.Draw(x, y, _("Cycle:"));
label.Draw(x + 48, y, GameCycle);
label.Draw(x + 110, y, CYCLES_PER_SECOND * VideoSyncSpeed / 100);
label.Draw(x + 110, y, CyclesPerSecond);
y += 20;
std::string nc;

View file

@ -99,7 +99,11 @@ static int NumRects;
static std::map<int, std::string> Key2Str;
static std::map<std::string, int> Str2Key;
double FrameTicks; /// Frame length in ms
/// Frame length in ms
static double FrameTicks;
/// Target refresh rate for renderer
int RefreshRate = 0;
const EventCallback *Callbacks;
@ -116,29 +120,47 @@ uint32_t SDL_CUSTOM_KEY_UP;
-- Sync
----------------------------------------------------------------------------*/
static int GetRefreshRate()
{
if (!RefreshRate) {
int displayCount = SDL_GetNumVideoDisplays();
SDL_DisplayMode mode;
for (int i = 0; i < displayCount; i++) {
SDL_GetDesktopDisplayMode(0, &mode);
if (mode.refresh_rate > RefreshRate) {
RefreshRate = mode.refresh_rate;
}
}
if (!RefreshRate) {
RefreshRate = 60;
}
}
return RefreshRate;
}
/**
** Initialise video sync.
** Calculate the length of video frame and any simulation skips.
**
** @see VideoSyncSpeed @see SkipFrames @see FrameTicks
** @see CyclesPerSecond @see SkipCycles @see SkipFrames @see FrameTicks
*/
void SetVideoSync()
{
double ms;
if (VideoSyncSpeed) {
ms = (1000.0 * 1000.0 / CYCLES_PER_SECOND) / VideoSyncSpeed;
int fps = GetRefreshRate();
int nativeFps = fps;
if (fps < CyclesPerSecond) {
fprintf(stdout, "WARNING: Game speed is faster than monitor refresh rate.\n");
FrameTicks = 1000.0 / CyclesPerSecond;
SDL_GL_SetSwapInterval(0); // disable vsync, so we can run faster than the refresh
} else {
ms = (double)INT_MAX;
FrameTicks = 1000.0 / fps;
if (SDL_GL_SetSwapInterval(-1) < 0) { // try to set adaptive vsync
SDL_GL_SetSwapInterval(1); // if it failed, set vsync
}
}
SkipFrames = ms / 400;
while (SkipFrames && ms / SkipFrames < 200) {
--SkipFrames;
}
ms /= SkipFrames + 1;
SkipCycles = (static_cast<double>(fps) / CyclesPerSecond) - 1;
FrameTicks = ms / 10;
DebugPrint("frames %d - %5.2fms\n" _C_ SkipFrames _C_ ms / 10);
DebugPrint("native fps: %d, render frame skip: %d, game cycle skip: %f\n" _C_ nativeFps _C_ Preference.FrameSkip _C_ SkipCycles);
}
/*----------------------------------------------------------------------------
@ -785,8 +807,10 @@ void WaitEventsOneFrame()
}
handleInput(NULL);
if (!SkipGameCycle--) {
SkipGameCycle = SkipFrames;
if (SkipGameCycle < 0) {
SkipGameCycle += SkipCycles;
} else {
SkipGameCycle--;
}
}
@ -795,21 +819,10 @@ void WaitEventsOneFrame()
*/
static Uint32 LastTick = 0;
static int RefreshRate = 0;
static void RenderBenchmarkOverlay()
{
if (!RefreshRate) {
int displayCount = SDL_GetNumVideoDisplays();
SDL_DisplayMode mode;
for (int i = 0; i < displayCount; i++) {
SDL_GetDesktopDisplayMode(0, &mode);
if (mode.refresh_rate > RefreshRate) {
RefreshRate = mode.refresh_rate;
}
}
}
int RefreshRate = GetRefreshRate();
// show a bar representing fps, where the entire bar is the max refresh rate of attached displays
Uint32 nextTick = SDL_GetTicks();
Uint32 frameTime = nextTick - LastTick;

View file

@ -168,8 +168,8 @@ int ClipY2; /// current clipping bottom right
static std::vector<Clip> Clips;
int VideoSyncSpeed = 100; /// 0 disable interrupts
int SkipFrames; /// Skip this frames
int CyclesPerSecond = CYCLES_PER_SECOND;
double SkipCycles; /// Skip this frames
Uint32 ColorBlack;
Uint32 ColorDarkGreen;
@ -348,9 +348,10 @@ void DeInitVideo()
*/
static int CclSetVideoSyncSpeed(lua_State *l)
{
LuaCheckArgs(l, 1);
VideoSyncSpeed = LuaToNumber(l, 1);
return 0;
LuaCheckArgs(l, 1);
RefreshRate = LuaToNumber(l, 1);
SetVideoSync();
return 0;
}
void VideoCclRegister()