diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarTransformMatrixJobWrapper.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarTransformMatrixJobWrapper.cs index 2daca7fe03..a029c6a1a6 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarTransformMatrixJobWrapper.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarTransformMatrixJobWrapper.cs @@ -1,6 +1,7 @@ using System; using DCL.AvatarRendering.AvatarShape.ComputeShader; using DCL.AvatarRendering.AvatarShape.UnityInterface; +using DCL.Utility; using Unity.Collections; using Unity.Mathematics; using UnityEngine; @@ -88,13 +89,28 @@ public void RegisterAvatar(AvatarBase avatarBase, ref AvatarTransformMatrixCompo public void Dispose() { + // Leak the resouces. Managed dispose of TransformAccessArray takes very much time. + if (DCL.Utility.ExitUtils.IsAboutToQuit) + { + return; + } + + var stopwatch = ShutdownStopwatch.StartNew(nameof(AvatarTransformMatrixJobWrapper)); + remoteAvatars.Complete(); + stopwatch.LogStep("remoteAvatars.Complete"); remoteAvatars.Dispose(); + stopwatch.LogStep("remoteAvatars.Dispose"); + mainPlayerAvatar.Dispose(); + stopwatch.LogStep("mainPlayerAvatar.Dispose"); if (dummyTransform != null) + { UnityEngine.Object.Destroy(dummyTransform.gameObject); + stopwatch.LogStep("dummyTransform.Destroy"); + } disposed = true; } diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/RemoteAvatarPipeline.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/RemoteAvatarPipeline.cs index 67269bfb13..6652935f13 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/RemoteAvatarPipeline.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/RemoteAvatarPipeline.cs @@ -225,13 +225,37 @@ private static void FillWithDummy(Transform[] array, int startIndex, int count, public void Dispose() { + // Leak the resouces. Managed dispose of TransformAccessArray takes very much time. + if (DCL.Utility.ExitUtils.IsAboutToQuit) + { + return; + } + + var stopwatch = DCL.Utility.ShutdownStopwatch.StartNew(nameof(RemoteAvatarPipeline)); + bonesCombined.Dispose(); + stopwatch.LogStep("bonesCombined.Dispose"); + matrixFromAllAvatars.Dispose(); + stopwatch.LogStep("matrixFromAllAvatars.Dispose"); + updateAvatar.Dispose(); + stopwatch.LogStep("updateAvatar.Dispose"); + Job.Dispose(); + stopwatch.LogStep("job.Dispose"); - if (bonesTransformAccessArray.isCreated) bonesTransformAccessArray.Dispose(); - if (rootsTransformAccessArray.isCreated) rootsTransformAccessArray.Dispose(); + if (bonesTransformAccessArray.isCreated) + { + bonesTransformAccessArray.Dispose(); + stopwatch.LogStep("bonesTransformAccessArray.Dispose"); + } + + if (rootsTransformAccessArray.isCreated) + { + rootsTransformAccessArray.Dispose(); + stopwatch.LogStep("rootsTransformAccessArray.Dispose"); + } } /// diff --git a/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs b/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs index 376546396a..57ff505927 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs @@ -106,6 +106,15 @@ public static class AppArgsFlags public const string LSD_REMOTE_AB_SERVER = "lsd-remote-ab-server"; public const string LSD_REMOTE_AB_WORLD = "lsd-remote-ab-world"; + public const string NO_LIVEKIT_MODE = "no-livekit-mode"; + + public const string NATIVE_SHUTDOWN_STOPWATCH = "native-shutdown-stopwatch"; + + /// + /// Use Unity's Application.Quit() (full native teardown) on exit instead of the default hard process termination. For native debugging only. + /// + public const string SOFT_SHUTDOWN = "soft-shutdown"; + public static class Multiplayer { public const string COMPRESSION = "compression"; diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs index 4601c686c7..2be970c7c9 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs @@ -430,12 +430,21 @@ static IMultiPool MultiPoolFactory() => var voiceChatRoom = new VoiceChatActivatableConnectiveRoom(); - IRoomHub roomHub = new RoomHub( - localSceneDevelopment ? IConnectiveRoom.Null.INSTANCE : archipelagoIslandRoom, - gateKeeperSceneRoom, - chatRoom, - voiceChatRoom - ); + IRoomHub roomHub; + + if (appArgs.HasFlag(AppArgsFlags.NO_LIVEKIT_MODE)) + { + roomHub = NullRoomHub.INSTANCE; + } + else + { + roomHub = new RoomHub( + localSceneDevelopment ? IConnectiveRoom.Null.INSTANCE : archipelagoIslandRoom, + gateKeeperSceneRoom, + chatRoom, + voiceChatRoom + ); + } var islandThroughputBunch = new ThroughputBufferBunch(new ThroughputBuffer(), new ThroughputBuffer()); var sceneThroughputBunch = new ThroughputBufferBunch(new ThroughputBuffer(), new ThroughputBuffer()); diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs index 2b4c12c50a..6af702a590 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/MainSceneLoader.cs @@ -96,6 +96,8 @@ public class MainSceneLoader : MonoBehaviour, ICoroutineRunner private FileStream? singleInstanceLock; private ErrorPopupWithRetryView? clockDesyncPopupPrefab; + private bool canShutdown; + private void Awake() { InitializeFlowAsync(destroyCancellationToken).Forget(); @@ -103,32 +105,62 @@ private void Awake() private void OnDestroy() { + Shutdown(); + } + + private void Shutdown() + { + if (PlayerLoopHelper.IsMainThread == false) + return; + + if (canShutdown == false) + return; + + canShutdown = false; + + var stopwatch = ShutdownStopwatch.StartNew(nameof(MainSceneLoader)); + DisableAllSelectableTransitions(); + stopwatch.LogStep(nameof(DisableAllSelectableTransitions)); if (dynamicWorldContainer != null) { foreach (IDCLGlobalPlugin plugin in dynamicWorldContainer.GlobalPlugins) + { plugin.SafeDispose(ReportCategory.ENGINE); + stopwatch.LogStep($"GlobalPlugin {plugin.GetType().Name}"); + } if (globalWorld != null) + { dynamicWorldContainer.RealmController.DisposeGlobalWorld(); + stopwatch.LogStep("DisposeGlobalWorld"); + } dynamicWorldContainer.SafeDispose(ReportCategory.ENGINE); + stopwatch.LogStep("dynamicWorldContainer.SafeDispose"); } if (staticContainer != null) { // Exclude SharedPlugins as they were disposed as they were already disposed of as `GlobalPlugins` foreach (IDCLPlugin worldPlugin in staticContainer.ECSWorldPlugins.Except(staticContainer.SharedPlugins)) + { worldPlugin.SafeDispose(ReportCategory.ENGINE); + stopwatch.LogStep($"ECSWorldPlugin {worldPlugin.GetType().Name}"); + } staticContainer.SafeDispose(ReportCategory.ENGINE); + stopwatch.LogStep("staticContainer.SafeDispose"); } bootstrapContainer?.Dispose(); + stopwatch.LogStep("bootstrapContainer.Dispose"); + splashScreen.Dispose(); + stopwatch.LogStep("splashScreen.Dispose"); - ReportHub.Log(ReportCategory.ENGINE, "OnDestroy successfully finished"); + ReportHub.LogProductionInfo($"[MainSceneLoader] OnDestroy successfully finished in {stopwatch.ElapsedMilliseconds}ms"); } private void OnApplicationQuit() @@ -144,6 +176,11 @@ public void ApplyConfig(IAppArgs applicationParametersParser) { if (applicationParametersParser.TryGetValue(AppArgsFlags.ENVIRONMENT, out string? environment)) ParseEnvironment(environment!); + + ExitUtils.Configure( + softShutdown: applicationParametersParser.HasFlag(AppArgsFlags.SOFT_SHUTDOWN), + nativeShutdownStopwatch: applicationParametersParser.HasFlag(AppArgsFlags.NATIVE_SHUTDOWN_STOPWATCH) + ); } private void ParseEnvironment(string environment) @@ -154,6 +191,9 @@ private void ParseEnvironment(string environment) private async UniTask InitializeFlowAsync(CancellationToken ct) { + canShutdown = true; + ExitUtils.RegisterCleanUpCandidate(new OnQuittingCleanUpCandidate(nameof(MainSceneLoader), Shutdown)); + IAppArgs applicationParametersParser = new ApplicationParametersParser( #if UNITY_EDITOR debugSettings.AppParameters diff --git a/Explorer/Assets/DCL/Infrastructure/Utility/ExitUtils.cs b/Explorer/Assets/DCL/Infrastructure/Utility/ExitUtils.cs index 8a45192a64..f0018ec6ed 100644 --- a/Explorer/Assets/DCL/Infrastructure/Utility/ExitUtils.cs +++ b/Explorer/Assets/DCL/Infrastructure/Utility/ExitUtils.cs @@ -4,9 +4,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Runtime.InteropServices; using DCL.Diagnostics; +using DCL.Prefs; using UnityEngine; using Utility.Multithreading; +using RichTypes; #if UNITY_EDITOR using UnityEditor; #endif @@ -49,22 +52,34 @@ public static class ExitUtils private static readonly Mutex> candidates = new (new ()); // IGNORE_LINE_WEBGL_THREAD_SAFETY_FLAG private static readonly Atomic isExiting = new (false); + private static bool useSoftShutdown; + private static bool useNativeShutdownStopwatch; + + /// + /// In the Editor always false so the full dispose path runs on Play Mode exit — + /// leaked native resources would accumulate in the Editor process until restart. + /// +#if UNITY_EDITOR + public static bool IsAboutToQuit => false; +#else + public static bool IsAboutToQuit => isExiting.Value(); +#endif + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void SubscribeToApplicationQuitting() { #if UNITY_EDITOR Patch.Reset(); -#endif - // Dirty reflection hack because there is no other way to view the subs of Application.quitting - Patch.ApplicationQuittingFirstSubscriberSelfPatchWithTimers(); -#if UNITY_EDITOR // ensure isExiting is false on reopening isExiting.Set(false); using (var scope = candidates.Lock()) // IGNORE_LINE_WEBGL_THREAD_SAFETY_FLAG scope.Value.Clear(); #endif Application.quitting += OnApplicationQuitting; + + // Dirty reflection hack because there is no other way to view the subs of Application.quitting + Patch.Apply(); } private static void OnApplicationQuitting() @@ -114,8 +129,22 @@ public static void UnregisterCleanUpCandidate(string name) } } + public static void Configure(bool softShutdown, bool nativeShutdownStopwatch) + { + useSoftShutdown = softShutdown; + useNativeShutdownStopwatch = nativeShutdownStopwatch; + ReportHub.LogProductionInfo($"[ExitUtils] Configured: softShutdown - {useSoftShutdown}, nativeShutdownStopwatch - {useNativeShutdownStopwatch}"); + } + + // Safe to call multiple times public static void Exit() { + if (Cysharp.Threading.Tasks.PlayerLoopHelper.IsMainThread == false) + { + ReportHub.LogProductionInfo("[ExitUtils] Exit() cannot be called from not a main thread"); + return; + } + Stopwatch stopwatch = Stopwatch.StartNew(); ReportHub.LogProductionInfo($"[ExitUtils] Exit requested at {DateTime.UtcNow:O}"); @@ -127,6 +156,13 @@ public static void Exit() isExiting.Set(true); +#if UNITY_STANDALONE_WIN + if (useNativeShutdownStopwatch) + { + StartExitStopwatch(); + } +#endif + using (var scope = candidates.Lock()) // IGNORE_LINE_WEBGL_THREAD_SAFETY_FLAG { foreach (OnQuittingCleanUpCandidate candidate in scope.Value) @@ -135,8 +171,15 @@ public static void Exit() ReportHub.LogProductionInfo($"[ExitUtils] CleanUpCandidates finished at {stopwatch.ElapsedMilliseconds}ms"); - // Reflection may drop the values. resubscribe to be sure. - Patch.ApplicationQuittingFirstSubscriberSelfPatchWithTimers(); + // Flush save file only AFTER the candidates + DCLPlayerPrefs.SaveSync(); + ReportHub.LogProductionInfo($"[ExitUtils] DCLPlayerPrefs flushed at {stopwatch.ElapsedMilliseconds}ms"); + + // Reflection may drop the values. Reapply to be sure, and to move TryTerminateSelf back to the + // last position in case anything subscribed to Application.quitting after the previous Apply(). + Patch.Apply(); + + ReportHub.LogProductionInfo($"[ExitUtils] Begin Quit call {stopwatch.ElapsedMilliseconds}ms"); #if UNITY_EDITOR EditorApplication.isPlaying = false; #else @@ -145,23 +188,188 @@ public static void Exit() ReportHub.LogProductionInfo($"[ExitUtils] Quit call dispatched at {stopwatch.ElapsedMilliseconds}ms"); } +#if UNITY_STANDALONE_WIN + // Expected to be called from main thread only. + private static void StartExitStopwatch() + { + string targetPath = NewTargetPath(); + string exePath = StopwatchExePath(); + + int pid = Process.GetCurrentProcess().Id; // IL2CPP safe + + // --target-pid -o + string[] args = new [] + { + "--target-pid", + pid.ToString(), + "-o", + targetPath + }; + + ReportHub.LogProductionInfo($"[ExitUtils] Start measuring native exit delay"); + + Result result = Plugins.DclNativeProcesses.DclProcesses.Start(exePath, args); + + if (result.Success == false) + { + ReportHub.LogProductionInfo($"[ExitUtils] Cannot start measuring native exit delay: {result.ErrorMessage}"); + } + } + + private static string NewTargetPath() + { + string dir = UnityEngine.Application.persistentDataPath; + long unixTime = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + string name = $"exit_stopwatch_{unixTime}.log"; + string path = System.IO.Path.Combine(dir, name); + return path; + } + + private static string StopwatchExePath() + { + string dir = UnityEngine.Application.streamingAssetsPath; + string path = System.IO.Path.Combine(dir, "dcl_exit_stopwatch.exe"); + return path; + } +#endif + +#if UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX + private static void TryTerminateSelf() + { + int pid = Process.GetCurrentProcess().Id; // IL2CPP safe + ReportHub.LogProductionInfo($"[ExitUtils] Terminating process pid={pid}"); + + if (useSoftShutdown) + { + ReportHub.LogProductionInfo($"[ExitUtils] Terminating process aborted due useSoftShutdown mode"); + return; + } + +#if UNITY_STANDALONE_WIN + IntPtr handle = OpenProcess(PROCESS_TERMINATE, false, (uint)pid); + + if (handle == IntPtr.Zero) + { + ReportHub.LogProductionInfo($"[ExitUtils] OpenProcess failed (err {Marshal.GetLastWin32Error()})"); + return; + } + + if (TerminateProcess(handle, 0) == false) + { + ReportHub.LogProductionInfo($"[ExitUtils] TerminateProcess failed (err {Marshal.GetLastWin32Error()})"); + CloseHandle(handle); + } +#elif UNITY_STANDALONE_OSX + if (kill(pid, SIGKILL) != 0) + { + ReportHub.LogProductionInfo($"[ExitUtils] kill(SIGKILL) failed (errno {Marshal.GetLastWin32Error()})"); + } +#endif + } + +#if UNITY_STANDALONE_WIN + + private const uint PROCESS_TERMINATE = 0x0001; + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + +#elif UNITY_STANDALONE_OSX + + private const int SIGKILL = 9; + + [DllImport("libc", SetLastError = true)] + private static extern int kill(int pid, int sig); + +#endif + +#endif + private static class Patch { private static readonly HashSet wrapped = new (); private static FieldInfo quittingField; + + // Safe to call multiple times. + public static void Apply() + { + ApplicationQuittingFirstSubscriberSelfPatchWithTimers(); + RegisterTerminateSelf(); + } + + public static void Reset() + { + wrapped.Clear(); + } + + private static FieldInfo? QuitFieldInfo() + { + quittingField ??= typeof(Application).GetField("quitting", BindingFlags.NonPublic | BindingFlags.Static); + + if (quittingField == null) + { + ReportHub.LogProductionInfo("[ExitUtils.Patch] Cannot find Application.quitting backing field"); + } + + return quittingField; + } + + // Keeps TryTerminateSelf as the very LAST Application.quitting subscriber. + // Safe to call multiple times. + private static void RegisterTerminateSelf() + { +#if (UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX) && !UNITY_EDITOR + try + { + var quittingField = QuitFieldInfo(); + if (quittingField == null) + { + return; + } + + Action terminateSelf = TryTerminateSelf; + + wrapped.Add(terminateSelf); + + Action? current = quittingField.GetValue(null) as Action; + Delegate[] handlers = current?.GetInvocationList() ?? Array.Empty(); + + Action? rebuilt = null; + + // Delete if exists + foreach (Delegate handler in handlers) + { + if (handler is not Action action) continue; + if (action == terminateSelf) continue; + + rebuilt += action; + } + + // Append at the very end + rebuilt += terminateSelf; + + quittingField.SetValue(null, rebuilt); + } + catch (Exception e) { ReportHub.LogException(e, ReportCategory.UNSPECIFIED); } +#endif + } // Method is safe to be called multiple times. Idempotency - public static void ApplicationQuittingFirstSubscriberSelfPatchWithTimers() + private static void ApplicationQuittingFirstSubscriberSelfPatchWithTimers() { ReportHub.LogProductionInfo($"[ExitUtils.Patch] Invoke ApplicationQuittingFirstSubscriberSelfPatchWithTimers, actions wrapped {wrapped.Count}"); try { - quittingField ??= typeof(Application).GetField("quitting", BindingFlags.NonPublic | BindingFlags.Static); - + var quittingField = QuitFieldInfo(); if (quittingField == null) { - ReportHub.LogProductionInfo("[ExitUtils.Patch] Cannot find Application.quitting backing field, per-subscriber timing disabled"); return; } @@ -193,11 +401,6 @@ public static void ApplicationQuittingFirstSubscriberSelfPatchWithTimers() catch (Exception e) { ReportHub.LogException(e, ReportCategory.UNSPECIFIED); } } - public static void Reset() - { - wrapped.Clear(); - } - private static Action WrapWithTimer(Action original) { string label = $"{original.Method.DeclaringType?.FullName}.{original.Method.Name}"; diff --git a/Explorer/Assets/DCL/Infrastructure/Utility/ShutdownStopwatch.cs b/Explorer/Assets/DCL/Infrastructure/Utility/ShutdownStopwatch.cs new file mode 100644 index 0000000000..fc9241a71a --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/Utility/ShutdownStopwatch.cs @@ -0,0 +1,37 @@ +#nullable enable + +using System.Diagnostics; +using DCL.Diagnostics; + +namespace DCL.Utility +{ + /// + /// Measures per-step durations during shutdown and logs them via . + /// Mutable struct: keep it as a local variable, do not copy it around. + /// + public struct ShutdownStopwatch + { + private readonly string prefix; + private readonly Stopwatch stopwatch; + private long stepStartedAtMs; + + public long ElapsedMilliseconds => stopwatch.ElapsedMilliseconds; + + private ShutdownStopwatch(string prefix) + { + this.prefix = prefix; + stopwatch = Stopwatch.StartNew(); + stepStartedAtMs = 0; + } + + public static ShutdownStopwatch StartNew(string prefix) => + new (prefix); + + public void LogStep(string step) + { + long totalMs = stopwatch.ElapsedMilliseconds; + ReportHub.LogProductionInfo($"[{prefix}] '{step}' took {totalMs - stepStartedAtMs}ms (total {totalMs}ms)"); + stepStartedAtMs = totalMs; + } + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/Utility/ShutdownStopwatch.cs.meta b/Explorer/Assets/DCL/Infrastructure/Utility/ShutdownStopwatch.cs.meta new file mode 100644 index 0000000000..27c39bc1ce --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/Utility/ShutdownStopwatch.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c3601704153cddf47aa50bfb2ba6dde6 \ No newline at end of file diff --git a/Explorer/Assets/DCL/PerformanceAndDiagnostics/Analytics/CrashDetector.cs b/Explorer/Assets/DCL/PerformanceAndDiagnostics/Analytics/CrashDetector.cs index 688ab31236..f7fc627d95 100644 --- a/Explorer/Assets/DCL/PerformanceAndDiagnostics/Analytics/CrashDetector.cs +++ b/Explorer/Assets/DCL/PerformanceAndDiagnostics/Analytics/CrashDetector.cs @@ -1,13 +1,13 @@ using DCL.Prefs; +using DCL.Utility; using Newtonsoft.Json.Linq; -using UnityEngine; namespace DCL.PerformanceAndDiagnostics.Analytics { /// /// Detects non-graceful application exits and reports them to the analytics service /// - public class CrashDetector : MonoBehaviour + public static class CrashDetector { public static void Initialize(IAnalyticsController analyticsController) { @@ -18,7 +18,7 @@ public static void Initialize(IAnalyticsController analyticsController) analyticsController.Track(AnalyticsEvents.General.CRASH, new JObject { - { "previous_session_id", previousSessionID } + { "previous_session_id", previousSessionID }, }); } @@ -26,16 +26,13 @@ public static void Initialize(IAnalyticsController analyticsController) DCLPlayerPrefs.SetString(DCLPrefKeys.CRASH_DETECTOR_SESSION_ID, analyticsController.SessionID); DCLPlayerPrefs.Save(); - var go = new GameObject("CrashDetector"); - go.AddComponent(); - DontDestroyOnLoad(go); + ExitUtils.UnregisterCleanUpCandidate(nameof(CrashDetector)); + ExitUtils.RegisterCleanUpCandidate(new OnQuittingCleanUpCandidate(nameof(CrashDetector), ClearCrashFlag)); } - private void OnApplicationQuit() + private static void ClearCrashFlag() { - // NOTE: If you remove this, make sure to call DCLPlayerPrefs.SaveSync() in another OnApplicationQuit method DCLPlayerPrefs.DeleteKey(DCLPrefKeys.CRASH_DETECTOR_FLAG); - DCLPlayerPrefs.SaveSync(); } } } diff --git a/Explorer/Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/Sentry/SentryReportHandler.cs b/Explorer/Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/Sentry/SentryReportHandler.cs index 3f7a6ea22c..e0b82a1184 100644 --- a/Explorer/Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/Sentry/SentryReportHandler.cs +++ b/Explorer/Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/Sentry/SentryReportHandler.cs @@ -1,5 +1,6 @@ using DCL.Optimization.Pools; using DCL.Optimization.ThreadSafePool; +using DCL.Utility; using Sentry; using Sentry.Extensibility; using Sentry.Unity; @@ -14,6 +15,8 @@ public class SentryReportHandler : ReportHandlerBase { public delegate void ConfigureScope(Scope scope); + private static readonly TimeSpan SESSION_FLUSH_TIMEOUT = TimeSpan.FromSeconds(2); + private readonly List scopeConfigurators = new (10); private readonly PerReportScope.Pool scopesPool; @@ -52,6 +55,14 @@ public SentryReportHandler(ICategorySeverityMatrix matrix, SentrySampler sentryS } SentrySdk.Init(options); + + ExitUtils.RegisterCleanUpCandidate(new OnQuittingCleanUpCandidate(nameof(SentryReportHandler), EndSessionAndFlush)); + } + + private static void EndSessionAndFlush() + { + SentrySdk.EndSession(); + SentrySdk.Flush(SESSION_FLUSH_TIMEOUT); } public void AddMeetMinimumRequirements(Scope scope, bool meets) diff --git a/Explorer/Assets/DCL/PluginSystem/Global/AvatarPlugin.cs b/Explorer/Assets/DCL/PluginSystem/Global/AvatarPlugin.cs index a4323dbeae..be914c030b 100644 --- a/Explorer/Assets/DCL/PluginSystem/Global/AvatarPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/Global/AvatarPlugin.cs @@ -14,6 +14,7 @@ using DCL.ResourcesUnloading; using DCL.Utilities; using DCL.Utilities.Extensions; +using DCL.Utility; using ECS; using System; using System.Collections.Generic; @@ -130,9 +131,16 @@ public AvatarPlugin( public void Dispose() { + var stopwatch = ShutdownStopwatch.StartNew(nameof(AvatarPlugin)); + attachmentsAssetsCache.Dispose(); + stopwatch.LogStep("attachmentsAssetsCache.Dispose"); + avatarTransformMatrixJobWrapper.Dispose(); + stopwatch.LogStep("avatarTransformMatrixJobWrapper.Dispose"); + UnityObjectUtils.SafeDestroyGameObject(poolParent); + stopwatch.LogStep("SafeDestroyGameObject(poolParent)"); } public async UniTask InitializeAsync(AvatarShapeSettings settings, CancellationToken ct) diff --git a/Explorer/Assets/DCL/PluginSystem/Global/MultiplayerPlugin.cs b/Explorer/Assets/DCL/PluginSystem/Global/MultiplayerPlugin.cs index fe75b11d4f..946bf33050 100644 --- a/Explorer/Assets/DCL/PluginSystem/Global/MultiplayerPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/Global/MultiplayerPlugin.cs @@ -28,6 +28,7 @@ using DCL.Profiles; using DCL.RealmNavigation; using DCL.UserInAppInitializationFlow; +using DCL.Utility; using ECS; using ECS.LifeCycle.Systems; using ECS.SceneLifeCycle; @@ -124,11 +125,17 @@ public MultiplayerPlugin( public void Dispose() { + var stopwatch = ShutdownStopwatch.StartNew(nameof(MultiplayerPlugin)); + archipelagoIslandRoom.Dispose(); + stopwatch.LogStep("archipelagoIslandRoom.Dispose"); + gateKeeperSceneRoom.Dispose(); + stopwatch.LogStep("gateKeeperSceneRoom.Dispose"); #if !NO_LIVEKIT_MODE IFFIClient.Default.Dispose(); + stopwatch.LogStep("IFFIClient.Default.Dispose"); #endif } diff --git a/Explorer/Assets/DCL/PluginSystem/World/MaterialWorldPlugin.cs b/Explorer/Assets/DCL/PluginSystem/World/MaterialWorldPlugin.cs index cc2ac0994b..2bc0592c76 100644 --- a/Explorer/Assets/DCL/PluginSystem/World/MaterialWorldPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/World/MaterialWorldPlugin.cs @@ -8,7 +8,6 @@ using ECS.Unity.Materials; using ECS.Unity.Materials.Components; using ECS.Unity.Materials.Systems; -using RenderHeads.Media.AVProVideo; using System; using System.Collections.Generic; using System.Threading; diff --git a/Explorer/Assets/DCL/PluginSystem/World/MediaPlayerPlugin.cs b/Explorer/Assets/DCL/PluginSystem/World/MediaPlayerPlugin.cs index 6f090ecca4..48b2ab7718 100644 --- a/Explorer/Assets/DCL/PluginSystem/World/MediaPlayerPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/World/MediaPlayerPlugin.cs @@ -10,7 +10,6 @@ using DCL.SDKComponents.MediaStream.Settings; using DCL.WebRequests; using ECS.LifeCycle; -using RenderHeads.Media.AVProVideo; using System; using System.Collections.Generic; using System.Threading; diff --git a/Explorer/Assets/Plugins/.DclNativeExitStopwatch/build.bat b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/build.bat new file mode 100644 index 0000000000..0676858796 --- /dev/null +++ b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/build.bat @@ -0,0 +1 @@ +clang -O2 dcl_exit_stopwatch.c -o .\..\..\StreamingAssets\dcl_exit_stopwatch.exe diff --git a/Explorer/Assets/Plugins/.DclNativeExitStopwatch/build.bat.meta b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/build.bat.meta new file mode 100644 index 0000000000..aae81e409c --- /dev/null +++ b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/build.bat.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 85b2c615e3cd2134c95258da65a9230e \ No newline at end of file diff --git a/Explorer/Assets/Plugins/.DclNativeExitStopwatch/dcl_exit_stopwatch.c b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/dcl_exit_stopwatch.c new file mode 100644 index 0000000000..d9c917d647 --- /dev/null +++ b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/dcl_exit_stopwatch.c @@ -0,0 +1,99 @@ +// Windows only + +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include + +static void usage(void) +{ + fprintf(stderr, + "Usage:\n" + " exit-timer.exe --target-pid -o \n"); +} + +int main(int argc, char** argv) +{ + DWORD target_pid = 0; + const char* output_path = NULL; + + for (int i = 1; i < argc; ++i) + { + if (strcmp(argv[i], "--target-pid") == 0) + { + if (i + 1 >= argc) + { + usage(); + return 1; + } + + target_pid = (DWORD)strtoul(argv[++i], NULL, 10); + } + else if (strcmp(argv[i], "-o") == 0) + { + if (i + 1 >= argc) + { + usage(); + return 1; + } + + output_path = argv[++i]; + } + } + + if (target_pid == 0 || output_path == NULL) + { + usage(); + return 1; + } + + HANDLE process = + OpenProcess(SYNCHRONIZE, FALSE, target_pid); + + if (!process) + { + fprintf(stderr, + "OpenProcess failed. pid=%lu error=%lu\n", + target_pid, + GetLastError()); + + return 1; + } + + LARGE_INTEGER freq; + LARGE_INTEGER start; + LARGE_INTEGER end; + + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&start); + + WaitForSingleObject(process, INFINITE); + + QueryPerformanceCounter(&end); + + double elapsed_ms = + ((double)(end.QuadPart - start.QuadPart) * 1000.0) / + (double)freq.QuadPart; + + FILE* f = fopen(output_path, "w"); + + if (!f) + { + fprintf(stderr, + "Failed to open output file: %s\n", + output_path); + + CloseHandle(process); + return 1; + } + + fprintf(f, "%.3f\n", elapsed_ms); + + fclose(f); + CloseHandle(process); + + return 0; +} diff --git a/Explorer/Assets/Plugins/.DclNativeExitStopwatch/dcl_exit_stopwatch.c.meta b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/dcl_exit_stopwatch.c.meta new file mode 100644 index 0000000000..3894d636c7 --- /dev/null +++ b/Explorer/Assets/Plugins/.DclNativeExitStopwatch/dcl_exit_stopwatch.c.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5334c6fc4f3901440bb5832f8b37bf71 \ No newline at end of file diff --git a/Explorer/Assets/Plugins/DclNativeExitStopwatch.meta b/Explorer/Assets/Plugins/DclNativeExitStopwatch.meta new file mode 100644 index 0000000000..c2aa564893 --- /dev/null +++ b/Explorer/Assets/Plugins/DclNativeExitStopwatch.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d91b7867ae6912a42babc7e159a546d6 \ No newline at end of file diff --git a/Explorer/Assets/StreamingAssets/dcl_exit_stopwatch.exe b/Explorer/Assets/StreamingAssets/dcl_exit_stopwatch.exe new file mode 100644 index 0000000000..66f06c3123 Binary files /dev/null and b/Explorer/Assets/StreamingAssets/dcl_exit_stopwatch.exe differ diff --git a/Explorer/Assets/StreamingAssets/dcl_exit_stopwatch.exe.meta b/Explorer/Assets/StreamingAssets/dcl_exit_stopwatch.exe.meta new file mode 100644 index 0000000000..79163a2293 --- /dev/null +++ b/Explorer/Assets/StreamingAssets/dcl_exit_stopwatch.exe.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ae26105a1ca222947964a671ea4ad787 \ No newline at end of file