diff --git a/Runtime/Client/LootLockerLifecycleManager.cs b/Runtime/Client/LootLockerLifecycleManager.cs index 65a2c6fb5..f63dd2f39 100644 --- a/Runtime/Client/LootLockerLifecycleManager.cs +++ b/Runtime/Client/LootLockerLifecycleManager.cs @@ -46,7 +46,11 @@ public class LootLockerLifecycleManager : MonoBehaviour #region Instance Handling private static LootLockerLifecycleManager _instance; - private static int _instanceId = 0; +#if UNITY_6000_5_OR_NEWER + private static EntityId _instanceId = default; +#else + private static int _instanceId = default; +#endif private static GameObject _hostingGameObject = null; private static readonly object _instanceLock = new object(); @@ -70,7 +74,7 @@ private static void AutoInitialize() { Debug.LogWarning($"[LootLocker] LifecycleManager auto-initialization failed: {ex.Message}. Services will be initialized on first use."); _instance = null; - _instanceId = 0; + _instanceId = default; if (_hostingGameObject != null) { Destroy(_hostingGameObject); @@ -115,7 +119,11 @@ private static void Instantiate() { var gameObject = new GameObject("LootLockerLifecycleManager"); _instance = gameObject.AddComponent(); +#if UNITY_6000_5_OR_NEWER + _instanceId = _instance.GetEntityId(); +#else _instanceId = _instance.GetInstanceID(); +#endif _hostingGameObject = gameObject; if (Application.isPlaying) @@ -150,7 +158,7 @@ private static void TeardownInstance() #endif _instance = null; - _instanceId = 0; + _instanceId = default; _hostingGameObject = null; } @@ -168,13 +176,22 @@ public static IEnumerator CleanUpOldInstances() #endif foreach (LootLockerLifecycleManager manager in managers) { - if (manager != null && _instanceId != manager.GetInstanceID() && manager.gameObject != null && ((LootLockerLifecycleManager)manager)._isInitialized) + if (manager != null && manager.gameObject != null && ((LootLockerLifecycleManager)manager)._isInitialized) { + bool isCurrentInstance; +#if UNITY_6000_5_OR_NEWER + isCurrentInstance = _instanceId == manager.GetEntityId(); +#else + isCurrentInstance = _instanceId == manager.GetInstanceID(); +#endif + if (!isCurrentInstance) + { #if UNITY_EDITOR - DestroyImmediate(manager.gameObject); + DestroyImmediate(manager.gameObject); #else - Destroy(manager.gameObject); + Destroy(manager.gameObject); #endif + } } } yield return null; diff --git a/Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs b/Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs new file mode 100644 index 000000000..8e643e7f4 --- /dev/null +++ b/Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs @@ -0,0 +1,59 @@ +using LootLocker; +using NUnit.Framework; + +namespace LootLockerTests.PlayMode +{ + /// + /// Guards against regressions in the IANA timezone strings produced by the SDK. + /// The backend rejects invalid IANA timezones (e.g. wrong casing such as "ETC/GMT") + /// with "invalid JSON input" errors when starting a session. + /// + public class TimezoneConverterTests + { + [Test, Category("LootLocker"), Category("LootLockerCI"), Category("LootLockerCIFast")] + public void ConvertUTCOffsetToIana_UsesCorrectCasingAndSign() + { + // IANA sign convention is reversed: Etc/GMT+X means UTC-X + Assert.AreEqual("Etc/GMT+5", LootLockerTimezoneConverter.convertUTCOffsetToIanaTzString(-5)); + Assert.AreEqual("Etc/GMT-2", LootLockerTimezoneConverter.convertUTCOffsetToIanaTzString(2)); + Assert.AreEqual("Etc/UTC", LootLockerTimezoneConverter.convertUTCOffsetToIanaTzString(0)); + #if UNITY_2021_1_OR_NEWER + // Out-of-range offsets are clamped (the converter only clamps from UNITY_2021_1_OR_NEWER) + Assert.AreEqual("Etc/GMT+12", LootLockerTimezoneConverter.convertUTCOffsetToIanaTzString(-999)); + Assert.AreEqual("Etc/GMT-14", LootLockerTimezoneConverter.convertUTCOffsetToIanaTzString(999)); + #endif + } + + [Test, Category("LootLocker"), Category("LootLockerCI"), Category("LootLockerCIFast")] + public void ConvertGMTOffsetToIana_UsesCorrectCasingAndSign() + { + Assert.AreEqual("Etc/GMT-5", LootLockerTimezoneConverter.convertGMTOffsetToIanaTzString(-5)); + Assert.AreEqual("Etc/GMT+2", LootLockerTimezoneConverter.convertGMTOffsetToIanaTzString(2)); + Assert.AreEqual("Etc/UTC", LootLockerTimezoneConverter.convertGMTOffsetToIanaTzString(0)); + } + + [Test, Category("LootLocker"), Category("LootLockerCI"), Category("LootLockerCIFast")] + public void TryConvertStringToIana_HandlesOffsetsWindowsAndIanaInputs() + { + // Numeric offset string + Assert.IsTrue(LootLockerTimezoneConverter.TryConvertStringToIanaTzString("2", out string fromOffset)); + Assert.AreEqual("Etc/GMT-2", fromOffset); + + // IANA passthrough + Assert.IsTrue(LootLockerTimezoneConverter.TryConvertStringToIanaTzString("Asia/Tokyo", out string iana)); + Assert.AreEqual("Asia/Tokyo", iana); + + // Windows timezone converted + Assert.IsTrue(LootLockerTimezoneConverter.TryConvertStringToIanaTzString("Romance Standard Time", out string fromWindows)); + Assert.AreEqual("Europe/Paris", fromWindows); + + // Unknown input falls back to Etc/UTC (correctly cased) + Assert.IsFalse(LootLockerTimezoneConverter.TryConvertStringToIanaTzString("NotATimezone", out string fallback)); + Assert.AreEqual("Etc/UTC", fallback); + + // Empty input falls back to Etc/UTC + Assert.IsFalse(LootLockerTimezoneConverter.TryConvertStringToIanaTzString("", out string emptyFallback)); + Assert.AreEqual("Etc/UTC", emptyFallback); + } + } +} diff --git a/Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs.meta b/Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs.meta new file mode 100644 index 000000000..799f7fd45 --- /dev/null +++ b/Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f237609cb88f46e0bb05d583a91dba17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: