diff --git a/src/Packages/Audience/Runtime/Unity/AudienceUnityHooks.cs b/src/Packages/Audience/Runtime/Unity/AudienceUnityHooks.cs index 6da3699e..2712c3d9 100644 --- a/src/Packages/Audience/Runtime/Unity/AudienceUnityHooks.cs +++ b/src/Packages/Audience/Runtime/Unity/AudienceUnityHooks.cs @@ -1,5 +1,6 @@ #nullable enable +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Immutable.Audience.Unity.Mobile; @@ -31,12 +32,33 @@ private static void Install() _persistentDataPath = Application.persistentDataPath; ImmutableAudience.DefaultPersistentDataPathProvider = () => Application.persistentDataPath; + // Set before the collectors below run so a collector failure logs via Debug.Log, not Console.WriteLine. + if (Log.Writer == null) Log.Writer = Debug.Log; + // Captured once on main thread; ReadOnlyDictionary blocks downstream mutation. - IReadOnlyDictionary launchProps = - new ReadOnlyDictionary(DeviceCollector.CollectGameLaunchProperties()); - IReadOnlyDictionary contextProps = - new ReadOnlyDictionary(DeviceCollector.CollectContext()); + // Each collector is isolated so one throwing can't block the other's provider or abort Install(). + IReadOnlyDictionary launchProps; + try + { + launchProps = new ReadOnlyDictionary(DeviceCollector.CollectGameLaunchProperties()); + } + catch (Exception ex) + { + Log.Warn(AudienceLogs.LaunchPropertiesCollectionFailed(ex)); + launchProps = new ReadOnlyDictionary(new Dictionary()); + } ImmutableAudience.LaunchContextProvider = () => launchProps; + + IReadOnlyDictionary contextProps; + try + { + contextProps = new ReadOnlyDictionary(DeviceCollector.CollectContext()); + } + catch (Exception ex) + { + Log.Warn(AudienceLogs.ContextCollectionFailed(ex)); + contextProps = new ReadOnlyDictionary(new Dictionary()); + } ImmutableAudience.ContextProvider = () => contextProps; #if UNITY_IOS && !UNITY_EDITOR && AUDIENCE_MOBILE_ATTRIBUTION @@ -58,8 +80,6 @@ private static void Install() #endif UnityLifecycleBridge.EnsureExists(); - - if (Log.Writer == null) Log.Writer = Debug.Log; } // Warms the install referrer cache for the next launch and returns diff --git a/src/Packages/Audience/Runtime/Unity/DeviceCollector.cs b/src/Packages/Audience/Runtime/Unity/DeviceCollector.cs index 7c09bb11..df7fb510 100644 --- a/src/Packages/Audience/Runtime/Unity/DeviceCollector.cs +++ b/src/Packages/Audience/Runtime/Unity/DeviceCollector.cs @@ -32,18 +32,28 @@ internal static Dictionary CollectContext() private static string? TryResolveScreenString() { - var resolution = Screen.currentResolution; - int width = resolution.width; - int height = resolution.height; - - if (width <= 0 || height <= 0) + // Can throw before a window/graphics device exists this early in boot; + // isolated so a failure here doesn't cost CollectContext()'s other fields. + try { - width = Screen.width; - height = Screen.height; + var resolution = Screen.currentResolution; + int width = resolution.width; + int height = resolution.height; + + if (width <= 0 || height <= 0) + { + width = Screen.width; + height = Screen.height; + } + + if (width <= 0 || height <= 0) return null; + return $"{width}x{height}"; + } + catch (Exception ex) + { + Log.Warn(AudienceLogs.ScreenResolutionReadFailed(ex)); + return null; } - - if (width <= 0 || height <= 0) return null; - return $"{width}x{height}"; } internal static Dictionary CollectGameLaunchProperties( diff --git a/src/Packages/Audience/Runtime/Utility/Log.cs b/src/Packages/Audience/Runtime/Utility/Log.cs index 94104ae9..f42ae85a 100644 --- a/src/Packages/Audience/Runtime/Utility/Log.cs +++ b/src/Packages/Audience/Runtime/Utility/Log.cs @@ -168,6 +168,20 @@ internal static string IdentityLoadOrGenerateFailed(Exception ex) => $"Identity file read/write failed. {ex.GetType().Name}: {ex.Message}. " + "Events will ship without identity fields this session."; + // ---- Install-time device/context collection ---- + + internal static string LaunchPropertiesCollectionFailed(Exception ex) => + $"CollectGameLaunchProperties threw {ex.GetType().Name}: {ex.Message} during Install(). " + + "game_launch will ship without auto-detected Unity context this session."; + + internal static string ContextCollectionFailed(Exception ex) => + $"CollectContext threw {ex.GetType().Name}: {ex.Message} during Install(). " + + "All events will ship without locale/screen/timezone/userAgent this session."; + + internal static string ScreenResolutionReadFailed(Exception ex) => + $"Screen resolution read threw {ex.GetType().Name}: {ex.Message}. " + + "Continuing without a screen field."; + // ---- Steam auto-detection ---- internal static string SteamPlatformDetectionFailed(Exception ex) =>