Difficulty based sorting and analytics fixes
This commit is contained in:
@@ -9,6 +9,7 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
|
||||
{
|
||||
string Id { get; }
|
||||
string DisplayName { get; }
|
||||
int Difficulty { get; }
|
||||
Sprite DefaultThumbnail { get; }
|
||||
GameObject DrawingPrefab { get; }
|
||||
GameObject ColoringPrefab { get; }
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
|
||||
public const string ShapeAssembled = "shape_assembled";
|
||||
public const string ColorApplied = "color_applied";
|
||||
|
||||
// Progression & content (GA4 recommended level_start / level_complete)
|
||||
// Progression & content. level_start is a GA4-recommended games event; level_complete is our
|
||||
// custom completion marker (GA4's own pair would be level_end + success).
|
||||
public const string LevelStart = "level_start";
|
||||
public const string LevelComplete = "level_complete";
|
||||
public const string AllContentCompleted = "all_content_completed";
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
namespace Darkmatter.Core.Data.Signals.Features.Capture
|
||||
{
|
||||
public record struct GallerySaveCompletedSignal(bool Success);
|
||||
/// <summary>
|
||||
/// TemplateId identifies which drawing was saved; publishers outside gameplay (art book) must set it
|
||||
/// because there is no active drawing for analytics to fall back on there.
|
||||
/// </summary>
|
||||
public record struct GallerySaveCompletedSignal(bool Success, string TemplateId = null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Darkmatter.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// The drawing-catalog (colorbook) screen became visible: scene entry (play / back / next) or
|
||||
/// returning from the art book. Distinct from <see cref="OpenColorBookSignal"/>, which is the
|
||||
/// command to show the view and only fires on one navigation path. Analytics keys colorbook_opened
|
||||
/// and abandoned-drawing detection off this.
|
||||
/// </summary>
|
||||
public record struct ColorbookShownSignal;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa0fb32346ff54da3a9bbfec9f763cf2
|
||||
@@ -12,6 +12,8 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
|
||||
{
|
||||
[SerializeField] private string id;
|
||||
[SerializeField] private string displayName;
|
||||
[SerializeField, Min(0), Tooltip("0 = auto (pieces + regions). Any positive value overrides the computed difficulty.")]
|
||||
private int difficultyOverride;
|
||||
[SerializeField] private Sprite defaultThumbnail;
|
||||
[SerializeField] private GameObject drawingPrefab;
|
||||
[SerializeField] private GameObject coloringPrefab;
|
||||
@@ -22,6 +24,11 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
|
||||
|
||||
public string Id => id;
|
||||
public string DisplayName => displayName;
|
||||
// Workload-based: every shape to place and region to color adds one point.
|
||||
// A positive difficultyOverride replaces the computed value.
|
||||
public int AutoDifficulty => pieces.Count + regions.Count;
|
||||
public int Difficulty => difficultyOverride > 0 ? difficultyOverride : AutoDifficulty;
|
||||
public bool HasDifficultyOverride => difficultyOverride > 0;
|
||||
public Sprite DefaultThumbnail => defaultThumbnail;
|
||||
public GameObject DrawingPrefab => drawingPrefab;
|
||||
public GameObject ColoringPrefab => coloringPrefab;
|
||||
|
||||
@@ -30,6 +30,12 @@ namespace Darkmatter.Features.AppBoot.Flow
|
||||
|
||||
public async UniTask StartAsync(CancellationToken cancellation = default)
|
||||
{
|
||||
// Entry points run in serviceModules registration order and this module is near the front.
|
||||
// Everything below LoadAsync completes synchronously, so without a real yield the
|
||||
// IntroStartedSignal publish would happen before later-registered subscribers
|
||||
// (e.g. AnalyticsTracker) have run Start() — the event bus has no replay, so they'd miss it.
|
||||
await UniTask.Yield();
|
||||
|
||||
#if UNITY_ANDROID || UNITY_IOS
|
||||
Application.targetFrameRate = 60;
|
||||
#endif
|
||||
|
||||
@@ -7,6 +7,7 @@ using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
||||
using Darkmatter.Core.Contracts.Features.Progression;
|
||||
using Darkmatter.Core.Contracts.Features.SaveGate;
|
||||
using Darkmatter.Core.Contracts.Services.Gallery;
|
||||
using Darkmatter.Core.Data.Signals.Features.Capture;
|
||||
using Darkmatter.Core.Data.Signals.Features.Drawing;
|
||||
using Darkmatter.Libs.Observer;
|
||||
using UnityEngine;
|
||||
@@ -209,7 +210,20 @@ namespace Darkmatter.Features.Artbook
|
||||
texture = await _progression.GetCachedThumbnailAsync(entry.Value.Id);
|
||||
if (texture == null) return;
|
||||
|
||||
await _gallery.SaveImageAsync(texture, entry.Value.Name, ct);
|
||||
// Mirror CaptureSystem's pairing: Started before the write, Completed(success) in
|
||||
// finally — art-book saves land in the same gallery_save_* analytics funnel as
|
||||
// gameplay saves.
|
||||
_eventBus.Publish(new GallerySaveStartedSignal());
|
||||
var success = false;
|
||||
try
|
||||
{
|
||||
await _gallery.SaveImageAsync(texture, entry.Value.Name, ct);
|
||||
success = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_eventBus.Publish(new GallerySaveCompletedSignal(success, entry.Value.Id));
|
||||
}
|
||||
// The art book has no success popup of its own, so use the shared toast.
|
||||
await _saveGate.ShowSavedAsync(ct);
|
||||
}
|
||||
|
||||
@@ -60,12 +60,14 @@ namespace Darkmatter.Features.Capture
|
||||
|
||||
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
|
||||
var success = false;
|
||||
// Started must pair 1:1 with the Completed publish in finally — a failed LoadImage or a
|
||||
// throw in BuildFileNameAsync would otherwise emit Completed(false) with no Started.
|
||||
_bus.Publish(new GallerySaveStartedSignal());
|
||||
try
|
||||
{
|
||||
if (tex.LoadImage(png))
|
||||
{
|
||||
var fileName = await BuildFileNameAsync();
|
||||
_bus.Publish(new GallerySaveStartedSignal());
|
||||
await _galleryService.SaveImageAsync(tex, fileName, ct);
|
||||
success = true;
|
||||
}
|
||||
@@ -73,7 +75,7 @@ namespace Darkmatter.Features.Capture
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.Destroy(tex);
|
||||
_bus.Publish(new GallerySaveCompletedSignal(success));
|
||||
_bus.Publish(new GallerySaveCompletedSignal(success, _progression.LastOpenedTemplateId));
|
||||
}
|
||||
return png;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,13 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
|
||||
// one VContainer cancels on teardown, and the linked _scopeCts may lag the callback order.
|
||||
if (cancellation.IsCancellationRequested || ct.IsCancellationRequested) return;
|
||||
|
||||
if (!_navigatingToGameplay) _loadingScreen.Hide();
|
||||
if (!_navigatingToGameplay)
|
||||
{
|
||||
_loadingScreen.Hide();
|
||||
// Catalog genuinely on screen — not a transit hop toward gameplay (edit/next relays land
|
||||
// in this scene with _navigatingToGameplay already latched and never reveal the catalog).
|
||||
_bus.Publish(new ColorbookShownSignal());
|
||||
}
|
||||
|
||||
PrewarmInterstitialAdAsync().Forget();
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ public sealed class DrawingCatalogController : IDrawingCatalogController
|
||||
private void Refresh()
|
||||
{
|
||||
_visible.Clear();
|
||||
// Catalog already orders ids by difficulty (easiest first), then id.
|
||||
_visible.AddRange(_catalog.AllTemplateIds);
|
||||
_visible.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
ListChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,14 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
}
|
||||
|
||||
private void HandleOpenColorBookSignal(OpenColorBookSignal signal)
|
||||
{
|
||||
ShowCatalog();
|
||||
}
|
||||
|
||||
private void ShowCatalog()
|
||||
{
|
||||
_view.Show();
|
||||
_eventBus.Publish(new ColorbookShownSignal());
|
||||
}
|
||||
|
||||
private void OnRightPageBtnClicked()
|
||||
@@ -84,7 +90,7 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
|
||||
private void OnArtBookBtnClicked()
|
||||
{
|
||||
_eventBus.Publish(new OpenArtBookSignal(()=> _view.Show()));
|
||||
_eventBus.Publish(new OpenArtBookSignal(ShowCatalog));
|
||||
_view.Hide();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
|
||||
_templates.Clear();
|
||||
_shapes.Clear();
|
||||
_palettes.Clear();
|
||||
_templates.AddRange(FindAllOfType<DrawingTemplateSO>());
|
||||
_templates.AddRange(FindAllOfType<DrawingTemplateSO>()
|
||||
.OrderBy(t => t.Difficulty)
|
||||
.ThenBy(t => t.Id, StringComparer.OrdinalIgnoreCase));
|
||||
_shapes.AddRange(FindAllOfType<ShapeSO>());
|
||||
_palettes.AddRange(FindAllOfType<ColorPaletteSO>());
|
||||
}
|
||||
@@ -169,7 +171,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
GUILayout.Label(string.IsNullOrEmpty(t.DisplayName) ? t.name : t.DisplayName, EditorStyles.boldLabel);
|
||||
GUILayout.Label("id: " + (string.IsNullOrEmpty(t.Id) ? "<unset>" : t.Id), EditorStyles.miniLabel);
|
||||
GUILayout.Label($"id: {(string.IsNullOrEmpty(t.Id) ? "<unset>" : t.Id)} • diff: {t.Difficulty}{(t.HasDifficultyOverride ? "*" : $" ({t.Pieces.Count}p+{t.AuthoredRegions.Count}r)")}", EditorStyles.miniLabel);
|
||||
}
|
||||
if (GUILayout.Button("Select", GUILayout.Width(54)))
|
||||
Select(t);
|
||||
@@ -206,6 +208,13 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
|
||||
EditorGUILayout.LabelField("Basics", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(so.FindProperty("id"), new GUIContent("Id (Addressable Key)"));
|
||||
EditorGUILayout.PropertyField(so.FindProperty("displayName"));
|
||||
EditorGUILayout.PropertyField(so.FindProperty("difficultyOverride"),
|
||||
new GUIContent("Difficulty Override", "0 = auto (pieces + regions). Any positive value overrides."));
|
||||
EditorGUILayout.LabelField(
|
||||
new GUIContent("Difficulty (effective)", "Sorts the catalog, lowest first."),
|
||||
new GUIContent(t.HasDifficultyOverride
|
||||
? $"{t.Difficulty} (override; auto would be {t.AutoDifficulty})"
|
||||
: $"{t.Difficulty} (auto: {t.Pieces.Count} pieces + {t.AuthoredRegions.Count} regions)"));
|
||||
EditorGUILayout.PropertyField(so.FindProperty("defaultThumbnail"));
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
@@ -48,7 +49,7 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
|
||||
_byId[t.Id] = t;
|
||||
}
|
||||
|
||||
_allIds.Sort();
|
||||
SortIds();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -84,12 +85,22 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
|
||||
if (!_allIds.Contains(id))
|
||||
{
|
||||
_allIds.Add(id);
|
||||
_allIds.Sort();
|
||||
SortIds();
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
// Easiest drawings first; ties broken by id so the order is stable.
|
||||
private void SortIds()
|
||||
{
|
||||
_allIds.Sort((a, b) =>
|
||||
{
|
||||
var byDifficulty = _byId[a].Difficulty.CompareTo(_byId[b].Difficulty);
|
||||
return byDifficulty != 0 ? byDifficulty : string.Compare(a, b, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
public void ReleaseAll()
|
||||
{
|
||||
if (!_initialized) return;
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core;
|
||||
using Darkmatter.Core.Contracts.Features.Capture;
|
||||
using Darkmatter.Core.Contracts.Features.Coloring;
|
||||
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
||||
@@ -28,6 +29,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
public sealed class GameplayFlowController : IAsyncStartable, IGameplayFlowController, IDisposable
|
||||
{
|
||||
private const int AutosaveDebounceMs = 500;
|
||||
private const float ThumbnailMinIntervalSec = 30f;
|
||||
|
||||
private readonly IProgressionSystem _progression;
|
||||
private readonly IDrawingTemplateCatalog _catalog;
|
||||
@@ -44,10 +46,12 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
private string _templateId;
|
||||
private DrawingPhase _phase = DrawingPhase.ShapeBuilding;
|
||||
private bool _allContentReported;
|
||||
private float _lastThumbnailTime = float.NegativeInfinity;
|
||||
|
||||
private IDisposable _assembledSub;
|
||||
private IDisposable _colorAppliedSub;
|
||||
private IDisposable _drawingSelectedSub;
|
||||
private IDisposable _openColorbookSub;
|
||||
private CancellationTokenSource _autosaveCts;
|
||||
private CancellationTokenSource _scopeCts;
|
||||
|
||||
@@ -104,6 +108,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
|
||||
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
|
||||
_drawingSelectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
|
||||
// The art book's "colorbook" button publishes OpenColorBookSignal. In the Colorbook
|
||||
// scene DrawingCatalogPresenter shows the catalog; during gameplay that presenter is
|
||||
// dead, so treat the signal as "leave gameplay for the colorbook" (same as back).
|
||||
_openColorbookSub = _bus.Subscribe<OpenColorBookSignal>(OnOpenColorBook);
|
||||
|
||||
_loadingScreen.SetProgress(1f);
|
||||
if (_phase == DrawingPhase.Coloring)
|
||||
@@ -131,8 +139,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
|
||||
public async UniTask SaveAsync(CancellationToken ct)
|
||||
{
|
||||
await SaveCurrentAsync(ct);
|
||||
await _capture.CapturePngAsync(saveToGallery: true, ct);
|
||||
// One capture serves both the gallery write and the progress thumbnail — this used to
|
||||
// run the full capture pipeline twice back to back.
|
||||
var png = await _capture.CapturePngAsync(saveToGallery: true, ct);
|
||||
await SaveCurrentAsync(ct, captureThumbnail: false, precapturedThumbnail: png);
|
||||
}
|
||||
|
||||
public async UniTask NextAsync(CancellationToken ct)
|
||||
@@ -209,6 +219,11 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
|
||||
}
|
||||
|
||||
private void OnOpenColorBook(OpenColorBookSignal _)
|
||||
{
|
||||
BackAsync(CancellationToken.None).Forget();
|
||||
}
|
||||
|
||||
private void OnDrawingSelected(DrawingSelectedSignal signal)
|
||||
{
|
||||
if (string.IsNullOrEmpty(signal.TemplateId)) return;
|
||||
@@ -232,7 +247,13 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
try
|
||||
{
|
||||
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
|
||||
await SaveCurrentAsync(ct);
|
||||
// Autosaves fire after every paint pause; capturing a thumbnail each time renders the
|
||||
// scene to an RT and PNG-encodes it, which OOM-kills long sessions on low-RAM devices.
|
||||
// Colors always persist; the thumbnail refreshes at most every ThumbnailMinIntervalSec
|
||||
// here and unconditionally on the exit paths (back / next / select-away).
|
||||
bool refreshThumbnail =
|
||||
Time.realtimeSinceStartup - _lastThumbnailTime >= ThumbnailMinIntervalSec;
|
||||
await SaveCurrentAsync(ct, captureThumbnail: refreshThumbnail);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -240,7 +261,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask SaveCurrentAsync(CancellationToken ct)
|
||||
private async UniTask SaveCurrentAsync(CancellationToken ct, bool captureThumbnail = true,
|
||||
byte[] precapturedThumbnail = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateId)) return;
|
||||
|
||||
@@ -263,7 +285,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
FirstCompletedUtc = existing?.FirstCompletedUtc,
|
||||
};
|
||||
|
||||
var thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
|
||||
byte[] thumbnailPng = precapturedThumbnail;
|
||||
if (thumbnailPng == null && captureThumbnail)
|
||||
thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
|
||||
if (thumbnailPng != null) _lastThumbnailTime = Time.realtimeSinceStartup;
|
||||
|
||||
await _progression.SaveProgressAsync(progress, thumbnailPng);
|
||||
}
|
||||
@@ -284,6 +309,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_assembledSub?.Dispose();
|
||||
_colorAppliedSub?.Dispose();
|
||||
_drawingSelectedSub?.Dispose();
|
||||
_openColorbookSub?.Dispose();
|
||||
_autosaveCts?.Cancel();
|
||||
_autosaveCts?.Dispose();
|
||||
_scopeCts?.Cancel();
|
||||
|
||||
@@ -56,8 +56,9 @@ namespace Darkmatter.Services.Analytics
|
||||
_subs.Add(_bus.Subscribe<IntroCompletedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.IntroCompleted)));
|
||||
_subs.Add(_bus.Subscribe<PlayBtnClickedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.PlayClicked)));
|
||||
|
||||
// Navigation
|
||||
_subs.Add(_bus.Subscribe<OpenColorBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ColorbookOpened)));
|
||||
// Navigation. ColorbookShownSignal (not OpenColorBookSignal, which is the show-view command
|
||||
// and misses scene-entry paths) fires on every way the catalog becomes visible.
|
||||
_subs.Add(_bus.Subscribe<ColorbookShownSignal>(_ => OnColorbookShown()));
|
||||
_subs.Add(_bus.Subscribe<OpenArtBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ArtbookOpened)));
|
||||
_subs.Add(_bus.Subscribe<ReturnToMainMenuSignal>(_ => OnReturnToMainMenu()));
|
||||
|
||||
@@ -90,6 +91,10 @@ namespace Darkmatter.Services.Analytics
|
||||
|
||||
private void OnDrawingSelected(string drawingId)
|
||||
{
|
||||
// GameplayFlowController relays the selection a second time after the scene swap
|
||||
// (artbook edit path) — same id while already active is that relay, not a new selection.
|
||||
if (drawingId == _activeDrawingId) return;
|
||||
|
||||
// A new selection while a drawing is still open (uncompleted) = the previous one was abandoned.
|
||||
EndActiveDrawingIfAbandoned();
|
||||
|
||||
@@ -156,7 +161,7 @@ namespace Darkmatter.Services.Analytics
|
||||
if (dur >= 0f) p[AnalyticsParams.DurationSeconds] = dur;
|
||||
_analytics.LogEvent(AnalyticsEvents.DrawingCompleted, p);
|
||||
|
||||
// GA4 recommended games event (lights up built-in level funnels).
|
||||
// Companion to GA4's level_start (custom name — GA4's own pair would be level_end).
|
||||
_analytics.LogEvent(AnalyticsEvents.LevelComplete, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.LevelName] = s.TemplateId,
|
||||
@@ -178,9 +183,11 @@ namespace Darkmatter.Services.Analytics
|
||||
private void OnGallerySaveCompleted(GallerySaveCompletedSignal s)
|
||||
{
|
||||
_analytics.LogEvent(AnalyticsEvents.GallerySaveCompleted, AnalyticsParams.Success, s.Success ? "true" : "false");
|
||||
// Prefer the id carried by the signal: art-book saves happen with no active drawing.
|
||||
string drawingId = string.IsNullOrEmpty(s.TemplateId) ? _activeDrawingId ?? string.Empty : s.TemplateId;
|
||||
_analytics.LogEvent(AnalyticsEvents.DrawingSaved, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.DrawingId] = _activeDrawingId ?? string.Empty,
|
||||
[AnalyticsParams.DrawingId] = drawingId,
|
||||
[AnalyticsParams.Success] = s.Success ? 1 : 0,
|
||||
});
|
||||
}
|
||||
@@ -191,6 +198,15 @@ namespace Darkmatter.Services.Analytics
|
||||
_analytics.LogEvent(AnalyticsEvents.MainMenuReturned);
|
||||
}
|
||||
|
||||
private void OnColorbookShown()
|
||||
{
|
||||
// Reaching the catalog with a drawing still open means the player backed out of gameplay
|
||||
// mid-drawing — the abandon moment. (Completion clears the active drawing before the
|
||||
// catalog reloads, so the finish path never lands here with one open.)
|
||||
EndActiveDrawingIfAbandoned();
|
||||
_analytics.LogEvent(AnalyticsEvents.ColorbookOpened);
|
||||
}
|
||||
|
||||
// Emits drawing_abandoned for an open, uncompleted drawing. Completion clears _activeDrawingId, so
|
||||
// reaching here with a non-null id means the drawing was left without finishing — the leak signal.
|
||||
private void EndActiveDrawingIfAbandoned()
|
||||
|
||||
@@ -68,44 +68,36 @@ namespace Darkmatter.Services.Capture
|
||||
|
||||
var prevActive = RenderTexture.active;
|
||||
RenderTexture.active = rt;
|
||||
var fullScreen = new Texture2D(sw, sh, TextureFormat.RGBA32, mipChain: false);
|
||||
fullScreen.ReadPixels(new Rect(0, 0, sw, sh), 0, 0);
|
||||
fullScreen.Apply();
|
||||
// Read only the crop region straight off the RT. A full-screen ReadPixels followed by
|
||||
// GetPixels/SetPixels cropping allocated a managed Color[] at 16 bytes/pixel (tens of MB)
|
||||
// on every capture — autosave runs this after each paint, OOM-killing low-RAM devices.
|
||||
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
|
||||
cropped.ReadPixels(new Rect(crop.x, crop.y, cropW, cropH), 0, 0);
|
||||
cropped.Apply();
|
||||
RenderTexture.active = prevActive;
|
||||
captureViewScope?.Dispose();
|
||||
captureViewScope = null;
|
||||
|
||||
try
|
||||
{
|
||||
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
|
||||
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
|
||||
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
|
||||
Texture2D output = cropped;
|
||||
if (output.width != targetW || output.height != targetH)
|
||||
output = Resize(cropped, targetW, targetH);
|
||||
|
||||
try
|
||||
{
|
||||
cropped.SetPixels(fullScreen.GetPixels((int)crop.x, (int)crop.y, cropW, cropH));
|
||||
cropped.Apply();
|
||||
|
||||
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
|
||||
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
|
||||
Texture2D output = cropped;
|
||||
if (output.width != targetW || output.height != targetH)
|
||||
output = Resize(cropped, targetW, targetH);
|
||||
|
||||
try
|
||||
{
|
||||
return output.EncodeToPNG();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (output != cropped) Object.Destroy(output);
|
||||
}
|
||||
return output.EncodeToPNG();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.Destroy(cropped);
|
||||
if (output != cropped) Object.Destroy(output);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.Destroy(fullScreen);
|
||||
Object.Destroy(cropped);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
Reference in New Issue
Block a user