using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using UnityEditor; using UnityEditor.U2D.Tooling.Analyzer; using UnityEngine; using UnityEngine.U2D; namespace SampleReport { /// /// Provides functionality to capture, save, and load Sprite asset data for reporting purposes. /// class SpriteDataSource : IReportDataSource { /// Stores the current Sprite capture data. SpriteCaptureData m_Capture = new(); /// Indicates whether the capture process should be cancelled. bool m_Cancel; /// The task responsible for capturing Sprite data asynchronously. Task m_CaptureTask; /// /// Starts capturing Sprite data from the specified asset search paths. /// /// Array of asset search paths. public async void Capture(string[] assetSearchPath) { m_Cancel = false; capturing = true; onCaptureStart?.Invoke(this); m_CaptureTask = GetSpriteData(m_Capture, assetSearchPath); await m_CaptureTask; m_Capture = m_CaptureTask.Result; capturing = false; onCaptureEnd?.Invoke(this); onDataSourceChanged?.Invoke(this); } /// /// Requests to stop the ongoing capture process. /// public void StopCapture() { m_Cancel = true; } /// /// Event triggered when the data source changes. /// public event Action onDataSourceChanged; /// /// Event triggered when capture starts. /// public event Action onCaptureStart; /// /// Event triggered when capture ends. /// public event Action onCaptureEnd; /// /// Gets a value indicating whether a capture is in progress. /// public bool capturing { get; private set; } /// /// Disposes the data source and cancels any ongoing capture. /// public void Dispose() { m_Cancel = true; if (m_CaptureTask != null) { var _ = m_CaptureTask.Result; } } /// /// Saves the current capture data to the specified save file. /// /// The save file to store data in. public void Save(ISaveFile saveData) { saveData.AddSaveData(m_Capture); } /// /// Loads capture data from the specified save file. /// /// The save file to load data from. public void Load(ISaveFile saveData) { List saveDataList = new (); saveData.GetSaveData(saveDataList); if (saveDataList.Count > 0) m_Capture = saveDataList[0]; else m_Capture = new (); onDataSourceChanged?.Invoke(this); } /// /// Gets the name of the data source. /// public string name => "Sprite Report Data Source"; /// /// Gets the last time the data source captured data. /// public long lastCaptureTime => m_Capture.lastCaptureTime; /// /// Gets the list of captured sprite assets. /// public List data => m_Capture.spriteData; /// /// Asynchronously captures sprite data from the specified asset search paths. /// /// The previous capture data for comparison. /// Array of asset search paths. /// A task representing the asynchronous operation, with the captured data as result. async Task GetSpriteData(SpriteCaptureData prevCapture, string[] assetSearchPath) { int id = Progress.Start("Sprite Data Capture"); var capture = new SpriteCaptureData(); string[] guids = AssetDatabase.FindAssets("t:Sprite", assetSearchPath); HashSet pathVisited = new (); for (int i = 0; i < guids.Length && !m_Cancel; ++i) { Progress.Report(id, i, guids.Length, "Capturing sprite data"); var path = AssetDatabase.GUIDToAssetPath(guids[i]); if (!pathVisited.Add(path)) continue; SpriteAssets spriteAssets = null; for(int j = 0; j < prevCapture.spriteData.Count; j++) { if (prevCapture.spriteData[j].assetPathGuid == guids[i]) { spriteAssets = prevCapture.spriteData[j]; break; } } if (!HasAssetChanged(spriteAssets, path)) { capture.spriteData.Add(spriteAssets); continue; } var sprites = AssetDatabase.LoadAllAssetsAtPath(path); spriteAssets = new SpriteAssets() { assetPathGuid = guids[i], fileModifiedTime = File.GetLastWriteTimeUtc(path).ToFileTimeUtc(), metaFileModifiedTime = File.GetLastWriteTimeUtc(AssetDatabase.GetTextMetaFilePathFromAssetPath(path)).ToFileTimeUtc() }; for (int j = 0; j < sprites.Length; ++j) { if (sprites[j] is Sprite sprite) { var indices = sprite.triangles.Length; var vertexCount = sprite.GetVertexCount(); spriteAssets.spriteData.Add(new SpriteData() { name = sprite.name, vertexCount = vertexCount, triangleCount = indices/3, spriteGlobalID = GlobalObjectId.GetGlobalObjectIdSlow(sprite).ToString() }); } } capture.spriteData.Add(spriteAssets); await Task.Delay(10); } Progress.Remove(id); capture.lastCaptureTime = DateTime.UtcNow.ToFileTimeUtc(); return capture; } /// /// Determines whether the asset or its meta file has changed since the previous capture. /// /// The previous asset capture data. /// The asset path. /// True if the asset or meta file has changed; otherwise, false. public static bool HasAssetChanged(SpriteAssets prevCapture, string path) { var fileTime = File.GetLastWriteTimeUtc(path).ToFileTimeUtc(); var metaPath = AssetDatabase.GetTextMetaFilePathFromAssetPath(path); var metaTime = File.GetLastWriteTimeUtc(metaPath).ToFileTimeUtc(); return prevCapture?.fileModifiedTime != fileTime || prevCapture?.metaFileModifiedTime != metaTime; } } }