Files
stas-barecky/Library/PackageCache/com.unity.2d.tooling@55ff0338cdce/Samples~/Sample Sprite Data source And Report

Sample Sprite Data Source and Report

This sample demonstrates how to create a custom data source and report for the Sprite Atlas Analyzer. It shows how to collect sprite mesh information (vertex and triangle counts) and create a filterable report to analyze sprite performance.

Overview

The sample consists of two main parts:

Data Source Components

  1. SpriteDataSource.cs - Custom data source that implements IReportDataSource
  2. SpriteCaptureData.cs - Data structures for storing captured sprite information

Report Components

  1. SpriteMeshSizeReport.cs - Custom report that inherits from AnalyzerIssueReportBase
  2. SpriteMeshSizeFilteredData.cs - Data structure for filtered report items
  3. SpriteMeshSizeReportSettings.cs - Settings UI for configuring report filters
  4. SpriteMeshSizeReportSettings.uxml - UXML layout for the settings UI
  5. SpriteMeshSizeReport.uss - Styling for the report UI

How to Create a Custom Data Source

1. Create Data Structures

First, define the data structures to store your captured information:

[Serializable]
class YourCaptureData : ISaveData
{
    public List<YourAssetData> assetData = new();
    public long lastCaptureTime;
}

[Serializable]
class YourAssetData
{
    public string assetPathGuid;
    public long fileModifiedTime;
    public long metaFileModifiedTime;
    // Add your specific data fields
}

2. Implement IReportDataSource

Create a class that implements IReportDataSource:

class YourDataSource : IReportDataSource
{
    YourCaptureData m_Capture = new();
    bool m_Cancel;
    Task<YourCaptureData> m_CaptureTask;

    public event Action<IReportDataSource> onDataSourceChanged;
    public event Action<IReportDataSource> onCaptureStart;
    public event Action<IReportDataSource> onCaptureEnd;

    public bool capturing { get; private set; }
    public string name => "Your Data Source Name";
    public long lastCaptureTime => m_Capture.lastCaptureTime;
    public List<YourAssetData> data => m_Capture.assetData;

    public async void Capture(string[] assetSearchPath)
    {
        m_Cancel = false;
        capturing = true;
        onCaptureStart?.Invoke(this);
        m_CaptureTask = CaptureData(m_Capture, assetSearchPath);
        await m_CaptureTask;
        m_Capture = m_CaptureTask.Result;
        capturing = false;
        onCaptureEnd?.Invoke(this);
        onDataSourceChanged?.Invoke(this);
    }

    public void StopCapture() => m_Cancel = true;
    public void Dispose() => m_Cancel = true;
    public void Save(ISaveFile saveData) => saveData.AddSaveData(m_Capture);
    public void Load(ISaveFile saveData) { /* Load implementation */ }
}

3. Implement Data Capture Logic

Create the asynchronous capture method:

async Task<YourCaptureData> CaptureData(YourCaptureData prevCapture, string[] assetSearchPath)
{
    int id = Progress.Start("Your Data Capture");
    var capture = new YourCaptureData();

    // Find assets of your target type
    string[] guids = AssetDatabase.FindAssets("t:YourAssetType", assetSearchPath);
    HashSet<string> pathVisited = new();

    for (int i = 0; i < guids.Length && !m_Cancel; ++i)
    {
        Progress.Report(id, i, guids.Length, "Capturing data");
        var path = AssetDatabase.GUIDToAssetPath(guids[i]);

        if (!pathVisited.Add(path))
            continue;

        // Check if asset changed since last capture
        if (!HasAssetChanged(prevCapture, path))
        {
            // Reuse existing data
            continue;
        }

        // Capture new data
        var assets = AssetDatabase.LoadAllAssetsAtPath(path);
        // Process your assets here

        await Task.Delay(10); // Yield control periodically
    }

    Progress.Remove(id);
    capture.lastCaptureTime = DateTime.UtcNow.ToFileTimeUtc();
    return capture;
}

How to Create a Custom Report

1. Create a Report Class

Create a class that inherits from AnalyzerIssueReportBase:

class YourCustomReport : AnalyzerIssueReportBase
{
    MultiColumnListView m_ReportContent;
    YourDataSource m_DataSource;
    List<YourFilteredData> m_Filtered = new();

    public YourCustomReport() : base(new[] { typeof(YourDataSource) })
    {
        SetReportListItemName("Your Report Name");
        SetReportListemCount("0");
        SetupReportContent();
    }

    public override VisualElement reportContent => m_ReportContent;
    public override VisualElement settingsContent => m_Settings; // or null
    public override string reportTitle => "Your Report Title";
}

2. Handle Data Source Changes

Override OnReportDataSourceChanged to process data:

protected override async void OnReportDataSourceChanged(IReportDataSource reportDataSource)
{
    if (reportDataSource is YourDataSource dataSource)
    {
        m_DataSource = dataSource;
        await FilterData(dataSource);
    }
}

3. Implement Data Filtering

Create asynchronous filtering logic:

async Task FilterData(YourDataSource dataSource)
{
    m_Filtered = new();
    var task = Task.Run(() =>
    {
        for (int i = 0; i < dataSource?.data?.Count; ++i)
        {
            var assetData = dataSource.data[i];
            // Apply your filtering logic here
            if (ShouldIncludeInReport(assetData))
            {
                m_Filtered.Add(new YourFilteredData()
                {
                    // Map your data
                });
            }
        }
    });

    isFilteringReport = true;
    await task;
    m_ReportContent.itemsSource = m_Filtered;
    m_ReportContent.Rebuild();
    isFilteringReport = false;
    SetReportListemCount($"{m_Filtered.Count}");
}

4. Setup Report UI

Configure the MultiColumnListView:

void SetupReportContent()
{
    var columns = new[]
    {
        new Column()
        {
            title = "Name",
            width = Length.Pixels(100),
            makeCell = () => new Label(),
            bindingPath = "name"
        },
        // Add more columns as needed
    };

    m_ReportContent = new MultiColumnListView();
    m_ReportContent.showAlternatingRowBackgrounds = AlternatingRowBackground.ContentOnly;
    m_ReportContent.selectionChanged += OnSelectionChanged;

    // Setup column binding
    for (int i = 0; i < columns.Length; ++i)
    {
        var bindingPath = columns[i].bindingPath;
        columns[i].bindCell = (e, k) =>
        {
            var label = e.Q<Label>();
            label.SetBinding("text", new DataBinding()
            {
                dataSourcePath = new PropertyPath(bindingPath)
            });
            e.dataSource = m_Filtered[k];
        };
        m_ReportContent.columns.Add(columns[i]);
    }
}

5. Create Settings UI (Optional)

If your report needs configurable settings:

public class YourReportSettings : VisualElement
{
    const string k_Uxml = "your-uxml-guid";

    public event Action<SettingsType> onApplyClickedEvent;

    public YourReportSettings(SettingsType initialSettings)
    {
        var path = AssetDatabase.GUIDToAssetPath(new GUID(k_Uxml));
        var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(path);
        uxml.CloneTree(this);

        // Setup UI elements and event handlers
    }
}

Key Features Demonstrated

Data Source Features

  • Asynchronous Data Capture: Non-blocking data collection with progress reporting
  • Incremental Updates: Only recapture data when assets have changed
  • Save/Load Support: Persist captured data across sessions
  • Progress Reporting: Visual feedback during long-running operations

Report Features

  • Custom Data Source Integration: Works with your custom data source
  • Asynchronous Filtering: Non-blocking data processing
  • Multi-Column Display: Flexible columnar data presentation
  • Interactive Selection: Object inspection integration
  • Configurable Settings: User-customizable report parameters
  • Real-time Updates: Automatic refresh when data changes