Kafayı yiyecektim arkadaş. Şaka gibi ama, "memory leak" diye diye kendi projemde tam bir bellek katliamına sebep oldum. Her şey Addressable Assets sistemiyle daha temiz, daha kontrollü bir memory yönetimi yapma hevesiyle başladı. "Artık Resources klasörüne elveda, modern çözümler kullanacağım!" dedim. Meğer ben modern çözümlerin tuzağına düşmüşüm.
Güzel Başlangıç, Her Şey Toz Pembe
İlk başta mükemmeldi. Asset'leri grupladım, etiketledim, Addressables.LoadAssetAsync() ile yüklemeye başladım. Performans harikaydı, build'ler düzenli, her şey yolunda gibiydi. "Neden daha önce kullanmadım ki?" diye kendime söyleniyordum. Ta ki...
Profiler Kabusu ve Gizli Bellek Canavarı
Oyunun belirli bir bölümünde, sürekli aynı sahneye girip çıkınca, Memory Profiler penceresinde gördüğüm manzara içler acısıydı. AssetBundle'lar ve Texture'lar gözle görülür şekilde birikiyor, ama ben Addressables.Release() ve hatta Addressables.ReleaseInstance() kullanıyordum! StackOverflow'da bile net bir cevap bulamadım, herkes "doğru kullanıyorsan sorun olmaz" diyordu. Meğerse sorun şuradaymış:
Evet. O Completed event'ının içinde, handle'ı serbest bırakmayı UNUTUYORDUM. Ama daha kötüsü vardı...
Reference Zinciri ve Koleksiyon Tuzağı
Asenkron işlemlerin dönüş tipi olan AsyncOperationHandle, bir struct. Onu bir class içindeki liste veya dictionary'de saklamaya kalktığınız anda, işler çığırından çıkıyor. Handle'ın "hayatta" kalması, arka plandaki asset'in de bellekte kalması anlamına geliyor. Şöyle bir şey yapmışım farkında olmadan:
Kurtuluş Yolu: Task'lar, Awaitable'lar ve Titizlik
Çözüm, disiplinli olmakta ve Addressables'ın .Task özelliğini veya Unity 2023'le gelen daha iyi awaitable desteğini kullanmakta yatıyormuş. Artık şöyle yapıyorum:
Ayrıca, Addressables penceresindeki Analyze tool'u ve Event Viewer'ı düzenli çalıştırmak, kimin neyi tuttuğunu görmek şart.
Sonuç olarak, Addressable Assets güçlü bir silah, ama tetiği çekmeden önce iki kere düşünmek gerekiyor. Asenkron programming'in "fire and forget" mantığı, burada tam bir hafıza (ve hayat) düşmanına dönüşebiliyor.
Siz de benzer bir Addressables veya AssetBundle kabusu yaşadınız mı? Özellikle sahne geçişlerinde memory temizliği için hangi pattern'leri kullanıyorsunuz? "Ben şöyle yapıyorum, çok rahatım" diyen var mı?
İlk başta mükemmeldi. Asset'leri grupladım, etiketledim, Addressables.LoadAssetAsync() ile yüklemeye başladım. Performans harikaydı, build'ler düzenli, her şey yolunda gibiydi. "Neden daha önce kullanmadım ki?" diye kendime söyleniyordum. Ta ki...
Oyunun belirli bir bölümünde, sürekli aynı sahneye girip çıkınca, Memory Profiler penceresinde gördüğüm manzara içler acısıydı. AssetBundle'lar ve Texture'lar gözle görülür şekilde birikiyor, ama ben Addressables.Release() ve hatta Addressables.ReleaseInstance() kullanıyordum! StackOverflow'da bile net bir cevap bulamadım, herkes "doğru kullanıyorsan sorun olmaz" diyordu. Meğerse sorun şuradaymış:
C#:
// İşte o lanet olası, masum görünen kod parçası:
var asyncHandle = Addressables.LoadAssetAsync<GameObject>("MyPrefab");
asyncHandle.Completed += (handle) =>
{
Instantiate(handle.Result);
// Addressables.Release(handle); // BUNU UNUTMAK ÖLÜM!
};
Evet. O Completed event'ının içinde, handle'ı serbest bırakmayı UNUTUYORDUM. Ama daha kötüsü vardı...
Asenkron işlemlerin dönüş tipi olan AsyncOperationHandle, bir struct. Onu bir class içindeki liste veya dictionary'de saklamaya kalktığınız anda, işler çığırından çıkıyor. Handle'ın "hayatta" kalması, arka plandaki asset'in de bellekte kalması anlamına geliyor. Şöyle bir şey yapmışım farkında olmadan:
C#:
List<AsyncOperationHandle> _ongoingLoads = new List<AsyncOperationHandle>();
// Her yüklemede listeye ekliyordum...
var handle = Addressables.LoadAssetAsync<Texture2D>("bgImage");
_ongoingLoads.Add(handle); // BU SATIR ÖLDÜRÜCÜ DARBE!
// Ve bu listeyi temizlemeyi hep erteliyordum...
Çözüm, disiplinli olmakta ve Addressables'ın .Task özelliğini veya Unity 2023'le gelen daha iyi awaitable desteğini kullanmakta yatıyormuş. Artık şöyle yapıyorum:
C#:
public async UniTask<GameObject> LoadAndForget(string key)
{
var handle = Addressables.LoadAssetAsync<GameObject>(key);
var obj = await handle.Task; // Task tamamlanana kadar bekle
var instance = Instantiate(obj);
Addressables.Release(handle); // HEMEN SERBEST BIRAK!
return instance;
}
Ayrıca, Addressables penceresindeki Analyze tool'u ve Event Viewer'ı düzenli çalıştırmak, kimin neyi tuttuğunu görmek şart.
Sonuç olarak, Addressable Assets güçlü bir silah, ama tetiği çekmeden önce iki kere düşünmek gerekiyor. Asenkron programming'in "fire and forget" mantığı, burada tam bir hafıza (ve hayat) düşmanına dönüşebiliyor.
Siz de benzer bir Addressables veya AssetBundle kabusu yaşadınız mı? Özellikle sahne geçişlerinde memory temizliği için hangi pattern'leri kullanıyorsunuz? "Ben şöyle yapıyorum, çok rahatım" diyen var mı?