2026-08-12 14:03:44 +03:00
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using CoreClient = RudderSdk.Core.RudderClient;
|
|
|
|
|
|
|
|
|
|
namespace RudderSdk.Unity
|
|
|
|
|
{
|
|
|
|
|
public enum RudderState
|
|
|
|
|
{
|
|
|
|
|
NotInitialized,
|
|
|
|
|
Initializing,
|
|
|
|
|
Ready,
|
|
|
|
|
Failed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Scene bootstrap MonoBehaviour and public entrypoint of the Rudder Unity SDK.
|
2026-09-04 14:23:38 +03:00
|
|
|
/// Put this on a startup scene object, assign a RudderConfiguration, then call
|
|
|
|
|
/// <see cref="Initialize"/>. Feature calls live on the returned Core client.
|
2026-08-12 14:03:44 +03:00
|
|
|
/// </summary>
|
|
|
|
|
[DefaultExecutionOrder(-1500), DisallowMultipleComponent]
|
|
|
|
|
public class Rudder : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
private static Rudder _instance;
|
|
|
|
|
private static CoreClient _client;
|
|
|
|
|
private static TaskCompletionSource<CoreClient> _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<CoreClient> 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(
|
2026-09-04 14:23:38 +03:00
|
|
|
"Rudder is not initialized. Add the Rudder component to a scene with a " +
|
|
|
|
|
"RudderConfiguration assigned, then call Rudder.Initialize().");
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _client;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-04 14:23:38 +03:00
|
|
|
/// Completes with the client after <see cref="Initialize"/>, or faults on
|
|
|
|
|
/// the initialization error. Prefer <see cref="Initialize"/> in game code.
|
2026-08-12 14:03:44 +03:00
|
|
|
public static Task<CoreClient> Ready => _readyCompletion.Task;
|
|
|
|
|
|
2026-09-04 14:23:38 +03:00
|
|
|
public static CoreClient Initialize()
|
2026-08-12 14:03:44 +03:00
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
if (State == RudderState.Ready && _client != null)
|
|
|
|
|
return _client;
|
2026-08-12 14:03:44 +03:00
|
|
|
|
2026-09-04 14:23:38 +03:00
|
|
|
if (State == RudderState.Failed)
|
|
|
|
|
{
|
|
|
|
|
_readyCompletion = NewCompletion();
|
|
|
|
|
LastError = null;
|
|
|
|
|
State = RudderState.NotInitialized;
|
|
|
|
|
}
|
2026-08-12 14:03:44 +03:00
|
|
|
|
|
|
|
|
if (_instance == null)
|
|
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
"Rudder is not in the scene. Add the Rudder component to a startup scene " +
|
|
|
|
|
"and assign a RudderConfiguration.");
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-04 14:23:38 +03:00
|
|
|
if (_instance._configuration == null)
|
2026-08-12 14:03:44 +03:00
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
"Rudder has no configuration. Assign a RudderConfiguration on the Rudder " +
|
|
|
|
|
"component (Assets > Create > Rudder > Configuration).");
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
State = RudderState.Initializing;
|
|
|
|
|
LastError = null;
|
|
|
|
|
|
2026-08-12 14:03:44 +03:00
|
|
|
var options = new RudderUnityClientOptions
|
|
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
BaseUrl = _instance._configuration.BaseUrl,
|
|
|
|
|
ProjectKey = _instance._configuration.ProjectKey,
|
|
|
|
|
TimeoutSeconds = _instance._configuration.TimeoutSeconds,
|
2026-08-12 14:03:44 +03:00
|
|
|
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}");
|
2026-09-04 14:23:38 +03:00
|
|
|
throw;
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
State = RudderState.Ready;
|
|
|
|
|
Debug.Log("[Rudder] Rudder SDK initialized.");
|
|
|
|
|
_readyCompletion.TrySetResult(_client);
|
|
|
|
|
Initialized?.Invoke(_client);
|
2026-09-04 14:23:38 +03:00
|
|
|
return _client;
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-04 14:23:38 +03:00
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
|
|
|
private static void ResetStatics()
|
2026-08-12 14:03:44 +03:00
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
_instance = null;
|
|
|
|
|
_client = null;
|
|
|
|
|
State = RudderState.NotInitialized;
|
|
|
|
|
LastError = null;
|
|
|
|
|
Initialized = null;
|
|
|
|
|
_readyCompletion = NewCompletion();
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-04 14:23:38 +03:00
|
|
|
private void Awake()
|
2026-08-12 14:03:44 +03:00
|
|
|
{
|
2026-09-04 14:23:38 +03:00
|
|
|
if (_instance != null && _instance != this)
|
|
|
|
|
{
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_instance = this;
|
|
|
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
{
|
|
|
|
|
_client?.Update(Time.deltaTime);
|
2026-08-12 14:03:44 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<CoreClient> NewCompletion()
|
|
|
|
|
{
|
|
|
|
|
return new TaskCompletionSource<CoreClient>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|