using System; using System.Threading.Tasks; using UnityEngine; using CoreClient = RudderSdk.Core.RudderClient; namespace RudderSdk.Unity { public enum RudderState { NotInitialized, Initializing, Ready, Failed } /// /// Scene bootstrap MonoBehaviour and public entrypoint of the Rudder Unity SDK. /// Add the component to a scene with a RudderConfiguration assigned, or call /// Rudder.Initialize(configuration) and the hidden GameObject is created for you. /// [DefaultExecutionOrder(-1500), DisallowMultipleComponent] public class Rudder : MonoBehaviour { private static Rudder _instance; private static CoreClient _client; private static TaskCompletionSource _readyCompletion = NewCompletion(); [SerializeField] private RudderConfiguration _configuration; /// Attach point for optional add-on packages so this assembly never has to /// reference them. Sugar over Ready: subscribers that load late must check /// State themselves. public static event Action Initialized; public static RudderState State { get; private set; } = RudderState.NotInitialized; public static Exception LastError { get; private set; } public static CoreClient Client { get { if (State != RudderState.Ready || _client == null) { throw new InvalidOperationException( "Rudder is not initialized. Add the Rudder component to the scene with a " + "RudderConfiguration assigned, or call Rudder.Initialize(configuration)."); } return _client; } } /// Completes with the initialized client, or faults with the initialization error. public static Task Ready => _readyCompletion.Task; public static void Initialize(RudderConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); if (State == RudderState.Initializing || State == RudderState.Ready) throw new InvalidOperationException("Rudder is already initialized."); if (_instance == null) { var host = new GameObject(nameof(Rudder)); _instance = host.AddComponent(); } _instance._configuration = configuration; _ = _instance.InitializeAsync(); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetStatics() { _instance = null; _client = null; State = RudderState.NotInitialized; LastError = null; Initialized = null; _readyCompletion = NewCompletion(); } private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; DontDestroyOnLoad(gameObject); } private void Start() { if (_instance == this && _configuration != null && State == RudderState.NotInitialized) _ = InitializeAsync(); } private async Task InitializeAsync() { State = RudderState.Initializing; LastError = null; try { var options = new RudderUnityClientOptions { BaseUrl = _configuration.BaseUrl, RealtimeUrl = _configuration.RealtimeUrl, ProjectKey = _configuration.ProjectKey, TimeoutSeconds = _configuration.TimeoutSeconds, KeyValueStore = new PlayerPrefsUnityKeyValueStore(), LoggerSink = new UnityDebugLoggerSink() }; _client = RudderUnityClientFactory.Create(options); try { await _client.Scenario.RestoreAsync(); } catch (Exception restoreException) { Debug.LogWarning("[Rudder] Failed to restore scenario state: " + restoreException.Message); } } catch (Exception exception) { _client = null; State = RudderState.Failed; LastError = exception; _readyCompletion.TrySetException(exception); Debug.LogError( "[Rudder] Failed to initialize the Rudder SDK. Check the RudderConfiguration asset " + $"(ProjectKey and BaseUrl must be set). {exception.Message}"); return; } State = RudderState.Ready; Debug.Log("[Rudder] Rudder SDK initialized."); _readyCompletion.TrySetResult(_client); Initialized?.Invoke(_client); } private void Update() { _client?.Update(Time.deltaTime); } private void OnApplicationQuit() { DisconnectRealtime(); } private void OnDestroy() { if (_instance != this) return; DisconnectRealtime(); _instance = null; _client = null; State = RudderState.NotInitialized; LastError = null; _readyCompletion.TrySetException(new InvalidOperationException("Rudder was destroyed before initialization completed.")); _readyCompletion = NewCompletion(); } private void DisconnectRealtime() { var client = _client; if (client == null) return; try { _ = client.Realtime.DisconnectAsync().ContinueWith( task => Debug.LogWarning( "[Rudder] Realtime disconnect failed: " + task.Exception?.GetBaseException().Message), TaskContinuationOptions.OnlyOnFaulted); } catch (Exception exception) { Debug.LogWarning("[Rudder] Realtime disconnect failed: " + exception.Message); } } private static TaskCompletionSource NewCompletion() { return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } } }