Editor/Integration/SessionTracker.cs

Editor utility that tracks editor play-session state and timing. It records server start time, play start, last hotload, a frame counter, and a persisted play session count using EditorCookie; it updates state on frame and hotload editor events.

File Access
using System;
using System.Linq;
using Editor;
using static Sandbox.Internal.GlobalToolsNamespace;

namespace SboxMcp.Integration;

/// <summary>
/// Tracks play-session identity and hotload timing so tools (and the agents
/// driving them) can tell restarts apart - play clones reuse the editor GUIDs,
/// which otherwise makes "did the scene restart?" a forensic guess.
/// The play counter persists across hotloads via EditorCookie; the rest is
/// transient (main-thread only, like the frame hooks that maintain it).
/// </summary>
public static class SessionTracker
{
	static bool? _wasPlaying;

	public static DateTime ServerStartedAt { get; } = DateTime.Now;
	public static DateTime? PlayStartedAt { get; private set; }
	public static DateTime? LastHotloadAt { get; private set; }

	// check ALL sessions, not just Active: during HTTP-driven play the "active"
	// session can still be the editor one, so Active?.IsPlaying under-reports
	public static bool IsPlaying =>
		SceneEditorSession.All?.Any( s => s is not null && s.IsPlaying ) ?? false;

	/// <summary>Monotonic editor-frame counter (drives perf_get_stats' FPS calc).</summary>
	public static long FrameCount { get; private set; }

	/// <summary>How many times play mode has been entered this editor run
	/// (persisted so a hotload doesn't reset it mid-session).</summary>
	public static int PlaySessionCount
	{
		get => EditorCookie.Get( "SboxMcp.PlaySessionCount", 0 );
		private set => EditorCookie.Set( "SboxMcp.PlaySessionCount", value );
	}

	[EditorEvent.Frame]
	static void OnFrame()
	{
		FrameCount++;

		var playing = IsPlaying;

		// first frame after (re)load: sync without counting, so a hotload during
		// play doesn't register as a new session
		if ( _wasPlaying is null )
		{
			_wasPlaying = playing;
			return;
		}

		if ( playing && _wasPlaying == false )
		{
			PlaySessionCount++;
			PlayStartedAt = DateTime.Now;
		}
		else if ( !playing && _wasPlaying == true )
		{
			PlayStartedAt = null;
		}

		_wasPlaying = playing;
	}

	[EditorEvent.Hotload]
	static void OnHotload() => LastHotloadAt = DateTime.Now;
}