Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## Unreleased

### Features

- Added Nintendo Switch 2 support. The SDK now recognises the platform, links the Switch 2 build of the native library from `Assets/Plugins/Sentry/Switch2/`, and uploads its debug symbols. Switch 2 shares the existing `SwitchNativeSupportEnabled` option ([#2831](https://github.com/getsentry/sentry-unity/pull/2831))

### Fixes

- The `UnityWebRequestTransport` no longer opens a connection while the platform reports no network, and backs off exponentially (1s up to 60s) after a connection error instead of retrying on every envelope. On Nintendo Switch each send attempt made while offline raises the system's "connect to the internet" dialog, so a game with logs or metrics enabled produced a prompt every few seconds ([#2831](https://github.com/getsentry/sentry-unity/pull/2831))
- The `UnityLogger` no longer throws while logging an exception whose stack trace cannot be stringified. On Nintendo Switch this escaped from inside the handler that had already dealt with the original error, aborting SDK initialization and leaving the Unity integrations unregistered ([#2831](https://github.com/getsentry/sentry-unity/pull/2831))
- On Nintendo Switch the SDK now asks the native SDK whether the network is usable before sending, instead of relying on `Application.internetReachability`, which reports the console as reachable while it is offline. Every send attempted in that state raised the system's "connect to the internet" dialog. Requires a sentry-switch build exposing `sentry_switch_utils_is_network_available()` ([#2831](https://github.com/getsentry/sentry-unity/pull/2831))

### Dependencies

- Bump .NET SDK from v6.8.0 to v6.9.0 ([#2815](https://github.com/getsentry/sentry-unity/pull/2815))
Expand Down
7 changes: 7 additions & 0 deletions package-dev/Plugins/Switch/sentry_native_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ const char* sentry_switch_utils_get_default_user_id(void)
return "";
}

int sentry_switch_utils_is_network_available(void)
{
/* Return 1 - with no native SDK to ask, assume the network is usable and let the
transport fall back on its connection-error backoff */
return 1;
}

/*
* =============================================================================
* Utility Functions
Expand Down
4 changes: 4 additions & 0 deletions package-dev/Plugins/Switch/sentry_native_stubs.c.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package-dev/Runtime/Sentry.Unity.Native.Switch.dll.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package-dev/Runtime/SentryInitialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#define SENTRY_NATIVE_SWITCH
#endif

#if UNITY_SWITCH2
#define SENTRY_NATIVE_SWITCH2
#endif

#if UNITY_WEBGL
#define SENTRY_WEBGL
#endif
Expand All @@ -45,7 +49,7 @@
using Sentry.Unity.iOS;
#elif SENTRY_NATIVE_ANDROID
using Sentry.Unity.Android;
#elif SENTRY_NATIVE || SENTRY_NATIVE_SWITCH
#elif SENTRY_NATIVE || SENTRY_NATIVE_SWITCH || SENTRY_NATIVE_SWITCH2
using Sentry.Unity.Native;
#elif SENTRY_WEBGL
using Sentry.Unity.WebGL;
Expand Down Expand Up @@ -108,7 +112,7 @@ private static void SetUpPlatformServices()
SentryPlatformServices.PlatformConfiguration = SentryNativeCocoa.Configure;
#elif SENTRY_NATIVE_ANDROID
SentryPlatformServices.PlatformConfiguration = SentryNativeAndroid.Configure;
#elif SENTRY_NATIVE_SWITCH
#elif SENTRY_NATIVE_SWITCH || SENTRY_NATIVE_SWITCH2
SentryPlatformServices.PlatformConfiguration = SentryNativeSwitch.Configure;
#elif SENTRY_NATIVE
SentryPlatformServices.PlatformConfiguration = SentryNative.Configure;
Expand Down
1 change: 1 addition & 0 deletions package-dev/Runtime/io.sentry.unity.dev.runtime.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"LinuxStandalone64",
"macOSStandalone",
"Switch",
"Switch2",
"PS5",
"WSA",
"WebGL",
Expand Down
1 change: 1 addition & 0 deletions package/Runtime/io.sentry.unity.runtime.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"macOSStandalone",
"PS5",
"Switch",
"Switch2",
"WSA",
"WebGL",
"WindowsStandalone32",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ internal static void Display(ScriptableSentryUnityOptions options, SentryCliOpti
options.PlayStationNativeSupportEnabled);

options.SwitchNativeSupportEnabled = EditorGUILayout.Toggle(
new GUIContent("Nintendo Switch", "Whether to enable native scope sync support on Nintendo Switch."),
new GUIContent("Nintendo Switch", "Whether to enable native scope sync support on Nintendo Switch and Switch 2."),
options.SwitchNativeSupportEnabled);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.IO;
using System.Linq;
using Sentry.Extensibility;
Expand All @@ -21,32 +22,57 @@ namespace Sentry.Unity.Editor.Native;
/// </remarks>
internal class SwitchNativePluginBuildPreProcess : IPreprocessBuildWithReport
{
private static readonly string[] RequiredFiles =
/// <summary>
/// <c>BuildTarget.Switch2</c> only exists in Unity 6000.3 and newer, and this assembly is
/// compiled against a single Unity version, so the target is matched by name. The build report
/// hands us the value itself, so the enum member never has to be referenced.
/// </summary>
internal const string Switch2BuildTargetName = "Switch2";

private static bool IsSwitchFamily(BuildTarget target) =>
target == BuildTarget.Switch || IsSwitch2(target);

private static bool IsSwitch2(BuildTarget target) =>
string.Equals(target.ToString(), Switch2BuildTargetName, StringComparison.Ordinal);

/// <summary>
/// Both platforms share one stub, so the required libraries are what differ between them.
/// </summary>
private static string[] RequiredFilesFor(BuildTarget target)
{
"Assets/Plugins/Sentry/Switch/libsentry.a",
"Assets/Plugins/Sentry/Switch/libzstd.a",
};
var directory = IsSwitch2(target) ? Switch2BuildTargetName : nameof(BuildTarget.Switch);
return new[]
{
$"Assets/Plugins/Sentry/{directory}/libsentry.a",
$"Assets/Plugins/Sentry/{directory}/libzstd.a",
};
}

public int callbackOrder => -100;

public void OnPreprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.Switch)
if (!IsSwitchFamily(report.summary.platform))
{
return;
}

var options = SentryScriptableObject.LoadOptions(isBuilding: true);
var logger = options?.DiagnosticLogger ?? new UnityLogger(new SentryUnityOptions());

ConfigureStub(logger, options?.SwitchNativeSupportEnabled ?? false);
// Switch 2 reuses the Switch implementation and therefore its option.
ConfigureStub(logger, options?.SwitchNativeSupportEnabled ?? false, report.summary.platform);
}

internal static void ConfigureStub(IDiagnosticLogger logger, bool nativeSupportEnabled)
internal static void ConfigureStub(IDiagnosticLogger logger, bool nativeSupportEnabled, BuildTarget target)
{
logger.LogDebug("Switch native support: checking for required files:\n{0}",
string.Join("\n", RequiredFiles.Select(f => $" - {f}")));
var requiredFiles = RequiredFilesFor(target);

logger.LogDebug("{0} native support: checking for required files:\n{1}",
target, string.Join("\n", requiredFiles.Select(f => $" - {f}")));

// One stub serves both platforms; the importer tracks compatibility per build target, so
// enabling it for one does not affect the other.
var stubPath = Path.Combine("Packages", SentryPackageInfo.GetName(), "Plugins", "Switch", "sentry_native_stubs.c");

var importer = AssetImporter.GetAtPath(stubPath) as PluginImporter;
Expand All @@ -56,14 +82,16 @@ internal static void ConfigureStub(IDiagnosticLogger logger, bool nativeSupportE
return;
}

var existingFiles = RequiredFiles.Where(File.Exists).ToList();
var missingFiles = RequiredFiles.Except(existingFiles).ToList();
var existingFiles = requiredFiles.Where(File.Exists).ToList();
var missingFiles = requiredFiles.Except(existingFiles).ToList();

var someFilesPresent = existingFiles.Count > 0 && missingFiles.Count > 0;
if (someFilesPresent)
{
// LogError has no two-argument overload that does not also take an exception, so the
// target goes into the format string rather than being passed alongside the file list.
logger.LogError(
"Switch native support is partially configured. Missing files:\n{0}\n" +
target + " native support is partially configured. Missing files:\n{0}\n" +
"Please add all required files to enable native support, or remove all files to fall back on no-op stubs.\n" +
"Build sentry-switch and copy the libraries to the expected locations. " +
"See: https://github.com/getsentry/sentry-switch",
Expand All @@ -75,26 +103,26 @@ internal static void ConfigureStub(IDiagnosticLogger logger, bool nativeSupportE
var allFilesPresent = missingFiles.Count == 0;
if (allFilesPresent)
{
logger.LogInfo("Switch native libraries found:\n{0}",
string.Join("\n", existingFiles.Select(f => $" - {f}")));
importer.SetCompatibleWithPlatform(BuildTarget.Switch, false);
logger.LogInfo("{0} native libraries found:\n{1}",
target, string.Join("\n", existingFiles.Select(f => $" - {f}")));
importer.SetCompatibleWithPlatform(target, false);
}
else
{
if (nativeSupportEnabled)
{
logger.LogWarning(
"Switch native support is enabled but required files are missing:\n{0}\n" +
"{0} native support is enabled but required files are missing:\n{1}\n" +
"Build sentry-switch and copy the libraries to the expected locations. " +
"See: https://github.com/getsentry/sentry-switch",
string.Join("\n", missingFiles.Select(f => $" - {f}"))
target, string.Join("\n", missingFiles.Select(f => $" - {f}"))
);
}
else
{
logger.LogDebug("Switch native support is disabled. Enabling stubs (native calls will be no-op).");
logger.LogDebug("{0} native support is disabled. Enabling stubs (native calls will be no-op).", target);
}
importer.SetCompatibleWithPlatform(BuildTarget.Switch, true);
importer.SetCompatibleWithPlatform(target, true);
}

importer.SaveAndReimport();
Expand Down
18 changes: 18 additions & 0 deletions src/Sentry.Unity.Native/SentryNativeSwitch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,21 @@ public static class SentryNativeSwitch
[DllImport("__Internal")]
private static extern IntPtr sentry_switch_utils_get_default_user_id();

[DllImport("__Internal")]
private static extern int sentry_switch_utils_is_network_available();

private static IDiagnosticLogger? Logger;

/// <summary>
/// Queries the network interface manager without submitting a network request.
/// </summary>
/// <remarks>
/// Unity's <c>Application.internetReachability</c> reports the console as reachable even while
/// it is offline, and every send attempted in that state raises the system's "connect to the
/// internet" dialog. Asking the native SDK first is what keeps the dialog off the screen.
/// </remarks>
internal static bool IsNetworkAvailable() => sentry_switch_utils_is_network_available() == 1;

/// <summary>
/// Configures the native support for Nintendo Switch.
/// </summary>
Expand Down Expand Up @@ -72,6 +85,11 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
return;
}

// Wired up before the storage and native SDK setup below: the probe only needs the native
// library to be linked, so the transport keeps the benefit even if either of those fails.
Logger?.LogDebug("Using the native SDK to determine network availability.");
Comment on lines 85 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: An early return in SentryNativeSwitch.Configure prevents setting the NetworkAvailabilityProbe on Switch when native support is disabled, causing an unreliable network check.
Severity: MEDIUM

Suggested Fix

Move the options.NetworkAvailabilityProbe = IsNetworkAvailable; assignment to before the if (!options.IsNativeSupportEnabled(platform)) check. This will ensure the reliable network probe is always configured for the Switch platform, regardless of the native support setting.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/Sentry.Unity.Native/SentryNativeSwitch.cs#L85-L90

Potential issue: In `SentryNativeSwitch.Configure`, an early `return` statement is
executed if `options.IsNativeSupportEnabled(platform)` is false. This prevents the
`options.NetworkAvailabilityProbe` from being set to the custom `IsNetworkAvailable`
function for the Nintendo Switch platform. Consequently, the transport layer falls back
to using `Application.internetReachability`, which is known to be unreliable on the
Switch and can incorrectly report the device as online. This defeats the purpose of a
fix designed to prevent offline error dialogs from appearing when the console is not
connected to the internet.

Did we get this right? 👍 / 👎 to inform future reviews.

options.NetworkAvailabilityProbe = IsNetworkAvailable;

Logger?.LogDebug("Mounting temporary storage for sentry-switch.");

if (sentry_switch_utils_mount() != 1)
Expand Down
3 changes: 3 additions & 0 deletions src/Sentry.Unity/Integrations/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal interface IApplication
string UnityVersion { get; }
string PersistentDataPath { get; }
RuntimePlatform Platform { get; }
NetworkReachability InternetReachability { get; }
}

/// <summary>
Expand Down Expand Up @@ -54,6 +55,8 @@ private ApplicationAdapter()

public RuntimePlatform Platform => Application.platform;

public NetworkReachability InternetReachability => Application.internetReachability;

private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
=> LogMessageReceived?.Invoke(condition, stackTrace, type);

Expand Down
1 change: 1 addition & 0 deletions src/Sentry.Unity/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[assembly: InternalsVisibleTo("Sentry.Unity.Native")]
[assembly: InternalsVisibleTo("Sentry.Unity.Native.PlayStation")]
[assembly: InternalsVisibleTo("Sentry.Unity.Native.Switch")]
[assembly: InternalsVisibleTo("Sentry.Unity.Native.Switch2")]
[assembly: InternalsVisibleTo("Sentry.Unity.Native.Xbox")]
[assembly: InternalsVisibleTo("Sentry.Unity.Tests")]
[assembly: InternalsVisibleTo("Sentry.Unity.Editor")]
Expand Down
12 changes: 12 additions & 0 deletions src/Sentry.Unity/SentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,18 @@ internal string? DefaultUserId
/// </summary>
internal Action? NativeSupportCloseCallback { get; set; } = null;

/// <summary>
/// Reports whether the network is currently usable, when the platform can answer that more
/// reliably than <see cref="UnityEngine.Application.internetReachability"/>.
/// </summary>
/// <remarks>
/// Set by the platform configuration where a native probe exists. On Nintendo Switch this
/// matters twice over: reachability reports the console as reachable while it is offline, and
/// merely attempting a connection there raises the system's "connect to the internet" dialog.
/// Must not block - it is called on the main thread before each send.
/// </remarks>
internal Func<bool>? NetworkAvailabilityProbe { get; set; } = null;

internal List<string> SdkIntegrationNames { get; set; } = new();

internal ISentryUnityInfo UnityInfo { get; private set; }
Expand Down
18 changes: 18 additions & 0 deletions src/Sentry.Unity/SentryUnityOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,27 @@ internal static bool IsValid(this SentryUnityOptions options)
return true;
}

/// <summary>
/// <c>RuntimePlatform.Switch2</c> was only added in Unity 6000.3. This assembly is compiled
/// against a single Unity version while the SDK still supports 2021.3, so Switch 2 is matched
/// by name instead of by enum member - referencing the member directly would stop the SDK from
/// building against the editors that predate it.
/// </summary>
internal const string Switch2PlatformName = "Switch2";

internal static bool IsSwitch2(this RuntimePlatform platform) =>
string.Equals(platform.ToString(), Switch2PlatformName, StringComparison.Ordinal);

internal static bool IsNativeSupportEnabled(this SentryUnityOptions options, RuntimePlatform? platform = null)
{
platform ??= ApplicationAdapter.Instance.Platform;

// Switch 2 reuses the Switch native support, and therefore its option.
if (platform.Value.IsSwitch2())
{
return options.SwitchNativeSupportEnabled;
}

return platform switch
{
RuntimePlatform.Android => options.AndroidNativeSupportEnabled,
Expand Down
Loading