Dostlar, selam. Bugün size Godot 4 ile uğraşırken yaşadığım ve neredeyse bilgisayarımı pencereden atacağım bir optimizasyon faciasını anlatacağım. Olay, oyunumdaki asset'leri (sprite'lar, sesler, sahneler) daha verimli yüklemek için yazdığım süslü püslü bir Custom Resource Loader scriptinde geçiyor. Amacım basitti: bir asset bir kere yüklendiyse, ikinci istekte doğrudan cache'den verilecek, böylece loading times ve gereksiz disk I/O azalacaktı. Ne güzel plan değil mi?
Her şey güllük gülistanlık giderken, oyunun belli bir bölümünde FPS'in anlamsız düşüşler yaptığını fark ettim. Hemen Godot'un Profiler'ını açtım. Memory sekmesine baktığımda, aynı asset grubunu her geçişte belleğin düzenli olarak ARTTIĞINI gördüm. "Cache'liyorum ya, azalması gerekmez mi?" diye kendi kendime söylenmeye başladım. Cache mekanizmam basit bir Dictionary üzerine kuruluydu.
Kod:
var _resource_cache: Dictionary = {} # Yüklü resource'lar burada duracak
Saçımı başımı yolmaya başladım. Cache'e eklemeden önce kontrol ediyordum, yoksa yüklüyordum. Mantık hatası yok gibiydi. Derken, load() fonksiyonunun bana verdiği şeyin aslında bir Resource örneği (instance) olduğunu, ve benim bu örneği cache'e koyduğumu fark ettim. Sorun şuydu: Godot, bazen (özellikle aynı frame içinde veya çok yakın zamanlarda gelen isteklerde) aynı dosya için load() çağrıldığında, henüz önceki yükleme işlemi tam olarak "işaretlenmeden" YENİ BİR ÖRNEK daha döndürüyor olabilirdi! Ben de cahil cesareti, cache'imde yok diye her ikisini de alıp cache'e ayrı ayrı yazıyordum. Sonuç: Aynı görsel hafızada iki kopya.
Çözüm, ResourceLoader.load_threaded_request() ve ResourceLoader.load_threaded_get_status() ikilisine geçiş yapmakta yattı. Bu sistem, yükleme isteklerini bir path (yol) üzerinden takip ediyor ve aynı yola yapılan ikinci bir isteği, zaten devam eden yükleme işlemine yönlendiriyor. Yani asset fiziksel olarak bir kere yükleniyor. Cache'imi path bazlı ve bu threaded loader'ın durumunu kontrol edecek şekilde yeniden yazdım. İşte o kritik kontrol:
Kod:
if _resource_cache.has(path):
return _resource_cache[path]
else:
# Threaded load başlat ve sonucu cache'le
# ...
Şaka gibi ama, bu değişiklikten sonra bellek kullanımı dümdüz bir çizgi oldu. Meğerse ben "verimlilik" yapayım derken, en temel hatayı yapıp belleği ikiye katlıyormuşum.
Siz de custom loader, cache mekanizması yazarken böyle "aynı şeyi iki kere yükleme" tuzağına düştünüz mü? Godot'da resource yönetimi için sizin go-to yönteminiz nedir? Cache'i ne zaman temizliyorsunuz? Yorumlara bekliyorum, belki hep birlikte daha iyi bir yol buluruz.