Skip to content

Timekeeping

Connor Jakubik edited this page Mar 18, 2024 · 11 revisions
Home → Timekeeping

Up to date for Platform 0.20.4.

Written by Connor Jakubik.

SimDynamX internal Feature Track

Timekeeping SimClock Design

The Space Teams Platform has special requirements for timekeeping. During a multiuser simulation, it must be possible to:

  1. Change timescale to any value including zero and negative numbers.
  2. Do a time-jump to any other time point.
  3. Be able to represent time for any point in the relevant future and past.
    • At least 1,000 years to cover past and future highly eccentric solar system object orbits.
  4. Provide threadsafe async access to read the time and write timescale and time-jump clock modifications.
  5. Synchronize clock modifications across a network to for a multiuser sim.

To fulfill these requirements, we implemented an eventual-consistency system which accumulates clock modifications over real-world "wall" time from all clients in the network, then recomputes the sim time from those modifications every time the clock is read. This means, with the assumption that all clients have a synchronized real-world "wall" time, they will have a synchronized sim-time (after clock modification messages have been received through the network).

Synchronization of real-world clocks is not guaranteed, so we have work-in-progress functionality to do our own synchronization through the Platform netcode.

Time standards and variable types

We have chosen the International Atomic Time (TAI) time standard for use in our timekeeping, as this differs from UTC in never having leapseconds. There is a minuscule difference between TAI and Barycentric Dynamical Time (TDB) which is significant for extremely precise planet ephemeris data, so we handle that in our implementation of NAIF SPICE to ensure we are providing millisecond accuracy or better for celestial body positions.

We are using microseconds (1E-6 second) as our unit for timekeeping for time-points and durations. We use microseconds in particular because, with the C++ standard library chrono's default setup, they use long long (int64) to encode durations and time_points. This results in being able to map up to +- 292,279 years, which should be sufficient for our purposes. Nanoseconds constrain us to +- 292 years, which is much more likely to be exceeded in long-term simulations.

There is a marginal increase in precision in dynamics calculations from using nanoseconds for timekeeping instead of microseconds. We have all the infrastructure in place to perform this change with a single line of code, but we can't easily provide both timestamp modes in a single application. In the future, we may provide a nanosecond version of the Platform, which would be incompatible with the microsecond version.

SimGlobals SimClock API

C++

	/**
	 * @brief Retrieves the current simulation time.
	 *
	 * This function returns the current time of the simulation clock
	 * as an object of type `sc_tai_time`.
	 *
	 * @return The current time in `sc_tai_time` format.
	 *
	 * @example Usage
	 * ```
	 * sc_tai_time currentTime = SimGlobals::SimClock_GetTimeNow();
	 * ```
	 */
	static sc_tai_time SimClock_GetTimeNow();

	/**
	 * @brief Retrieves the current time scaling factor of the simulation.
	 *
	 * This function returns the current time scaling factor, which determines how
	 * simulation time relates to real (wall clock) time. A value of 1.0 would mean
	 * they are the same, a value greater than 1 would mean simulation time is
	 * faster than real time. A value of 0 would mean time freezes (but the simulation
	 * functionality still runs). Negative values are allowed as well.
	 *
	 * @return The current time scaling factor as a `double`.
	 *
	 * @example Usage
	 * ```
	 * double timescale = SimGlobals::SimClock_GetTimescale();
	 * ```
	 */
	static double SimClock_GetTimescale();

	/**
	 * @brief Resets the simulation clock to a specified time, and freezes simclock.
	 *
	 * This function resets the simulation clock to the given `setClockTo` time.
	 *
	 * @param setClockTo The time to which the simulation clock should be set, in `sc_tai_time` format.
	 * @param _wallTimeToApply The real-world time this clock modification should be submitted for, in `sc_tai_time` format. The default value is the current wall time (`sc_tai::now()`).
	 * @param retransmit A boolean flag indicating whether to retransmit the new time setting. Default is true.
	 *
	 * @example Usage
	 * ```
	 * sc_tai_time newSimTime = some_function_to_get_new_time();
	 * SimGlobals::SimClock_ResetTo(newSimTime);
	 * // Optionally unfreeze the simclock
	 * SimGlobals::SimClock_SetTimescale(1.0);
	 * // OR
	 * SimGlobals::SimClock_UnFreeze();
	 * ```
	 */
	static void SimClock_ResetTo(sc_tai_time setClockTo, sc_tai_time _wallTimeToApply = sc_tai::now(), bool retransmit = true);

	/**
	 * @brief Freezes the simulation clock.
	 *
	 * This function sets the sim timescale to 0, which halts the counting of 
	 * wall time towards sim time, effectively pausing the simulation.
	 *
	 * @param _wallTimeToApply The wall time this clock modification should be submitted for, in `sc_tai_time` object. The default value is the current wall time (`sc_tai::now()`).
	 * @param retransmit A boolean flag indicating whether or not to retransmit the new time setting. The default value is true.
	 *
	 * @see SimClock_UnFreeze
	 * 
	 * @example Usage
	 * ```
	 * SimGlobals::SimClock_Freeze();
	 * ```
	 */
	static void SimClock_Freeze(sc_tai_time _wallTimeToApply = sc_tai::now(), bool retransmit = true);

	/**
	 * @brief Unfreezes the simulation clock, using the last non-zero timescale.
	 *
	 * If there is no non-zero previous timescale, the new timescale defaults to 1.0.
	 *
	 * @param _wallTimeToApply The wall time this clock modification should be submitted for, in `sc_tai_time` object. The default value is the current wall time (`sc_tai::now()`).
	 * @param retransmit A boolean flag indicating whether or not to retransmit the new time setting. The default value is true.
	 *
	 * @see SimClock_Freeze
	 *
	 * @example Usage
	 * ```
	 * SimGlobals::SimClock_UnFreeze();
	 * ```
	 */
	static void SimClock_UnFreeze(sc_tai_time _wallTimeToApply = sc_tai::now(), bool retransmit = true);

	/**
	 * @brief Set simulation timescale
	 *
	 * This is the ratio of sim time passage per wall time passage.
	 *
	 * @param timescale The new timescale the sim clock will use.
	 * @param _wallTimeToApply The wall time this clock modification should be submitted for, in `sc_tai_time` object. The default value is the current wall time (`sc_tai::now()`).
	 * @param retransmit A boolean flag indicating whether or not to retransmit the new time setting. The default value is true.
	 *
	 * @example Usage
	 * ```
	 * SimGlobals::SimClock_SetTimescale(100.0);
	 * ```
	 */
	static void SimClock_SetTimescale(double timescale, sc_tai_time _wallTimeToApply = sc_tai::now(), bool retransmit = true);

Python

import SC_Compute_Server as sc

# These are wrappers of the corresponding C++ functions above.
sc.SimGlobals_SimClock_Freeze()
sc.SimGlobals_SimClock_UnFreeze()
sc.SimGlobals_SimClock_SetTimescale(timescale : float)
sc.SimGlobals_SimClock_GetTimescale() -> float
sc.SimGlobals_SimClock_GetTimeNow() -> sc.timestamp
# In Platform versions 0.16.0 and lower, ResetTo uses a conversion from 
# a normal Python datetime object for the setClockTo argument, which
# includes leapseconds and may include time zone changes! See below for more details.
# In later Platform versions, the setClockTo argument is a sc.timestamp
sc.SimGlobals_SimClock_ResetTo(setClockTo)

# sc.timestamp class
sc.timestamp.from_datetime(datetime) -> sc.timestamp
sc.timestamp.as_tai_string() -> string
sc.timestamp.as_utc_string() -> string
# You might also get a sc.timestamp from a parameter with "timestamp" type.

C++ Implementation of Python SimGlobals_SimClock_ResetTo in Platform Versions <= 0.16.0

// (In the pybind11 definition of the SC_Compute_Server module)
compute.def("SimGlobals_SimClock_ResetTo", [](date::sys_time<std::chrono::microseconds> setClockTo) {
	sc_tai_time setClockTo_tai = sc_tai::from_sys(std::chrono::time_point_cast<sc_duration>(setClockTo));
	SimGlobals::SimClock_ResetTo(setClockTo_tai, sc_tai::now(), true);
    };