/* * SPDX-License-Identifier: AGPL-3.0-or-later * Copyright (C) 2025 Sergej Görzen * This file is part of OmiLAXR. */ using System; using System.ComponentModel; using UnityEngine; using UnityEngine.Scripting; namespace OmiLAXR.TrackingBehaviours.System { /// /// Comprehensive system-level tracking behavior that monitors application lifecycle events. /// Tracks game start, quit, focus changes, and pause states with precise timestamps. /// Uses Unity's runtime initialization hooks to capture early application events. /// [AddComponentMenu("OmiLAXR / 3) Tracking Behaviours / System Tracking Behaviour")] [Description("Tracks states of game (started, quit, paused, resumed, focused, unfocused) and detects state changes.")] public class SystemTrackingBehaviour : TrackingBehaviour { /// /// Event triggered when the application/game starts with the start timestamp. /// public readonly TrackingBehaviourEvent OnStartedGame = new TrackingBehaviourEvent(); /// /// Event triggered when the application/game quits with the quit timestamp. /// public readonly TrackingBehaviourEvent OnQuitGame = new TrackingBehaviourEvent(); /// /// Event triggered when application focus changes. /// Second parameter indicates if the application gained (true) or lost (false) focus. /// public readonly TrackingBehaviourEvent OnFocusedGame = new TrackingBehaviourEvent(); /// /// Event triggered when application pause state changes. /// Second parameter indicates if the application was paused (true) or resumed (false). /// public readonly TrackingBehaviourEvent OnPausedGame = new TrackingBehaviourEvent(); /// /// Static controller for capturing application start time before most systems initialize. /// Uses Unity's RuntimeInitializeOnLoadMethod to hook into very early application lifecycle. /// [Preserve] protected static class SystemStartController { /// /// Timestamp of when the application started initializing. /// Captured at the earliest possible moment during Unity's startup sequence. /// public static DateTime? StartTime; /// /// Called automatically by Unity before the splash screen appears. /// Records the precise moment the application begins initialization. /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] private static void GameStarted() { StartTime = DateTime.Now; } } // State tracking variables private bool _isFirstRun = true; private bool _sendStartSignal; private bool _isRunning; /// /// Manually triggers the game start signal if it hasn't been sent yet. /// Uses the captured start time from SystemStartController or current time as fallback. /// public void SendStartSignal() { if (_sendStartSignal) return; // Use captured start time or current time as fallback var now = SystemStartController.StartTime.HasValue ? SystemStartController.StartTime.Value : DateTime.Now; OnStartedGame?.Invoke(this, now); _sendStartSignal = true; } /// /// Called when the tracking pipeline starts. /// Ensures the start signal is sent and marks the system as running. /// /// The pipeline that started this behavior protected override void OnStartedPipeline(Pipeline pipeline) { if (_isRunning) return; base.OnStartedPipeline(pipeline); SendStartSignal(); _isRunning = true; } protected override void OnQuitPipeline(Pipeline pipeline) { base.OnQuitPipeline(pipeline); TriggerEnd(); } /// /// Internal method to handle application termination. /// Triggers the quit event if the system is currently running. /// private void TriggerEnd() { if (!_isRunning) return; OnQuitGame.Invoke(this, DateTime.Now); _isRunning = false; } /// /// Unity callback for application focus changes. /// Triggers focus events with timestamp and focus state. /// /// True if application gained focus, false if lost protected virtual void OnApplicationFocus(bool hasFocus) { if (!enabled) return; OnFocusedGame?.Invoke(this, DateTime.Now, hasFocus); } /// /// Unity callback for application pause state changes. /// Ignores the first call which occurs during initial application startup. /// /// True if application was paused, false if resumed protected virtual void OnApplicationPause(bool pauseStatus) { if (!enabled) return; // Skip the first pause callback which happens during app initialization if (_isFirstRun) { _isFirstRun = false; return; } OnPausedGame?.Invoke(this, DateTime.Now, pauseStatus); } } }