holodepth

HTML5 Canvas · Performans ve optimizasyon

Nesne havuzlama: döngü içinde tahsisatı kesmek ve yeniden kullanmak

Canvas tabanlı oyun ve görselleştirmede her karede yüzlerce küçük nesne ( mermi, parçacık, geçici çizim kaydı, çarpışma sonucu ) üretmek doğaldır. JavaScript’te her {} veya new çağrısı çöp toplayıcı ( GC ) için iş oluşturur; düşük gecikme hedefinde ani takılmaların sık nedeni budur. Nesne havuzlama, önceden ayrılmış nesneleri «edin → kullan → iade et» döngüsüyle yeniden dolaşıma sokmaktır. Piksel maliyeti ve bağlam sırası başka başlıklarda işlenir: Yeniden çizim maliyeti, Batching mantığı; burada odak bellek tahsisatı ve nesne ömrüdür.

Güncelleme fazının zamanlaması kare yönetimi ile uyumludur; gereksiz çizimi kesmek yeniden çizim bayrağı ile ilişkilidir — havuz, görünmez kalan sahnede bile edilen gereksiz tahsisleri azaltır. Görünmez tampon üretimi için komşu Offscreen Canvas düşünülür.

Özet: havuz ne zaman mantıklıdır?

Belirti Muhtemel sorun Havuz katkısı
Kare başına yüzlerce geçici nesne Yüksek tahsis + GC dalgalanması Aynı yapıları yeniden kullanma
Parçacık / mermi patlaması Kısa ömürlü nesne seli Üst sınırlı havuz + sıfırlama
Çizim komut listesi oluşturma Her kare yeni dizi / nesne Listeyi havuzdan derleyip iade

Nesne havuzlama nedir ve çöp toplayıcı baskısı

Havuz deseni, boştaki nesneler için bir yeniden kullanım deposu tutar. İhtiyaç olduğunda depodan bir öğe alınır ( acquire ), iş bitince temizlenip depoya geri konur ( release ). Böylece sürekli { x: 0, y: 0 } üretmek yerine aynı nesnenin alanları güncellenir — çöp toplayıcıya düşen kısa ömürlü nesne sayısı düşer. Bu, Canvas’a özgü bir API değildir; fakat 60 veya 120 kare hedefinde döngü içindeki mikro tahsisler bir araya gelince kullanıcıya takılma olarak yansır.

Havuz «her yerde şart» değildir: düşük frekanslı kullanıcı arayüzü güncellemesi veya seyrek oluşturulan grafiklerde karmaşıklık borcu kazançtan büyük olabilir. Kazanım genelde yüksek churn ( çok oluştur / çok bırak ) bölgelerinde görülür — özellikle fizik sonrası geçici sonuç nesneleri, çarpışma listesi girişleri ve tek karelik görsel efekt kayıtları tipiktir. Önce performans panelinde tahsis ve GC düzenini gözlemlemek, sonra havuzu dar bir alanda denemek doğru sıradır.

Havuzlanmış nesneler hâlâ JavaScript nesnesidir; TypeScript veya sınıf kullanıyorsanız aynı kurallar geçerli — önemli olan sözleşme disiplini: hangi alanın havuz dışı görünür durum, hangisinin yalnızca dahili önbellek olduğu net yazılmalıdır.

Edinme ve iade yaşam döngüsü

Yaşam döngüsü üç adımdan oluşur: (1) boş listeden nesne al veya fabrika ile yeni üret; (2) kare veya olay süresince kullan; (3) iş bitince sıfırlayıp boş listeye iade et. Atlama yapılan her adım — özellikle iade — bellek sızıntısı veya çift kullanım hatasına yol açar. «Yalnızca tek sahibim» kuralı: havuzdan çıkan nesneyi aynı anda iki sistem referans etmemelidir; aktarım yapılıyorsa ya sahipliği açıkça sabitleyin ya da havuz dışı kopya politikası seçilmelidir.

İade sırasında referansları koparmak önemlidir: başka bir nesneye tutulan büyük dizi veya grafik referansı bırakmak, havuzda «yanlışlıkla paylaşılan» kalıcı durum doğurur. Bu yüzden reset işlevi ( aşağıdaki fabrika örneğinde ) hem sayısal alanları sıfırlamalı hem de isteğe bağlı nesne alanlarını null yapmalıdır.

Çok kare boyunca yaşayan oyun varlıkları genelde havuz dışında kalır; havuz kısa ömürlü yardımcı yapılar içindir. Uzun ömürlü dünya nesnesini havuza atmak mimari karışıklık yaratır — havuz ile «varlık yönetimi»ni karıştırmayın. Oyun durumu düşüncesi ile çelişmeden, havuzu düşük seviyeli çizim ve fizik yardımcıları için kullanın.

Güvenli havuz fabrikası ve üst sınır politikası

Üretim kodunda havuzun sonsuz büyümesine izin vermeyin: ani patlamalarda ( binlerce parçacık ) bellek tükenebilir. Üst sınır ( cap ), boş listede tutulacak maksimum nesne sayısını sınırlar; fazlası iade edilirken düşürülür veya hiç havuzlanmaz — ürün politikası seçilir. Aşağıdaki fabrika, fabrika işlevini ve isteğe bağlı sıfırlayıcıyı dışarıdan alır; kullanıcı girdisi doğrudan fabrikaya bağlanmaz ( güvenlik için fabrika içinde sabit başlangıç kullanın ).

acquire boş listeden çıkarır; boşsa fabrikayı çağırır. release önce sıfırlar, sonra üst sınır uygunsa boş listeye iter. İsteğe bağlı prewarm(n) ile başlangıç dalgasını önceden doldurarak ilk saniye takılmalarını yumuşatabilirsiniz — mobilde bellek bütçesi ile tartılır.

/**
 * @template T
 * @param {() => T} factory - Her çağrıda yeni boş nesne (kullanıcı girdisi bağlamayın).
 * @param {{ maxFree?: number, reset?: (obj: T) => void }} [options]
 */
export function createObjectPool(factory, options = {}) {
  const maxFree = Math.max(0, Math.floor(Number(options.maxFree) || 256));
  const reset =
    typeof options.reset === 'function'
      ? options.reset
      : () => {
          /* no-op */
        };

  /** @type {T[]} */
  const free = [];

  function acquire() {
    const obj = free.length ? free.pop() : factory();
    return obj;
  }

  /** İş bitince çağrılır; referansları reset içinde temizleyin. */
  function release(obj) {
    if (obj == null) return;
    reset(obj);
    if (free.length < maxFree) free.push(obj);
  }

  function prewarm(count) {
    const n = Math.max(0, Math.floor(Number(count) || 0));
    for (let i = 0; i < n && free.length < maxFree; i++) free.push(factory());
  }

  return { acquire, release, prewarm, freeCount: () => free.length };
}

Sıfırlama işlevi ve düz nesne şeması

En okunabilir yaklaşım, havuzlanan yapıyı düz nesne ( plain object ) olarak tutmaktır: sayısal alanlar ( konum, hız, süre ) doğrudan özellik olarak güncellenir; referans tutulan alanlar ( hedef varlık işaretçisi ) iade sırasında koparılır. Sınıf örnekleri de havuzlanabilir; yeter ki reset tutarlı ve hızlı olsun — constructor yan etkileri üretmemelidir.

Örnek şema: parçacık için x, y, vx, vy, life, active. Kare güncellemesinde yalnız active === true olanlar işlenir; ömür bitince release çağrılır ve listeden çıkarılır. Liste olarak yoğun dizi ( dense array ) veya seyrek işaretçi dizisi ürün kararıdır — tam sayı matematiği veya TypedArray ile yoğun tampon kullanımı bu sayfanın konusu dışında bırakılır; havuz yine de üst düzey meta nesneler için kullanılabilir.

Aşağıdaki örnek yalnız deseni gösterir: gerçek oyunda üst sınır, ömür politikası ve çarpışma kodu eklenmelidir. Harici kaynak veya DOM erişimi yoktur.

import { createObjectPool } from './object-pool.js';

function particleFactory() {
  return { x: 0, y: 0, vx: 0, vy: 0, life: 0, active: false };
}

function resetParticle(p) {
  p.x = 0;
  p.y = 0;
  p.vx = 0;
  p.vy = 0;
  p.life = 0;
  p.active = false;
}

export function createParticlePool(maxFree = 512) {
  return createObjectPool(particleFactory, { maxFree, reset: resetParticle });
}

/** Örnek: spawn — pozitif ömür ve pozisyon doğrulaması çağıran tarafta yapılmalıdır. */
export function spawnParticle(pool, px, py, vx, vy, frames) {
  const p = pool.acquire();
  p.x = px;
  p.y = py;
  p.vx = vx;
  p.vy = vy;
  p.life = Math.max(0, Math.floor(frames));
  p.active = true;
  return p;
}

Çizim komut listesi ile havuzun birleştirilmesi

Batching mantığı içindeki komut listesi her kare için geçici diziler üretebilir. Havuz kullanımı: liste düğümlerini havuzdan al, sıralayıp çiz, sonra tüm düğümleri iade et — böylece push ile büyüyen dizinin her elemanı için sürekli nesne yaratımı kesilir. Liste kapasitesi ( dizi .length = 0 ile sıfırlama ) ile havuz ( nesne ömrü ) farklı kavramlardır; ikisi birlikte kullanılabilir.

Pratik akış: güncelleme aşamasında aktif havuz nesnelerinden komut nesneleri türetilir veya doğrudan sıralama anahtarı taşıyan geçici kayıtlar havuzdan alınır; çizim flush’undan sonra hepsi release edilir. Bir nesneyi hem mantık hem çizim için paylaşıyorsanız, çizim kaydı nesnesini havuzda tutup içine referans yazmak yerine kopya kimlik ( id ) taşımak daha güvenlidir — böylece havuz iadesi ile mantık nesnesinin yaşamı karışmaz.

Gereksiz karelerde listeyi hiç oluşturmamak kirli bayrak ile uyumludur: havuz edinimi de atlanır; hem CPU hem bellek kazançlıdır.

Yüksek frekanslı varlık senaryoları ve ölçek

Parçacık ve mermi sistemleri klasik havuz adayıdır: ömür kısa, oluşturma sık, görsel olarak benzer kayıtlar. Çarpışma testleri için geçici «temas sonucu» nesneleri de havuzlanabilir — fizik motorunun kendi API’sine müdahale etmeden, yalnız kendi yardımcı yapılarınız için kullanın.

Kullanıcı arayüzü düğümleri genelde düşük churn gösterir; burada havuz yerine bileşen ömrünü yönetmek daha okunabilir olabilir. Ölçek binlerce kaydırsa ( büyük liste sanallaştırması ) liste satırı meta nesneleri için sınırlı havuz düşünülebilir — ancak erişilebilirlik ve odak yönetimi ile çakışmayın.

Mobil cihazlarda havuz üst sınırını düşük tutmak ve ani patlamada efektleri kısıtlamak ( «parçacık sayısı tavanı» ) kullanıcı deneyimi ile performansın uzlaşımıdır — teknik olarak doğru kod, cihazı ısıtmaktan kaçınmak için ürün politikası gerektirir.

Tuzaklar, ölçüm ve kontrol listesi

Çift iade: Aynı nesneyi iki kez release etmek, listede yineleme ve üst üste «aktif» kullanım riski doğurur. Koruma: nesneye dahili bayrak ( __pooledReleased ) veya havuz dışı tek sahipli kuyruk disiplini. Sızıntı: Edinilen nesne her zaman bir çıkış yolunda iade edilmeli; erken return ve hata dallarında bile release çağrısı unutulmamalıdır. Tükenmiş kapasite: Üst sırada fazla nesne düşürülünce efekt seyrekleşir; bunu görsel olarak tolere edilebilir kılın veya üst sınarı dinamik ayarlayın.

Havuzu ekledikten sonra mutlaka karşılaştırmalı ölçüm yapın: bazen beklenmedik şekilde kazanç düşük çıkar ( motor zaten nesneleri optimize ediyor olabilir ). Batching ve redraw stratejisi ile çelişen «her şeyi yeniden tahsis et» alışkanlığını havuz tek başına çözmez — üç başlık birlikte düşünülür.

Bu sayfanın sınırı

Tam teşekküllü ECS, varlık bileşen grafı veya çok iş parçacıklı bellek modelleri burada işlenmez. Odak: Canvas oyun döngüsünde kısa ömürlü JavaScript nesnelerinin güvenli yeniden kullanımıdır.

  • Havuz üst sınırı ve ani patlama politikası tanımlandı mı?
  • release tüm çıkış yollarında çağrılıyor mu?
  • İade sırasında dış referanslar koparılıyor mu?
  • Havuz nesnesi iki sistemde eşzamanlı paylaşılmıyor mu?
  • GC ve kare süresi ölçümü havuz öncesi / sonrası karşılaştırıldı mı?