diff --git a/Explorer/Assets/DCL/Character/CharacterCamera/Systems/ControlCinemachineVirtualCameraSystem.cs b/Explorer/Assets/DCL/Character/CharacterCamera/Systems/ControlCinemachineVirtualCameraSystem.cs index b29548cbf5d..30f660b08e3 100644 --- a/Explorer/Assets/DCL/Character/CharacterCamera/Systems/ControlCinemachineVirtualCameraSystem.cs +++ b/Explorer/Assets/DCL/Character/CharacterCamera/Systems/ControlCinemachineVirtualCameraSystem.cs @@ -117,6 +117,7 @@ private void HandleOffset([Data] float dt, ref CameraComponent cameraComponent, { ThirdPersonCameraShoulder.Right => ThirdPersonCameraShoulder.Left, ThirdPersonCameraShoulder.Left => ThirdPersonCameraShoulder.Right, + ThirdPersonCameraShoulder.Center => ThirdPersonCameraShoulder.Right, }; ThirdPersonCameraShoulder thirdPersonCameraShoulder = cameraComponent.Shoulder; diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs index 88138d8e2ae..aa4ed0d510d 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs +++ b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs @@ -25,13 +25,13 @@ public class CharacterPreviewAvatarContainer : MonoBehaviour, IDisposable private bool isFOVTransitioning; [field: SerializeField] internal Vector3 previewPositionInScene { get; private set; } - [field: SerializeField] internal Transform avatarParent { get; private set; } - [field: SerializeField] internal Camera camera { get; private set; } - [field: SerializeField] internal Transform cameraTarget { get; private set; } - [field: SerializeField] internal Transform rotationTarget { get; private set; } - [field: SerializeField] internal CinemachineFreeLook freeLookCamera { get; private set; } - [field: SerializeField] internal GameObject previewPlatform { get; private set; } - [field: SerializeField] internal AvatarPreviewHeadIKSettings headIKSettings { get; private set; } + [field: SerializeField] internal Transform avatarParent { get; private set; } = null!; + [field: SerializeField] internal new Camera camera { get; private set; } = null!; + [field: SerializeField] internal Transform cameraTarget { get; private set; } = null!; + [field: SerializeField] internal Transform rotationTarget { get; private set; } = null!; + [field: SerializeField] internal CinemachineFreeLook freeLookCamera { get; private set; } = null!; + [field: SerializeField] internal GameObject previewPlatform { get; private set; } = null!; + [field: SerializeField] internal AvatarPreviewHeadIKSettings headIKSettings { get; private set; } = null!; internal float TargetFOV { get; set; } internal float RotationModifier { get; set; } diff --git a/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/FriendPanelSectionControllerBase.cs b/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/FriendPanelSectionControllerBase.cs index 6590ce140e9..d2a8f8dcd10 100644 --- a/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/FriendPanelSectionControllerBase.cs +++ b/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/FriendPanelSectionControllerBase.cs @@ -19,6 +19,7 @@ public abstract class FriendPanelSectionControllerBase : IDisposable protected readonly U requestManager; private CancellationTokenSource friendListInitCts = new (); + private bool disposed; protected UniTaskCompletionSource? panelLifecycleTask { get; private set; } @@ -40,6 +41,7 @@ public virtual void Dispose() view.Disable -= Disable; requestManager.Dispose(); friendListInitCts.SafeCancelAndDispose(); + disposed = true; } public async UniTask InitAsync(CancellationToken ct) @@ -53,6 +55,9 @@ public async UniTask InitAsync(CancellationToken ct) if (!result.Success) return; + if (ct.IsCancellationRequested) + return; + view.SetLoadingState(false); bool showScrollView = ShouldShowScrollView(); @@ -73,6 +78,9 @@ public virtual void Reset() => protected void CheckShouldInit() { + if (disposed) + return; + if (!requestManager.WasInitialised) InitAsync(friendListInitCts.Token).Forget(); } diff --git a/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/SectionLoadingView.cs b/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/SectionLoadingView.cs index be027652d98..ddbe31f5ee5 100644 --- a/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/SectionLoadingView.cs +++ b/Explorer/Assets/DCL/Friends/UI/FriendPanel/Sections/SectionLoadingView.cs @@ -10,8 +10,11 @@ public class SectionLoadingView : MonoBehaviour [field: SerializeField] public LoadingBrightView LoadingBright { get; private set; } [field: SerializeField] public float FadeDuration { get; private set; } = 0.3f; + private Tweener? fadeTween; + public void Show() { + fadeTween?.Kill(); CanvasGroup.alpha = 1; CanvasGroup.blocksRaycasts = true; LoadingBright.StartLoadingAnimation(null); @@ -19,8 +22,16 @@ public void Show() public void Hide() { - CanvasGroup.DOFade(0, FadeDuration).OnComplete(() => CanvasGroup.blocksRaycasts = false); + fadeTween?.Kill(); + fadeTween = CanvasGroup.DOFade(0, FadeDuration).OnComplete(() => CanvasGroup.blocksRaycasts = false); LoadingBright.FinishLoadingAnimation(null); } + + private void OnDestroy() + { + // Without this the fade outlives panel teardown and DOTween keeps driving the destroyed CanvasGroup. + fadeTween?.Kill(); + fadeTween = null; + } } } diff --git a/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/UIBindings/ElementBinding.cs b/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/UIBindings/ElementBinding.cs index 9ed7a09298a..2af05b135d3 100644 --- a/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/UIBindings/ElementBinding.cs +++ b/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/UIBindings/ElementBinding.cs @@ -8,7 +8,7 @@ namespace DCL.DebugUtilities.UIBindings /// public class ElementBinding : IElementBinding { - private T tempValue; + private T tempValue = default!; private bool tempValueIsDirty; diff --git a/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/Views/DebugElementBase.cs b/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/Views/DebugElementBase.cs index db17cf8a505..b3a73c1528e 100644 --- a/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/Views/DebugElementBase.cs +++ b/Explorer/Assets/DCL/PerformanceAndDiagnostics/DebugUtilities/Views/DebugElementBase.cs @@ -5,7 +5,7 @@ namespace DCL.DebugUtilities.Views { public abstract class DebugElementBase : VisualElement where TElement: DebugElementBase where TDef: IDebugElementDef { - protected TDef definition { get; private set; } + protected TDef definition { get; private set; } = default!; public void Initialize(TDef definition) { diff --git a/Explorer/Assets/DCL/RealmNavigation/LoadingOperation/SequentialLoadingOperation.cs b/Explorer/Assets/DCL/RealmNavigation/LoadingOperation/SequentialLoadingOperation.cs index e20c881dfe9..8d819f256b5 100644 --- a/Explorer/Assets/DCL/RealmNavigation/LoadingOperation/SequentialLoadingOperation.cs +++ b/Explorer/Assets/DCL/RealmNavigation/LoadingOperation/SequentialLoadingOperation.cs @@ -55,14 +55,26 @@ public virtual async UniTask> ExecuteAsync(string processN if (!lastOpResult.Success) { - ReportHub.LogError( - reportData, - $"Operation failed on {processName} attempt {attempt + 1}/{attemptsCount}: {lastOpResult.AsResult().ErrorMessage}" - ); + // Do not log cancellation as an error (CLAUDE.md §9): on shutdown the inner op + // converts its OperationCanceledException into a TaskError.Cancelled result. + if (!ct.IsCancellationRequested && lastOpResult.Error?.State != TaskError.Cancelled) + ReportHub.LogError( + reportData, + $"Operation failed on {processName} attempt {attempt + 1}/{attemptsCount}: {lastOpResult.AsResult().ErrorMessage}" + ); break; } } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + // Cancellation of the outer flow is not an error: convert to a cancelled result + // (the check below exits the attempt loop). An OperationCanceledException from an + // operation's internal token is NOT ours to absorb - it propagates as before, so + // the attempt loop cannot re-run the whole chain on an inner timeout. + lastOpResult = EnumResult.CancelledResult(TaskError.Cancelled); + break; + } catch (Exception e) { lastOpResult = EnumResult.ErrorResult(TaskError.UnexpectedException, $"Unhandled exception on {processName} attempt {attempt + 1}/{attemptsCount}: {e}"); diff --git a/Explorer/Assets/DCL/SceneLoadingScreens/LoadingScreen/LoadingScreen.cs b/Explorer/Assets/DCL/SceneLoadingScreens/LoadingScreen/LoadingScreen.cs index 6c7ad6c326f..f8a38da2c0b 100644 --- a/Explorer/Assets/DCL/SceneLoadingScreens/LoadingScreen/LoadingScreen.cs +++ b/Explorer/Assets/DCL/SceneLoadingScreens/LoadingScreen/LoadingScreen.cs @@ -102,10 +102,12 @@ async UniTask> ExecuteLoadingScreenAsync() SceneLoadingScreenController.IssueCommand(new SceneLoadingScreenController.Params(loadReport)), ct) .SuppressToResultAsync(ReportCategory.SCENE_LOADING); - if (loadReport.GetStatus().TaskStatus == UniTaskStatus.Pending) + // Both logs below are pure cancellation artifacts on ExitPlayMode (the outer ct cancels ShowAsync, + // leaving loadReport Pending and result as TaskError.Cancelled). Only log when not cancelled (CLAUDE.md §9). + if (!ct.IsCancellationRequested && loadReport.GetStatus().TaskStatus == UniTaskStatus.Pending) ReportHub.LogError(ReportCategory.SCENE_LOADING, "Loading screen finished unexpectedly, but the loading process continues"); - if (finalResult.HasValue && !result.Success) + if (!ct.IsCancellationRequested && finalResult.HasValue && !result.Success) ReportHub.LogError(ReportCategory.SCENE_LOADING, $"Loading screen finished with an error after the flow has finished: {result.Error.AsMessage()}"); return result; diff --git a/Explorer/Assets/DCL/SceneLoadingScreens/SceneLoadingScreenView.cs b/Explorer/Assets/DCL/SceneLoadingScreens/SceneLoadingScreenView.cs index c0fc0ad2690..4d9fbeb94f9 100644 --- a/Explorer/Assets/DCL/SceneLoadingScreens/SceneLoadingScreenView.cs +++ b/Explorer/Assets/DCL/SceneLoadingScreens/SceneLoadingScreenView.cs @@ -72,11 +72,13 @@ private void Awake() public void ClearTips() { + // Application/view teardown can destroy the tip objects (children of this view) before + // ClearTips runs; skip the already-destroyed entries. foreach (TipView tip in tips) - Destroy(tip.gameObject); + if (tip != null) Destroy(tip.gameObject); foreach (TipBreadcrumb? breadcrumb in tipsBreadcrumbs) - Destroy(breadcrumb.gameObject); + if (breadcrumb != null) Destroy(breadcrumb.gameObject); tips.Clear(); tipsBreadcrumbs.Clear(); diff --git a/Explorer/Assets/DCL/SceneLoadingScreens/UnityLocalizationSceneTipsProvider.cs b/Explorer/Assets/DCL/SceneLoadingScreens/UnityLocalizationSceneTipsProvider.cs index 341b91e0897..ae4f6b6d2fe 100644 --- a/Explorer/Assets/DCL/SceneLoadingScreens/UnityLocalizationSceneTipsProvider.cs +++ b/Explorer/Assets/DCL/SceneLoadingScreens/UnityLocalizationSceneTipsProvider.cs @@ -46,7 +46,7 @@ public async UniTask InitializeAsync(CancellationToken ct) fallbackTips = Get(tipsTable, imagesTable, ct); } - public async UniTask GetAsync(CancellationToken ct) => + public UniTask GetAsync(CancellationToken ct) => // TODO: we will need specific scene tips in the future, but its disabled at the moment /*StringTable tipsTable = await tipsDatabase.GetTableAsync($"LoadingSceneTips-{parcelCoord.x},{parcelCoord.y}").Task @@ -60,7 +60,7 @@ public async UniTask GetAsync(CancellationToken ct) => ct.ThrowIfCancellationRequested(); return await Get(tipsTable, imagesTable, ct);*/ - fallbackTips; + UniTask.FromResult(fallbackTips); private SceneTips Get(StringTable tipsTable, AssetTable? imagesTable, CancellationToken ct) { diff --git a/Explorer/Assets/DCL/UI/Controls/ControlsPanel.prefab b/Explorer/Assets/DCL/UI/Controls/ControlsPanel.prefab index b778a333bcd..2467fae66ee 100644 --- a/Explorer/Assets/DCL/UI/Controls/ControlsPanel.prefab +++ b/Explorer/Assets/DCL/UI/Controls/ControlsPanel.prefab @@ -671,7 +671,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: 4974685691c134a35bcea75af1936381, type: 3} + m_Sprite: {fileID: 0} m_Type: 0 m_PreserveAspect: 1 m_FillCenter: 1 diff --git a/Explorer/Assets/DCL/UI/GenericContextMenu/Controls/GenericContextMenuSubMenuButtonView.cs b/Explorer/Assets/DCL/UI/GenericContextMenu/Controls/GenericContextMenuSubMenuButtonView.cs index 5c728178a6d..a66e04a6771 100644 --- a/Explorer/Assets/DCL/UI/GenericContextMenu/Controls/GenericContextMenuSubMenuButtonView.cs +++ b/Explorer/Assets/DCL/UI/GenericContextMenu/Controls/GenericContextMenuSubMenuButtonView.cs @@ -1,4 +1,5 @@ using Cysharp.Threading.Tasks; +using DCL.Diagnostics; using DCL.UI.Controls.Configs; using System; using System.Threading; @@ -148,7 +149,8 @@ private async UniTaskVoid WaitAndTriggerExitAsync(CancellationToken token) if (!isHovering) ShowSubmenu(false); } - catch (Exception) { } + catch (OperationCanceledException) { } + catch (Exception e) { ReportHub.LogException(e, ReportCategory.UI); } } } } diff --git a/Explorer/Assets/DCL/UI/GenericContextMenu/GenericContextMenuController.cs b/Explorer/Assets/DCL/UI/GenericContextMenu/GenericContextMenuController.cs index b5383c79f56..117a9f0710b 100644 --- a/Explorer/Assets/DCL/UI/GenericContextMenu/GenericContextMenuController.cs +++ b/Explorer/Assets/DCL/UI/GenericContextMenu/GenericContextMenuController.cs @@ -410,7 +410,6 @@ private Vector3 GetControlsPosition(ControlsContainerView container, Vector2 anc float bestOutOfBoundsPercent = adjustedOutOfBoundsPercent; float3 bestPosition = adjustedPosition; - var foundPerfectPosition = false; for (var i = 0; i < fallbackDirectionsCount; i++) {