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. /// Put this on a startup scene object, assign a RudderConfiguration, then call /// . Feature calls live on the returned Core client. /// [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 a scene with a " + "RudderConfiguration assigned, then call Rudder.Initialize()."); } return _client; } } /// Completes with the client after , or faults on /// the initialization error. Prefer in game code. public static Task Ready => _readyCompletion.Task; public static CoreClient Initialize() { if (State == RudderState.Ready && _client != null) return _client; if (State == RudderState.Failed) { _readyCompletion = NewCompletion(); LastError = null; State = RudderState.NotInitialized; } if (_instance == null) { throw new InvalidOperationException( "Rudder is not in the scene. Add the Rudder component to a startup scene " + "and assign a RudderConfiguration."); } if (_instance._configuration == null) { throw new InvalidOperationException( "Rudder has no configuration. Assign a RudderConfiguration on the Rudder " + "component (Assets > Create > Rudder > Configuration)."); } try { State = RudderState.Initializing; LastError = null; var options = new RudderUnityClientOptions { BaseUrl = _instance._configuration.BaseUrl, ProjectKey = _instance._configuration.ProjectKey, TimeoutSeconds = _instance._configuration.TimeoutSeconds, KeyValueStore = new PlayerPrefsUnityKeyValueStore(), LoggerSink = new UnityDebugLoggerSink() }; _client = RudderUnityClientFactory.Create(options); } 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}"); throw; } State = RudderState.Ready; Debug.Log("[Rudder] Rudder SDK initialized."); _readyCompletion.TrySetResult(_client); Initialized?.Invoke(_client); return _client; } [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 Update() { _client?.Update(Time.deltaTime); } private void OnDestroy() { if (_instance != this) return; _instance = null; _client = null; State = RudderState.NotInitialized; LastError = null; _readyCompletion.TrySetException(new InvalidOperationException("Rudder was destroyed before initialization completed.")); _readyCompletion = NewCompletion(); } private static TaskCompletionSource NewCompletion() { return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } } }