Game.Simulation.SimulationSystem
Assembly: Assembly-CSharp
Namespace: Game.Simulation
Type: class
Base: GameSystemBase
Summary:
SimulationSystem is the core simulation stepping system for Cities: Skylines 2. It is responsible for advancing the in-game simulation frames, managing loading-time stepping, smoothing the effective simulation speed, honoring user performance preferences (frame rate vs. simulation speed), and coordinating with other systems (UpdateSystem, ToolSystem, PathfindResultSystem and EndFrameBarrier). It also tracks timing and frame indices, serializes frame index for save/load, and uses a small IJob (SimulationEndTimeJob) to stop a Stopwatch on a worker thread when simulation steps complete to measure frame duration. The class handles both editor and game simulation phases and limits step counts based on pending pathfinding results and performance settings.
Fields
public enum PerformancePreference { FrameRate, Balanced, SimulationSpeed }
Defines preferences that bias how simulation steps are throttled relative to elapsed frame time:- FrameRate: favors keeping frame-rate (fewer simulation steps per update).
- Balanced: compromise between framerate and simulation speed.
-
SimulationSpeed: favors simulation speed (more steps when possible).
-
private struct SimulationEndTimeJob : IJob
A small job that holds a GCHandle to a Stopwatch, stops it and frees the handle from a worker thread. Used to measure elapsed simulation time without blocking the main thread. -
public const float PENDING_FRAMES_SPEED_FACTOR = 1f / 48f
Factor used to scale simulation speed when there are pending pathfinding frames to catch up. -
private const int LOADING_COUNT = 1024
Default number of simulated frames to run during a NewGame preload. -
public const string kLoadingTask = "LoadSimulation"
Task name used with TaskManager for reporting loading progress. -
private UpdateSystem m_UpdateSystem
Reference to UpdateSystem used to drive system update phases (PreSimulation, GameSimulation, EditorSimulation, PostSimulation, LoadSimulation). -
private ToolSystem m_ToolSystem
Reference to ToolSystem; used to determine whether editor or game simulation phases should run. -
private PathfindResultSystem m_PathfindResultSystem
Reference to PathfindResultSystem used to inspect pending pathfinding frames and limit simulation stepping accordingly. -
private EndFrameBarrier m_EndFrameBarrier
Reference to EndFrameBarrier used for job dependency (producerHandle) and elapsed time sampling to adapt step counts. -
private float m_Timer
Accumulated simulation time (in seconds) used to determine how many fixed-step simulation frames to run this update. -
private float m_LastSpeed
Stores the previously selectedSpeed to help compute smoothing deltas. -
private float m_SelectedSpeed
Backing field for selectedSpeed property (user-requested simulation speed; setting while loading is ignored). -
private int m_LoadingCount
Remaining load-simulation frames to run during preload. -
private int m_StepCount
Number of simulation steps performed this update that are still being timed by the stopwatch. -
private bool m_IsLoading
True while game is in the initial loading/preload mode; affects selectedSpeed behavior and loadingProgress updates. -
private JobHandle m_WatchDeps
JobHandle returned from scheduling the SimulationEndTimeJob; completed on OnDestroy and before accessing stopwatch results. -
private Stopwatch m_Stopwatch
Stopwatch used to measure simulation execution time across the step loop. It is passed to the job via GCHandle so it can be stopped on a worker thread when the scheduled job completes. -
public uint frameIndex { get; private set; }
Current global simulation frame index (incremented every simulated step). -
private float m_LastPerfSample?
(not present)
Note: only fields present in source are documented above.
Properties
-
public uint frameIndex { get; private set; }
Current simulation frame counter. Incremented for each simulated step. Serialized to saves. -
public float frameTime { get; private set; }
Unconsumed fraction of a simulation frame (in frames, not seconds). Calculated from m_Timer * 60f before stepping. -
public float selectedSpeed { get; private set }
Desired simulation speed set by UI or systems (1.0 = normal, 0 = paused). Setter ignores changes while m_IsLoading is true to avoid changing speed during preload. -
public float smoothSpeed { get; private set; }
Smoothed representation of the perceived simulation speed used for UI and smoothing behavior. Computed each update to reduce abrupt changes. -
public float loadingProgress { get; private set; }
Progress of loading simulation (0..1). When m_IsLoading is true, setter reports progress to TaskManager with task name "LoadSimulation". Getter returns 1.0 when not loading. -
public float frameDuration { get; private set; }
Average wall-clock duration (seconds) per simulation step for the last batch. Computed using Stopwatch elapsed ticks and last step count. -
public PerformancePreference performancePreference { get; set; }
User-configurable preference that biases step limits (FrameRate, Balanced, or SimulationSpeed). Default read from SharedSettings.general.performancePreference when created.
Constructors
public SimulationSystem()
Default constructor. The system uses OnCreate to perform actual initialization. Marked with [Preserve] attribute in source to prevent stripping.
Methods
protected override void OnCreate()
Initializes internal fields and subsystem references:- Sets default selectedSpeed (1.0f).
- Loads performancePreference from SharedSettings (falls back to Balanced).
- Constructs a Stopwatch.
-
Retrieves UpdateSystem, ToolSystem, PathfindResultSystem and EndFrameBarrier from the World. Called when the GameSystem is created.
-
protected override void OnDestroy()
Completes any outstanding job dependencies (m_WatchDeps.Complete()) to ensure the stopwatch job has finished, then calls base.OnDestroy(). -
public void Serialize<TWriter>(TWriter writer) where TWriter : IWriter
Serializes the frameIndex into the provided writer. Only frameIndex is saved. -
public void Deserialize<TReader>(TReader reader) where TReader : IReader
Reads the saved frameIndex and resets frameTime and internal timer to zero. Ensures simulation resumes deterministically. -
public void SetDefaults(Context context)
Sets default runtime values (frameIndex = 0, frameTime = 0, m_Timer = 0). Called when creating a new simulation context. -
protected override void OnGamePreload(Purpose purpose, GameMode mode)
Called during game preload. Sets selectedSpeed = 0, loadingProgress = 0, sets m_LoadingCount to LOADING_COUNT for new games (1024) or 0 otherwise, and marks m_IsLoading true. This triggers UpdateLoadingProgress behavior until loading completes. -
private void UpdateLoadingProgress()
Runs a small loop of simulation steps used during preload: - Calls UpdateSystem phases PreSimulation, then iterates a fixed number of load simulation steps (8), calling UpdateSystem.Update(SystemUpdatePhase.LoadSimulation, frameIndex, i) for each.
- After the load block, PostSimulation is run and m_LoadingCount is decremented.
-
Reports loadingProgress via TaskManager until m_LoadingCount reaches zero. Used only while m_IsLoading is true.
-
protected override void OnUpdate()
Main per-frame logic: - If previous steps were timed (m_StepCount != 0), completes m_WatchDeps, computes frameDuration based on stopwatch and step count, resets stopwatch and step count.
- Handles loading state and transitions: updates loading progress until complete; once done, sets m_IsLoading false and selectedSpeed to either 0 (paused after loading) or 1 based on SharedSettings.gameplay.pausedAfterLoading.
- If selectedSpeed == 0, no steps are taken and smoothSpeed = 0.
- Otherwise, computes an amount of simulation time to accumulate using UnityEngine.Time.deltaTime * selectedSpeed, applies pending pathfind catch-up scaling (PENDING_FRAMES_SPEED_FACTOR) if pathfinding backlog exists, clamps step counts based on pending pathfind frames, and clamps by a maximum per-update step limit (1..8 scaled by selectedSpeed * pending factor).
- Adjusts step count further according to performancePreference and EndFrameBarrier timings to prevent simulation stepping from starving frame-rate or running too long.
- Updates smoothSpeed using exponential smoothing and constraints to reduce jitter in UI.
- Increments frameIndex for each simulated step and invokes UpdateSystem.Update for either EditorSimulation or GameSimulation depending on ToolSystem.actionMode.
- Starts m_Stopwatch and schedules SimulationEndTimeJob using GCHandle.Alloc(m_Stopwatch) and schedules it with m_EndFrameBarrier.producerHandle; stores returned JobHandle in m_WatchDeps.
-
Always runs PreSimulation and PostSimulation phases around stepping. This method contains the core algorithm that maps wall-clock time to fixed simulation steps while respecting pathfind backlog and preferences.
-
SimulationEndTimeJob.Execute()
Stops the Stopwatch referenced by GCHandle and frees the handle when the job runs. This is scheduled to run after the simulation steps (via EndFrameBarrier.producerHandle) so the stopwatch measures the CPU time used by the scheduled simulation jobs/steps when possible.
Usage Example
[Preserve]
protected override void OnCreate()
{
base.OnCreate();
// SimulationSystem handles its own initialization; example usage:
// - Read current frame index
// - Pause simulation after loading by setting selectedSpeed (setter ignored while loading)
var sim = World.GetOrCreateSystemManaged<SimulationSystem>();
uint currentFrame = sim.frameIndex;
float progress = sim.loadingProgress;
// Pause or resume:
sim.selectedSpeed = 0f; // pause (will be ignored if system is still in loading)
}
Notes and tips: - The system enforces a maximum of 8 simulation steps per update (scaled by selectedSpeed and pending frames factor). This is to avoid long stalls in the main thread. - Pathfinding backlog is respected: if PathfindResultSystem reports pendingSimulationFrame, the simulation will limit steps to avoid outrunning pathfinding results. - performancePreference interacts with EndFrameBarrier timing to adapt steps to keep smooth frame-rate (FrameRate preference) or favor catching up simulation (SimulationSpeed). - frameIndex is the only persistent state serialized; other transient timing values are reset on load.