Ok, the new version of the project is prepared — sources in the attachment, help yourselves.
I modified the clock class code so that the game on Unix systems would stop working while waiting for the next frame. The function
FPNanoSleep is responsible for this. On Windows, the game will still eat all the available power of one CPU core to do the delay between frames, because it does not have a similar function. Thanks to the new solution, the project should compile and work at least on
Windows,
Linux,
FreeBSD,
Solaris and
macOS. Unless I have to port something else apart from the clock (though I doubt it). Tests are needed.
I will describe what the new solution is. First of all, we need the units there are few of them:
uses
{$IFDEF WINDOWS}
Windows,
{$ELSE}
BaseUnix, Unix,
{$ENDIF}
SysUtils;
Specifying the number of "ticks" performed by the CPU in one second looks the same as before. On Windows, the value is retrieved using
QueryPerformanceFrequency, and on other systems it is a fixed billion-nanosecond value:
function TClock.GetHardwareCounterFrequency(): Int64;
begin
{$IFDEF WINDOWS}
Result := 0;
QueryPerformanceFrequency(Result);
{$ELSE}
Result := 1000000000;
{$ENDIF}
end;
The current value of the hardware clock is read on Windows using
QueryPerformanceCounter, and on other systems using
FPGetTimeOfDay and converted to the total number of nanoseconds:
function TClock.GetHardwareCounterValue(): Int64;
{$IFDEF UNIX}
var
Counter: TTimeVal;
{$ENDIF}
begin
{$IFDEF WINDOWS}
Result := 0;
QueryPerformanceCounter(Result);
{$ELSE}
FPGetTimeOfDay(@Counter, nil);
Result := Int64(Counter.tv_sec) * 1000000000 + Int64(Counter.tv_usec) * 1000;
{$ENDIF}
end;
The last thing is a method whose task is to stop the program from working and waiting for the next frame. On Windows nothing has changed, and on other platforms the
FPNanoSleep function is used:
procedure TClock.WaitForNMI();
var
{$IFDEF WINDOWS}
NextFrameCounts, CurrentCounts: Int64;
{$ELSE}
SleepTime: Int64;
RequestedTime, RemainingTime: TTimeSpec;
{$ENDIF}
begin
{$IFDEF WINDOWS}
NextFrameCounts := FFrameCountsBegin + FCountsPerFrame;
repeat
CurrentCounts := GetHardwareCounterValue();
until CurrentCounts >= NextFrameCounts;
{$ELSE}
SleepTime := FFrameCountsBegin + FCountsPerFrame - FFrameCountsEnd;
if SleepTime > 0 then
begin
RemainingTime.tv_sec := SleepTime div 1000000000;
RemainingTime.tv_nsec := SleepTime mod 1000000000;
repeat
RequestedTime := RemainingTime;
if FPNanoSleep(@RequestedTime, @RemainingTime) = 0 then Exit;
if FPGetErrNo() <> ESysEINTR then Exit;
until False;
end;
{$ENDIF}
end;
So, probably this is the final version of the clock class code. And as before, if you have the time and willingness, test it and let me know how the game works. Just make a screenshot with in-level gameplay (so that the load counter is visible) and write if there are any problems.
Thank you all for the help.