Files
2025-01-04 00:34:03 +01:00

121 lines
3.9 KiB
JavaScript

/**
* A singleton class {@link game#time} which keeps the official Server and World time stamps.
* Uses a basic implementation of https://www.geeksforgeeks.org/cristians-algorithm/ for synchronization.
*/
class GameTime {
constructor(socket) {
/**
* The most recently synchronized timestamps retrieved from the server.
* @type {{clientTime: number, serverTime: number, worldTime: number}}
*/
this._time = {};
/**
* The average one-way latency across the most recent 5 trips
* @type {number}
*/
this._dt = 0;
/**
* The most recent five synchronization durations
* @type {number[]}
*/
this._dts = [];
// Perform an initial sync
if ( socket ) this.sync(socket);
}
/**
* The amount of time to delay before re-syncing the official server time.
* @type {number}
*/
static SYNC_INTERVAL_MS = 1000 * 60 * 5;
/* -------------------------------------------- */
/* Properties */
/* -------------------------------------------- */
/**
* The current server time based on the last synchronization point and the approximated one-way latency.
* @type {number}
*/
get serverTime() {
const t1 = Date.now();
const dt = t1 - this._time.clientTime;
if ( dt > GameTime.SYNC_INTERVAL_MS ) this.sync();
return this._time.serverTime + dt;
}
/* -------------------------------------------- */
/**
* The current World time based on the last recorded value of the core.time setting
* @type {number}
*/
get worldTime() {
return this._time.worldTime;
}
/* -------------------------------------------- */
/* Methods */
/* -------------------------------------------- */
/**
* Advance the game time by a certain number of seconds
* @param {number} seconds The number of seconds to advance (or rewind if negative) by
* @param {object} [options] Additional options passed to game.settings.set
* @returns {Promise<number>} The new game time
*/
async advance(seconds, options) {
return game.settings.set("core", "time", this.worldTime + seconds, options);
}
/* -------------------------------------------- */
/**
* Synchronize the local client game time with the official time kept by the server
* @param {Socket} socket The connected server Socket instance
* @returns {Promise<GameTime>}
*/
async sync(socket) {
socket = socket ?? game.socket;
// Get the official time from the server
const t0 = Date.now();
const time = await new Promise(resolve => socket.emit("time", resolve));
const t1 = Date.now();
// Adjust for trip duration
if ( this._dts.length >= 5 ) this._dts.unshift();
this._dts.push(t1 - t0);
// Re-compute the average one-way duration
this._dt = Math.round(this._dts.reduce((total, t) => total + t, 0) / (this._dts.length * 2));
// Adjust the server time and return the adjusted time
time.clientTime = t1 - this._dt;
this._time = time;
console.log(`${vtt} | Synchronized official game time in ${this._dt}ms`);
return this;
}
/* -------------------------------------------- */
/* Event Handlers and Callbacks */
/* -------------------------------------------- */
/**
* Handle follow-up actions when the official World time is changed
* @param {number} worldTime The new canonical World time.
* @param {object} options Options passed from the requesting client where the change was made
* @param {string} userId The ID of the User who advanced the time
*/
onUpdateWorldTime(worldTime, options, userId) {
const dt = worldTime - this._time.worldTime;
this._time.worldTime = worldTime;
Hooks.callAll("updateWorldTime", worldTime, dt, options, userId);
if ( CONFIG.debug.time ) console.log(`The world time advanced by ${dt} seconds, and is now ${worldTime}.`);
}
}