Chasing the tick turned up why its period looked quantised: every interval the trace reported was a multiple of 1/32s, which is the spacing between representable float32 values near 474196 - this machine's uptime in seconds. GetRTC returned QueryPerformanceCounter scaled to milliseconds since BOOT, so (Scalar) Now() was a number near half a million and had lost resolution accordingly. That is not only a measurement problem. Scalar is a 32-bit float, so any absolute time held in one degrades as the number grows: 3.9ms apart after nine hours of uptime, 15.6ms after a day and a half, 31.25ms after three days - past which the clock cannot resolve a single 20ms physics step. Two places subtract absolute times in float and inherit it. L4CTRL polls the joystick when (Scalar)Now() - lastJoystickUpdate exceeds 50ms, and lastJoystickUpdate is a Scalar, so that test becomes 62.5ms after three days of uptime and 125ms after twelve: a player's controls get less responsive the longer their machine has been switched on, with nothing on screen to explain it. The smoke emitter in L4VIDRND compares myLastSmoke plus an interval against now, and once the interval falls under the spacing the addition rounds to no change at all. Separately, GetRTC returns a long, and milliseconds since boot overflow one after 24.8 days. Counting from launch fixes the whole class at the source. Every Time arithmetic path is untouched, because those subtract ticks as integers and were always exact - which is also why the simulation itself was never affected, and why the render fraction measured clean. The origin is taken in Startup rather than on first use, so it is fixed before anything reads the clock and no two threads can race to set it. Peer machines already disagreed about this origin, having booted at different moments, so the network is no worse off; reconciling that is what RP412NETCLOCK does. The fix is self-checking: the trace's interval readings should stop being multiples of 0.03125. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
187 lines
3.9 KiB
C++
187 lines
3.9 KiB
C++
#pragma once
|
|
|
|
#include "scalar.h"
|
|
|
|
class Time;
|
|
|
|
//##########################################################################
|
|
//######################## System Clock ##############################
|
|
//##########################################################################
|
|
|
|
class SystemClock SIGNATURED
|
|
{
|
|
friend class Time;
|
|
|
|
public:
|
|
friend float Get_Frame_Percent_Used();
|
|
friend volatile Time& Now();
|
|
friend double HiResNow();
|
|
friend __int64 HiResNowTicks();
|
|
friend __int64 HiResCounterFreq();
|
|
static void TogglePause();
|
|
|
|
SystemClock();
|
|
~SystemClock() { Shutdown(); }
|
|
static SystemClock timer;
|
|
void Shutdown();
|
|
static long GetTicksPerSecond() { return ticksPerSecond; }
|
|
|
|
protected:
|
|
long pauseStart;
|
|
long pauseTime;
|
|
static long ticksPerSecond;
|
|
static __int64 perfCounterFreq;
|
|
|
|
//
|
|
// The counter reading this process started at, so the clock counts from
|
|
// launch rather than from the machine's boot. See GetRTC.
|
|
//
|
|
static __int64 perfCounterOrigin;
|
|
|
|
static long GetRTC();
|
|
static double GetHiRes();
|
|
static __int64 GetHiResTicks();
|
|
};
|
|
|
|
//##########################################################################
|
|
//############################ Time ##################################
|
|
//##########################################################################
|
|
|
|
class Time
|
|
{
|
|
public:
|
|
long ticks;
|
|
static Time Null;
|
|
|
|
#if defined(USE_SIGNATURE)
|
|
friend int Is_Signature_Bad(const volatile Time *p);
|
|
#endif
|
|
|
|
Time() {}
|
|
// Non-volatile overloads: rvalues (function returns) cannot bind to a
|
|
// volatile reference in standard C++; VC9 allowed it as an extension.
|
|
Time(const Time &t) : ticks(t.ticks) {}
|
|
Time(const volatile Time &t) : ticks(t.ticks) {}
|
|
Time(SystemClock&) : ticks(0) {}
|
|
|
|
Time& operator=(const Time &t)
|
|
{
|
|
Check_Pointer(this);
|
|
Check(&t);
|
|
ticks = t.ticks;
|
|
return *this;
|
|
}
|
|
Time& operator=(const volatile Time &t)
|
|
{
|
|
Check_Pointer(this);
|
|
Check(&t);
|
|
ticks = t.ticks;
|
|
return *this;
|
|
}
|
|
Time& operator=(long t)
|
|
{
|
|
Check_Pointer(this);
|
|
ticks = t;
|
|
return *this;
|
|
}
|
|
Time& operator=(Scalar t)
|
|
{
|
|
Check_Pointer(this);
|
|
ticks = Float_To_Time(t);
|
|
return *this;
|
|
}
|
|
|
|
operator Scalar() const volatile
|
|
{
|
|
Check(this);
|
|
return Time_To_Float(ticks);
|
|
}
|
|
operator long() const volatile
|
|
{
|
|
Check(this);
|
|
return ticks;
|
|
}
|
|
|
|
Scalar operator-(const volatile Time &t) const volatile
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
return Time_To_Float(ticks - t.ticks);
|
|
}
|
|
|
|
Time& operator+=(const volatile Time &t)
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
ticks += t.ticks;
|
|
return *this;
|
|
}
|
|
Time& operator+=(Scalar t)
|
|
{
|
|
Check(this);
|
|
ticks += Float_To_Time(t);
|
|
return *this;
|
|
}
|
|
Time& operator+=(long t)
|
|
{
|
|
Check(this);
|
|
ticks += t;
|
|
return *this;
|
|
}
|
|
Time& operator-=(const volatile Time &t)
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
ticks -= t.ticks;
|
|
return *this;
|
|
}
|
|
Time& operator-=(Scalar t)
|
|
{
|
|
Check(this);
|
|
ticks -= Float_To_Time(t);
|
|
return *this;
|
|
}
|
|
|
|
Logical operator<(const volatile Time &t) const volatile
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
return ticks < t.ticks;
|
|
}
|
|
Logical operator<=(const volatile Time &t) const volatile
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
return ticks <= t.ticks;
|
|
}
|
|
Logical operator>(const volatile Time &t) const volatile
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
return ticks > t.ticks;
|
|
}
|
|
Logical operator>=(const volatile Time &t) const volatile
|
|
{
|
|
Check(this);
|
|
Check(&t);
|
|
return ticks >= t.ticks;
|
|
}
|
|
|
|
friend std::ostream& operator <<(std::ostream& stream, const volatile Time& t)
|
|
{
|
|
Check(&t);
|
|
return stream << t.ticks;
|
|
}
|
|
|
|
Logical TestInstance() const volatile;
|
|
static Logical TestClass();
|
|
|
|
private:
|
|
static long Float_To_Time(Scalar t) { return (long)(t * SystemClock::ticksPerSecond + 0.5f); }
|
|
static Scalar Time_To_Float(long t) { return (Scalar)(t / (float)SystemClock::ticksPerSecond); }
|
|
};
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Time functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
void Convert_From_Ascii(const char *str, Time *time);
|