Skip to content

Game.Debug.DebugWatchSystem

Assembly: Game
Namespace: Game.Debug

Type: class

Base: GameSystemBase

Summary: DebugWatchSystem provides an in-game debug-watch framework for Cities: Skylines 2. It scans managed systems for members annotated with DebugWatchValueAttribute and builds UI foldouts that let you view live values, create time history watches, or distribution watches. Watches support sampling at configurable intervals, exporting to JSON via IJsonWritable, and toggling per-member or per-array-item watches. The system also manages enabling/disabling of monitored systems when marked with DebugWatchOnlyAttribute and provides helpers to create typed accessors for various value/container types.


Fields

  • private static readonly string[] colors
    Array of 36 hex color strings used to assign colors to watches when one isn't explicitly provided by attribute. Colors are cycled with a non-trivial step (multiplied by 23) to spread selections.

  • private SimulationSystem m_SimulationSystem
    Reference to the SimulationSystem (used to read frameIndex and determine when to advance watches).

  • private List<ManagedSystemState> m_ManagedSystemStates
    List of systems that are managed by this debug UI (systems that are only enabled when their foldout is opened via DebugWatchOnlyAttribute).

  • private List<Watch> m_Watches
    Active list of Watch instances currently registered (history and distribution watches).

  • private uint m_LastFrameIndex
    Tracks the last processed simulation frame index so watches only advance once per new frame.

  • private bool m_WatchesChanged
    Flag that indicates watches data/state changed and can be used by UI to refresh/export.

Properties

  • public List<Watch> watches { get; }
    Exposes the internal list of active watches. Useful for external inspection, export, or toggling.

  • public bool watchesChanged { get; }
    Read-only flag that reports whether any watch advanced or the watch set changed since last cleared. Use ClearWatchesChanged() to reset.

Constructors

  • public DebugWatchSystem()
    Default constructor. Marked with [Preserve] in OnCreate; the system initializes its collections and obtains SimulationSystem in OnCreate before being used. The constructor itself does no heavy work.

Methods

  • protected override void OnCreate() : System.Void
    Initializes internal lists, finds the SimulationSystem from the World and disables this debug system by default (base.Enabled = false). Called once when the managed world creates the system. Sets up:
  • m_ManagedSystemStates = new List();
  • m_Watches = new List();
  • m_SimulationSystem = base.World.GetOrCreateSystemManaged();
  • base.Enabled = false;

  • protected override void OnUpdate() : System.Void
    Per-frame update: ensures enabled state of managed systems (those annotated with DebugWatchOnlyAttribute) matches whether their foldout is opened or any watch references that system. Advances every registered watch when the SimulationSystem.frameIndex changes, aggregating whether any watch reported a change into m_WatchesChanged.

  • public void ClearWatchesChanged() : System.Void
    Clears the internal m_WatchesChanged flag.

  • public void ClearWatches() : System.Void
    Removes all watches and marks m_WatchesChanged = true.

  • protected override void OnStopRunning() : System.Void
    Called when the system stops running — disables all managed systems that were tracked in m_ManagedSystemStates.

  • public List<DebugUI.Widget> BuildSystemFoldouts() : List<DebugUI.Widget>
    Scans all managed systems in the world, finds members annotated with DebugWatchValueAttribute and constructs a set of DebugUI widgets (foldouts, values, containers, toggles) reflecting available values and watches. Key behaviors:

  • Creates Watch instances for supported member types via Watch.TryCreate.
  • For NativeArray<> members, builds "Values" and "Watches" sub-foldouts and offers per-item watch toggles plus an "All Values" toggle.
  • Assigns colors to watches (uses attribute color if present, otherwise uses colors array).
  • Adds systems with DebugWatchOnlyAttribute to m_ManagedSystemStates (so their Enabled state is controlled by foldout).
  • Returns a sorted list of top-level widgets by display name.

Note: This method contains several small local static helpers used to create Value/ValueContainer/WatchToggle widgets and a NextColor lambda to select colors.

  • private static IEnumerable<MemberInfo> GetWatchValueMembers(Type systemType)
    Helper to reflect over a system type and return properties, fields, and methods that have DebugWatchValueAttribute.

  • public static IValueAccessor CreateTypedAccessor(IValueAccessor accessor)
    Tries to wrap a generic IValueAccessor into a typed/cast accessor (CastAccessor) for common primitive types and into NativeValueAccessor for NativeValue specializations. This allows the Watch factory to create strongly-typed HistoryWatch and DistributionWatch.

  • private static FieldInfo[] GetWatchDepsFields(Type systemType)
    Finds fields on the system type of type JobHandle that are annotated with DebugWatchDepsAttribute. These are used by Value accessor creation to provide dependency handles for accessing values that rely on jobs.

  • private static string GetArrayItemName(MemberInfo member, int index)
    Returns a friendly name for array item indices when building UI:

  • If member has ResourceArrayAttribute -> returns resource name via EconomyUtils.GetResource(index)
  • If member has EnumArrayAttribute -> returns Enum.GetName for the enum type in the attribute
  • Otherwise returns index.ToString()

Additionally, nested types inside DebugWatchSystem:

  • ManagedSystemState (private class)
  • Fields: ComponentSystemBase m_System; DebugUI.Foldout m_Foldout
  • Used to track foldout state and associated system for DebugWatchOnly systems.

  • Watch (public abstract class)

  • Base abstraction for watches. Fields: ComponentSystemBase m_System; string m_DisplayName; string m_Color.
  • Members: abstract Enable(), Disable(), Advance(uint frameIndex) -> bool, abstract Write(IJsonWriter writer).
  • Static factory TryCreate(IValueAccessor accessor, int historyLength, int updateInterval) -> returns a concrete Watch (HistoryWatch or DistributionWatch) for supported types (int, uint, float, int2/3, float2/3, uint2/3 and DebugWatchDistribution).

  • HistoryWatch (private class, Watch implementation)

  • Keeps a circular buffer of m_HistoryFrames and m_HistoryValues of length historyLength. Samples the accessor at the configured m_UpdateInterval and stores frames+values. Implements IJsonWritable to export its recorded history (writes debug.HistoryWatch with name, color and array of debug.WatchHistoryValue entries). Equality is defined by the underlying accessor.

  • DistributionWatch (private class, Watch implementation)

  • Accepts ITypedValueAccessor and samples values into 20 buckets (kBucketCount = 20). Supports sampling at m_UpdateInterval, can treat distribution as persistent/relative and will compute bucket ranges/percentages. Exports via IJsonWritable as debug.DistributionWatch with an array of debug.DistributionBucket values (min, max, count). Equality is defined by the underlying accessor.

Usage Example

[Preserve]
protected override void OnCreate()
{
    base.OnCreate();
    m_ManagedSystemStates = new List<ManagedSystemState>();
    m_Watches = new List<Watch>();
    m_SimulationSystem = base.World.GetOrCreateSystemManaged<SimulationSystem>();
    base.Enabled = false;
}

Additional notes: - The DebugWatchSystem is intended to be used only in debug/development builds or enabled via mods that add debug UI widgets. It relies heavily on reflection and custom attributes (DebugWatchValueAttribute, DebugWatchOnlyAttribute, DebugWatchDepsAttribute, ResourceArrayAttribute, EnumArrayAttribute). - The Watch implementations are IJsonWritable, so recorded data can be exported for inspection or telemetry. - When creating custom systems or adding DebugWatchValueAttribute to members, be mindful of threading and JobHandle dependencies — CreateTypedAccessor and GetWatchDepsFields help ensure safe access to NativeValue<> or NativeArray<> data.