Crash fixes

This commit is contained in:
Savya Bikram Shah
2026-06-26 18:18:49 +05:45
parent 21e5206626
commit b28e1f637d
11 changed files with 161 additions and 940 deletions

View File

@@ -1,3 +1,4 @@
using System;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
@@ -7,6 +8,6 @@ namespace Darkmatter.Core.Contracts.Services.Capture
public interface ICaptureService public interface ICaptureService
{ {
UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale, UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
CancellationToken cancellationToken = default); Func<IDisposable> captureViewScopeFactory = null, CancellationToken cancellationToken = default);
} }
} }

View File

@@ -1,7 +1,6 @@
using System; using System;
using UnityEngine;
namespace Darkmatter.Features.Artbook namespace Darkmatter.Features.Artbook
{ {
public record struct ArtbookEntry(string Id, string Name, Texture2D Thumbnail, DateTime UpdatedUtc); public record struct ArtbookEntry(string Id, string Name, DateTime UpdatedUtc);
} }

View File

@@ -26,12 +26,13 @@ namespace Darkmatter.Features.Artbook
private readonly IRewardedSaveGate _saveGate; private readonly IRewardedSaveGate _saveGate;
private readonly List<ArtbookEntry> _entries = new(); private readonly List<ArtbookEntry> _entries = new();
private readonly List<Sprite> _ownedSprites = new(); private readonly List<Sprite> _visibleSprites = new();
private readonly List<Texture2D> _ownedTextures = new(); private readonly List<Texture2D> _visibleTextures = new();
private Action _onClose; private Action _onClose;
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private IDisposable _openSubscription; private IDisposable _openSubscription;
private int _currentSpread; private int _currentSpread;
private int _renderVersion;
public ArtbookPresenter( public ArtbookPresenter(
ArtbookView view, ArtbookView view,
@@ -75,7 +76,9 @@ namespace Darkmatter.Features.Artbook
private async UniTaskVoid LoadAndShowAsync(CancellationToken ct) private async UniTaskVoid LoadAndShowAsync(CancellationToken ct)
{ {
ClearOwnedAssets(); try
{
ClearVisibleAssets();
_entries.Clear(); _entries.Clear();
_currentSpread = 0; _currentSpread = 0;
@@ -89,20 +92,25 @@ namespace Darkmatter.Features.Artbook
var progress = _progression.GetProgress(id); var progress = _progression.GetProgress(id);
if (progress is not { hasThumbnail: true }) continue; if (progress is not { hasThumbnail: true }) continue;
var texture = await _progression.GetCachedThumbnailAsync(id);
if (ct.IsCancellationRequested) return;
if (texture == null) continue;
var template = await _catalog.LoadAsync(id); var template = await _catalog.LoadAsync(id);
if (ct.IsCancellationRequested) return; if (ct.IsCancellationRequested) return;
if (template == null) continue;
_ownedTextures.Add(texture); _entries.Add(new ArtbookEntry(id, template.DisplayName, progress.Value.UpdatedUtc));
_entries.Add(new ArtbookEntry(id, template.DisplayName, texture, progress.Value.UpdatedUtc));
} }
_entries.Sort((a, b) => b.UpdatedUtc.CompareTo(a.UpdatedUtc)); _entries.Sort((a, b) => b.UpdatedUtc.CompareTo(a.UpdatedUtc));
RenderSpread(); RenderSpread();
} }
catch (OperationCanceledException)
{
// Opening/closing the art book can cancel the load at any await point.
}
catch (Exception e)
{
Debug.LogError($"[Artbook] Failed to load entries: {e}");
}
}
private void HandlePrevSpreadClicked() private void HandlePrevSpreadClicked()
{ {
@@ -122,7 +130,7 @@ namespace Darkmatter.Features.Artbook
private void RenderSpread() private void RenderSpread()
{ {
_view.SetSpread(GetLeftEntry(), SpriteFor(GetLeftEntry()), GetRightEntry(), SpriteFor(GetRightEntry())); RenderSpreadAsync(++_renderVersion, _cts?.Token ?? CancellationToken.None).Forget();
_view.SetNavigation(_currentSpread > 0, _currentSpread < TotalSpreads - 1); _view.SetNavigation(_currentSpread > 0, _currentSpread < TotalSpreads - 1);
} }
@@ -138,13 +146,51 @@ namespace Darkmatter.Features.Artbook
return idx < _entries.Count ? _entries[idx] : null; return idx < _entries.Count ? _entries[idx] : null;
} }
private Sprite SpriteFor(ArtbookEntry? entry) private async UniTaskVoid RenderSpreadAsync(int version, CancellationToken ct)
{ {
if (!entry.HasValue) return null; var left = GetLeftEntry();
var tex = entry.Value.Thumbnail; var right = GetRightEntry();
ClearVisibleAssets();
_view.SetSpread(left, null, right, null);
try
{
var leftSprite = await SpriteForAsync(left, version, ct);
var rightSprite = await SpriteForAsync(right, version, ct);
if (ct.IsCancellationRequested || version != _renderVersion)
{
return;
}
_view.SetSpread(left, leftSprite, right, rightSprite);
}
catch (OperationCanceledException)
{
if (version == _renderVersion) ClearVisibleAssets();
}
catch (Exception e)
{
if (version == _renderVersion) ClearVisibleAssets();
Debug.LogError($"[Artbook] Failed to render spread: {e}");
}
}
private async UniTask<Sprite> SpriteForAsync(ArtbookEntry? entry, int version, CancellationToken ct)
{
if (!entry.HasValue || ct.IsCancellationRequested || version != _renderVersion) return null;
var tex = await _progression.GetCachedThumbnailAsync(entry.Value.Id);
if (ct.IsCancellationRequested || version != _renderVersion)
{
if (tex != null) UnityEngine.Object.Destroy(tex);
return null;
}
if (tex == null) return null; if (tex == null) return null;
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f); var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
_ownedSprites.Add(sprite); _visibleTextures.Add(tex);
_visibleSprites.Add(sprite);
return sprite; return sprite;
} }
@@ -153,14 +199,33 @@ namespace Darkmatter.Features.Artbook
private async UniTaskVoid SaveToGalleryAsync(ArtbookEntry? entry) private async UniTaskVoid SaveToGalleryAsync(ArtbookEntry? entry)
{ {
if (!entry.HasValue || entry.Value.Thumbnail == null) return; Texture2D texture = null;
try
{
if (!entry.HasValue) return;
var ct = _cts?.Token ?? CancellationToken.None; var ct = _cts?.Token ?? CancellationToken.None;
// Same kid-friendly prompt + rewarded ad as the gameplay save button. // Same kid-friendly prompt + rewarded ad as the gameplay save button.
if (!await _saveGate.RequestSaveAsync(ct)) return; if (!await _saveGate.RequestSaveAsync(ct)) return;
await _gallery.SaveImageAsync(entry.Value.Thumbnail, entry.Value.Name, ct); texture = await _progression.GetCachedThumbnailAsync(entry.Value.Id);
if (texture == null) return;
await _gallery.SaveImageAsync(texture, entry.Value.Name, ct);
// The art book has no success popup of its own, so use the shared toast. // The art book has no success popup of its own, so use the shared toast.
await _saveGate.ShowSavedAsync(ct); await _saveGate.ShowSavedAsync(ct);
} }
catch (OperationCanceledException)
{
// The art book was closed or replaced while saving.
}
catch (Exception e)
{
Debug.LogError($"[Artbook] Failed to save page: {e}");
}
finally
{
if (texture != null) UnityEngine.Object.Destroy(texture);
}
}
private void HandleLeftEditClicked() => OpenForEdit(GetLeftEntry()); private void HandleLeftEditClicked() => OpenForEdit(GetLeftEntry());
private void HandleRightEditClicked() => OpenForEdit(GetRightEntry()); private void HandleRightEditClicked() => OpenForEdit(GetRightEntry());
@@ -169,27 +234,43 @@ namespace Darkmatter.Features.Artbook
{ {
if (!entry.HasValue) return; if (!entry.HasValue) return;
_eventBus.Publish(new DrawingSelectedSignal(entry.Value.Id)); _eventBus.Publish(new DrawingSelectedSignal(entry.Value.Id));
StopActiveWork();
_view.Hide(); _view.Hide();
} }
private void HandleBackButtonClicked() private void HandleBackButtonClicked()
{ {
_onClose?.Invoke(); _onClose?.Invoke();
StopActiveWork();
_view.Hide(); _view.Hide();
} }
private void HandleColorbookButtonClicked() private void HandleColorbookButtonClicked()
{ {
_eventBus.Publish(new OpenColorBookSignal()); _eventBus.Publish(new OpenColorBookSignal());
StopActiveWork();
_view.Hide(); _view.Hide();
} }
private void StopActiveWork()
{
_renderVersion++;
_cts?.Cancel();
_view.SetSpread(null, null, null, null);
ClearVisibleAssets();
}
private void ClearOwnedAssets() private void ClearOwnedAssets()
{ {
foreach (var s in _ownedSprites) UnityEngine.Object.Destroy(s); ClearVisibleAssets();
foreach (var t in _ownedTextures) UnityEngine.Object.Destroy(t); }
_ownedSprites.Clear();
_ownedTextures.Clear(); private void ClearVisibleAssets()
{
foreach (var s in _visibleSprites) UnityEngine.Object.Destroy(s);
foreach (var t in _visibleTextures) UnityEngine.Object.Destroy(t);
_visibleSprites.Clear();
_visibleTextures.Clear();
} }
public void Dispose() public void Dispose()

View File

@@ -51,12 +51,11 @@ namespace Darkmatter.Features.Capture
// A null result keeps the existing thumbnail and skips the gallery write (both no-op on null). // A null result keeps the existing thumbnail and skips the gallery write (both no-op on null).
if (_coloring.IsPlayingCompletionAnimation) return null; if (_coloring.IsPlayingCompletionAnimation) return null;
byte[] png; var png = await _captureService.CapturePngAsync(
using (_coloring.UseNonZoomedView()) _refs.PaperRoot.gameObject,
{ _config.CaptureScale,
png = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, _config.CaptureScale, ct); _coloring.UseNonZoomedView,
} ct);
if (!saveToGallery || png == null || png.Length == 0) return png; if (!saveToGallery || png == null || png.Length == 0) return png;
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false); var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
@@ -5,6 +6,7 @@ using Darkmatter.Core.Contracts.Services.Camera;
using Darkmatter.Core.Contracts.Services.Capture; using Darkmatter.Core.Contracts.Services.Capture;
using UnityEngine; using UnityEngine;
using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType; using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType;
using Object = UnityEngine.Object;
namespace Darkmatter.Services.Capture namespace Darkmatter.Services.Capture
{ {
@@ -15,7 +17,7 @@ namespace Darkmatter.Services.Capture
public CaptureService(ICameraService cameraService) => _cameraService = cameraService; public CaptureService(ICameraService cameraService) => _cameraService = cameraService;
public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale, public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
CancellationToken cancellationToken = default) Func<IDisposable> captureViewScopeFactory = null, CancellationToken cancellationToken = default)
{ {
if (captureObject == null) return null; if (captureObject == null) return null;
var paperRT = captureObject.transform as RectTransform; var paperRT = captureObject.transform as RectTransform;
@@ -26,10 +28,6 @@ namespace Darkmatter.Services.Capture
int sw = Screen.width; int sw = Screen.width;
int sh = Screen.height; int sh = Screen.height;
Rect crop = ComputeCropRect(captureObject, sw, sh);
int cropW = Mathf.Max(1, (int)crop.width);
int cropH = Mathf.Max(1, (int)crop.height);
var prevFlags = cam.clearFlags; var prevFlags = cam.clearFlags;
var prevBg = cam.backgroundColor; var prevBg = cam.backgroundColor;
var prevTarget = cam.targetTexture; var prevTarget = cam.targetTexture;
@@ -41,15 +39,22 @@ namespace Darkmatter.Services.Capture
List<Canvas> disabledCanvases = null; List<Canvas> disabledCanvases = null;
List<UnityEngine.UI.Graphic> disabledGraphics = null; List<UnityEngine.UI.Graphic> disabledGraphics = null;
IDisposable captureViewScope = null;
try try
{ {
await UniTask.WaitForEndOfFrame(cancellationToken); await UniTask.WaitForEndOfFrame(cancellationToken);
captureViewScope = captureViewScopeFactory?.Invoke();
disabledCanvases = DisableOtherRootCanvases(paperCanvas); disabledCanvases = DisableOtherRootCanvases(paperCanvas);
disabledGraphics = HideNonPaperGraphics(paperRT); disabledGraphics = HideNonPaperGraphics(paperRT);
HideBackdropGraphics(paperRT, disabledGraphics); HideBackdropGraphics(paperRT, disabledGraphics);
Rect crop = ComputeCropRect(captureObject, sw, sh);
int cropW = Mathf.Max(1, (int)crop.width);
int cropH = Mathf.Max(1, (int)crop.height);
cam.clearFlags = CameraClearFlags.SolidColor; cam.clearFlags = CameraClearFlags.SolidColor;
cam.backgroundColor = new Color(0f, 0f, 0f, 0f); cam.backgroundColor = new Color(0f, 0f, 0f, 0f);
cam.targetTexture = rt; cam.targetTexture = rt;
@@ -67,6 +72,8 @@ namespace Darkmatter.Services.Capture
fullScreen.ReadPixels(new Rect(0, 0, sw, sh), 0, 0); fullScreen.ReadPixels(new Rect(0, 0, sw, sh), 0, 0);
fullScreen.Apply(); fullScreen.Apply();
RenderTexture.active = prevActive; RenderTexture.active = prevActive;
captureViewScope?.Dispose();
captureViewScope = null;
try try
{ {
@@ -118,6 +125,7 @@ namespace Darkmatter.Services.Capture
foreach (var c in disabledCanvases) foreach (var c in disabledCanvases)
if (c != null) if (c != null)
c.enabled = true; c.enabled = true;
captureViewScope?.Dispose();
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -2689,7 +2689,7 @@ MonoBehaviour:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0} m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1 m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:

View File

@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><resources><string name="com.crashlytics.android.build_id" translatable="false">7f32d9eb-b712-4616-ad93-07615d514802</string></resources> <?xml version="1.0" encoding="utf-8"?><resources><string name="com.crashlytics.android.build_id" translatable="false">8fc48ed6-41de-4dcf-a980-9aa3316fad11</string></resources>

View File

@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><resources><string name="com.google.firebase.crashlytics.unity_version" translatable="false">6000.4.5f1</string></resources> <?xml version="1.0" encoding="utf-8"?><resources><string name="com.google.firebase.crashlytics.unity_version" translatable="false">6000.5.1f1</string></resources>

View File

@@ -18,12 +18,15 @@ MonoBehaviour:
m_PlatformId: b9b35072a6f44c2e863f17467ea3dc13 m_PlatformId: b9b35072a6f44c2e863f17467ea3dc13
m_PlatformBuildProfile: m_PlatformBuildProfile:
rid: 570818657416118487 rid: 570818657416118487
m_ActivePlatformGuid:
m_AdditionalPlatformBuildSettings: []
m_OverrideGlobalSceneList: 0 m_OverrideGlobalSceneList: 0
m_Scenes: [] m_Scenes: []
m_HasScriptingDefines: 0 m_HasScriptingDefines: 0
m_ScriptingDefines: [] m_ScriptingDefines: []
m_PlayerSettingsYaml: m_PlayerSettingsYaml:
m_Settings: [] m_Settings: []
requiredComponents: []
references: references:
version: 2 version: 2
RefIds: RefIds:
@@ -33,6 +36,7 @@ MonoBehaviour:
m_Development: 0 m_Development: 0
m_ConnectProfiler: 0 m_ConnectProfiler: 0
m_BuildWithDeepProfilingSupport: 0 m_BuildWithDeepProfilingSupport: 0
m_BuildWithCodeCoverage: 0
m_AllowDebugging: 0 m_AllowDebugging: 0
m_WaitForManagedDebugger: 0 m_WaitForManagedDebugger: 0
m_ManagedDebuggerFixedPort: 0 m_ManagedDebuggerFixedPort: 0

View File

@@ -148,7 +148,7 @@ PlayerSettings:
loadStoreDebugModeEnabled: 0 loadStoreDebugModeEnabled: 0
visionOSBundleVersion: 1.0 visionOSBundleVersion: 1.0
tvOSBundleVersion: 1.0 tvOSBundleVersion: 1.0
bundleVersion: 1.8 bundleVersion: 1.9
preloadedAssets: preloadedAssets:
- {fileID: 11400000, guid: cc969dbecb228fa49b16da9273753a8f, type: 2} - {fileID: 11400000, guid: cc969dbecb228fa49b16da9273753a8f, type: 2}
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3} - {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
@@ -180,7 +180,7 @@ PlayerSettings:
iPhone: 1 iPhone: 1
tvOS: 0 tvOS: 0
overrideDefaultApplicationIdentifier: 1 overrideDefaultApplicationIdentifier: 1
AndroidBundleVersionCode: 8 AndroidBundleVersionCode: 9
AndroidMinSdkVersion: 26 AndroidMinSdkVersion: 26
AndroidTargetSdkVersion: 0 AndroidTargetSdkVersion: 0
AndroidPreferredInstallLocation: 1 AndroidPreferredInstallLocation: 1