Skip to content
Merged
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
29 changes: 23 additions & 6 deletions Runtime/Client/LootLockerLifecycleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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);
Expand Down Expand Up @@ -115,7 +119,11 @@ private static void Instantiate()
{
var gameObject = new GameObject("LootLockerLifecycleManager");
_instance = gameObject.AddComponent<LootLockerLifecycleManager>();
#if UNITY_6000_5_OR_NEWER
_instanceId = _instance.GetEntityId();
#else
_instanceId = _instance.GetInstanceID();
#endif
_hostingGameObject = gameObject;

if (Application.isPlaying)
Expand Down Expand Up @@ -150,7 +158,7 @@ private static void TeardownInstance()
#endif

_instance = null;
_instanceId = 0;
_instanceId = default;
_hostingGameObject = null;
}

Expand All @@ -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;
Expand Down
59 changes: 59 additions & 0 deletions Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using LootLocker;
using NUnit.Framework;

namespace LootLockerTests.PlayMode
{
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
11 changes: 11 additions & 0 deletions Tests/LootLockerTests/PlayMode/TimezoneConverterTests.cs.meta

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