AddressableをIDisposableで管理する

Addressable、めっちゃ便利だけどAsyncOperationHandleを握っておいてReleaseメソッドを呼ぶ。という開放がちょっと面倒なんですよね。
そこで、IDisposableで管理できるようにしてみました。

合わせて読みたい -> EditorではAddressableのロードを早くする

使い方

// 読み込む
var prefab = await AddressableWrapper.Load<GameObject>("Test.prefab", cancellationToken);

// 使う
GameObject.Instantiate(prefab.Value);

// 開放する
prefab.Dispose();

コード

AddressableWrapper.cs
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using JetBrains.Annotations;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace KyubunsSandbox
{
    public static class AddressableWrapper
    {
        private static IAddressableLoader _addressableLoader;

        public static IAddressableLoader AddressableLoader
        {
            get => _addressableLoader ?? (_addressableLoader = new DefaultAddressableLoader());
            set => _addressableLoader = value;
        }

        [MustUseReturnValue]
        public static UniTask<IDisposableAsset<TObject>> Load<TObject>(string address, CancellationToken cancellationToken) where TObject : UnityEngine.Object
        {
            return AddressableLoader.LoadAssetAsync<TObject>(address, cancellationToken);
        }

        public interface IAddressableLoader
        {
            UniTask<IDisposableAsset<TObject>> LoadAssetAsync<TObject>(string address, CancellationToken cancellationToken) where TObject : UnityEngine.Object;
        }

        public class DefaultAddressableLoader : IAddressableLoader
        {
            public async UniTask<IDisposableAsset<TObject>> LoadAssetAsync<TObject>(string address, CancellationToken cancellationToken) where TObject : UnityEngine.Object
            {
                var handle = Addressables.LoadAssetAsync<TObject>(address);
                await handle.ToUniTask(cancellationToken: cancellationToken);
                if (handle.Status == AsyncOperationStatus.Succeeded)
                {
                    return new DisposableAsset<TObject>(handle.Result, () => Addressables.Release(handle));
                }
                throw new Exception($"Failed to load {address} {handle.Status}");
            }
        }
    }

    public interface IDisposableAsset<out T> : IDisposable
    {
        T Value { get; }
    }

    public class DisposableAsset<T> : IDisposableAsset<T>
    {
        public T Value { get; }

        private bool _disposed;
        private readonly Action _dispose;

        public DisposableAsset(T value, Action dispose)
        {
            Value = value;
            _dispose = dispose;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            _dispose();
        }
    }
}