Files
RP412/MUNGA_L4/L4TIME.cpp
T
CydandClaude Opus 5 2bd824e16e The clock counts from launch, not from boot
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>
2026-08-11 10:25:35 -05:00

204 lines
5.4 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4time.h"
//#include <bios.h>
//#include <sos.h>
#include <intrin.h>
#include <windows.h>
//#############################################################################
//########################### System Clock ##############################
//#############################################################################
SystemClock SystemClock::timer;
long SystemClock::ticksPerSecond;
__int64 SystemClock::perfCounterFreq;
__int64 SystemClock::perfCounterOrigin;
//RB 1/20/07
//volatile long fast_time = 0L;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//void outportb(short portid, unsigned char value)
//{
// __asm
// {
// MOV DX, portid
// MOV AL, value
// OUT DX, AL
// }
// __outbyte(portid, value);
//}
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
////
//unsigned char inportb(short portid)
//{
// unsigned char retVal;
// __asm
// {
// MOV DX, portid
// IN AL, DX
// MOV retVal, AL
// }
// return retVal;
//}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
/*TIME RB 1/20/07
void Timer_Handler()
{
++fast_time;
}*/
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//
// Milliseconds since this process started.
//
// It used to be milliseconds since the machine BOOTED, which is what
// QueryPerformanceCounter counts from, and that had two consequences.
//
// The quiet one: Scalar is a 32-bit float, so an absolute time held in one
// loses resolution as the number grows. Consecutive representable values
// are 3.9ms apart after nine hours of uptime, 15.6ms after a day and a
// half, and 31.25ms after three days - by which point the clock can no
// longer resolve a single 20ms physics step. Anything computed by
// subtracting two absolute times IN FLOAT inherits that, and the joystick
// poll interval in L4CTRL is exactly such a subtraction: its 50ms test
// quietly becomes 62.5ms after three days of uptime and 125ms after
// twelve, so a player's controls grow less responsive the longer the
// machine has been switched on. The smoke emitter in L4VIDRND has the same
// defect, where adding a small interval to a large timestamp can round to
// no change at all.
//
// The loud one: this returns a long, and milliseconds since boot overflows
// one after 24.8 days.
//
// Counting from launch fixes both at the source and leaves every Time
// arithmetic path untouched - those subtract ticks as integers and were
// always exact. 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.
//
long SystemClock::GetRTC()
{
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
return (long)(
((count.QuadPart - SystemClock::perfCounterOrigin) * (__int64)1000)
/ SystemClock::perfCounterFreq
);
}
double SystemClock::GetHiRes()
{
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
return (count.QuadPart / (double)SystemClock::perfCounterFreq);
}
__int64 SystemClock::GetHiResTicks()
{
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
return count.QuadPart;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SystemClock::SystemClock()
{
//
//-------------------------------
// Initialize the timer variables
//-------------------------------
//
pauseStart = 0;
pauseTime = 0;
//
//------------------------------
// Initialize the platform timer
//------------------------------
//
// query the performance counter for its frequency
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
//SystemClock::ticksPerSecond = freq.QuadPart;
SystemClock::perfCounterFreq = freq.QuadPart;
SystemClock::ticksPerSecond = 1000L;
//
// Time zero. Set here rather than on the first GetRTC call so that the
// origin is fixed before anything can read the clock, and so no two
// threads can race to establish it.
//
LARGE_INTEGER origin;
QueryPerformanceCounter(&origin);
SystemClock::perfCounterOrigin = origin.QuadPart;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void SystemClock::Shutdown()
{
/*STUBBED: TIME RB 1/20/07
if (timer_handler_handle != 0xFFFF)
{
#if DEBUG_LEVEL>0
WORD err = sosTIMERRemoveEvent(timer_handler_handle);
Verify(err == _ERR_NO_ERROR);
err = sosTIMERUnInitSystem(0);
Verify(err == _ERR_NO_ERROR);
#else
sosTIMERRemoveEvent(timer_handler_handle);
sosTIMERUnInitSystem(0);
#endif
}
*/
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define TIMER_CONTROL 0x43
#define GET_TIMER0 0xC2
#define TIMER0_PORT 0x40
#define OUTPUT_BIT 0x80
#undef inportb
#undef outportb
float Get_Frame_Percent_Used()
{
//STUBBED: SOS RB 1/14/07
///* RB 1/11/07
//outportb(TIMER_CONTROL, GET_TIMER0);
//int half = inportb(TIMER0_PORT) & OUTPUT_BIT;
//int subtick = ((int)inportb(TIMER0_PORT)) >> 1;
//subtick += (((int)inportb(TIMER0_PORT)) << 7);
//*/
//__outbyte(TIMER_CONTROL, GET_TIMER0);
//int half = __inbyte(TIMER0_PORT) & OUTPUT_BIT;
//int subtick = ((int)__inbyte(TIMER0_PORT)) >> 1;
//subtick += (((int)__inbyte(TIMER0_PORT)) << 7);
//if (timer_handler_handle == 0xFFFF)
//{
// subtick += half << 8;
// return (65535 - subtick) / 65536.0f;
//}
//else
//{
// if (half)
// {
// subtick += _wTIMERValue >> 1;
// }
// return (_wTIMERValue - 1 - subtick) / (float)_wTIMERValue;
//}
return 0.0f;
}