İçeriğe geç
academia.sh

Ders 10 / 24

Zamanlama ve Çizim Döngüsü

Tarayıcının kare hazırlama sırası ve güncellemenin bu sıraya yerleştirilmesi; kare bütçesi, sabit gecikmeli zamanlayıcının kaydırdığı hiza, kare geri çağırımının kuralları, okuma ile yazmanın ayrılması ve uzun işin bölünmesi.

İçindekiler

Gözlemciler ölçümün ne zaman alınacağını çözer, değişikliğin ne zaman uygulanacağını açık bırakır. Ölçüm listesi kaydırılırken başlığın konumunu güncelleyen ya da bir çubuğu büyüten kod, bunu rastgele bir anda yaparsa tarayıcının çizim düzenine ters düşer.

Tarayıcı ekranı sürekli değil, kare kare yeniler. Her karede belirli bir sırayı izler ve bu sıra tanımlıdır. Güncellemeyi bu sıraya yerleştirmek, hareketin düzgün görünmesinin tek yoludur.

Karenin Anatomisi

Bir kare şu aşamalardan geçer: birikmiş girdi olayları işlenir, süresi dolan zamanlayıcılar çalışır, kare geri çağırımları çağrılır, biçem hesaplanır, yerleşim yapılır, boyama yapılır ve katmanlar birleştirilir.

Bu sıradan iki sonuç çıkar. Birincisi, kare geri çağırımları biçem ve yerleşimden önce çalışır; orada yapılan değişiklik aynı karede ekrana yansır. İkincisi, bir karede yapılan bütün işin toplamı, karenin süresini aşmamalıdır.

Karenin süresi ekranın yenileme hızına bağlıdır ve her cihazda aynı değildir. Saniyede 60 kare varsayımıyla bir kare 16,67 milisaniye sürer; daha hızlı ekranlarda bu süre kısalır. Bu nedenle yazılan kod belirli bir süreyi varsaymak yerine, tarayıcının verdiği zaman damgasına dayanmalıdır.

Zamanlayıcı ile Kare Hizası

Yaygın bir yaklaşım, güncellemeyi sabit gecikmeli bir zamanlayıcıya bağlamaktır. Bu yaklaşımın sorunu, gecikmenin işin bitişinden sayılmasıdır: her turda işin süresi kadar bir kayma birikir.

// kare.mjs — sabit gecikmeli zamanlayici ile kare hizasindaki geri cagirimin karsilastirmasi
const KARE = 1000 / 60;              // saniyede 60 kare varsayimiyla kare suresi (ms)
const IS = 4;                        // her guncellemenin isledigi sure (ms)
const ADIM = 8;

const kareNo = (t) => Math.floor(t / KARE);

// Zamanlayici: her tur, isin bitisinden 16 ms sonrasina yeniden kurulur.
let t = 0;
const zamanlayici = [];
for (let i = 0; i < ADIM; i += 1) {
  zamanlayici.push(t);
  t += IS + 16;
}

// Kare hizasi: geri cagirim kare basinda calisir, is suresi bir sonraki hedefi kaydirmaz.
const hizali = Array.from({ length: ADIM }, (_, i) => i * KARE);

const sayim = (anlar) => {
  const kutu = new Map();
  for (const an of anlar) kutu.set(kareNo(an), (kutu.get(kareNo(an)) ?? 0) + 1);
  return kutu;
};

console.log("adim  zamanlayici(ms)  kare   kare hizasi(ms)  kare");
for (let i = 0; i < ADIM; i += 1)
  console.log(
    String(i + 1).padStart(4),
    zamanlayici[i].toFixed(2).padStart(15), String(kareNo(zamanlayici[i])).padStart(6),
    hizali[i].toFixed(2).padStart(17), String(kareNo(hizali[i])).padStart(6),
  );

const bos = (anlar) => {
  const kutu = sayim(anlar);
  const son = kareNo(anlar[anlar.length - 1]);
  let n = 0;
  for (let k = 0; k <= son; k += 1) if ((kutu.get(k) ?? 0) === 0) n += 1;
  return { son, bos: n };
};
for (const [ad, anlar] of [["zamanlayici", zamanlayici], ["kare hizasi", hizali]]) {
  const { son, bos: n } = bos(anlar);
  console.log(`${ad.padEnd(11)}: ${ADIM} guncelleme, kare 0..${son} araligina yayildi, bos kare: ${n}`);
}
console.log("kare butcesi:", KARE.toFixed(2), "ms | is:", IS, "ms | kalan:", (KARE - IS).toFixed(2), "ms");
adim  zamanlayici(ms)  kare   kare hizasi(ms)  kare
   1            0.00      0              0.00      0
   2           20.00      1             16.67      1
   3           40.00      2             33.33      2
   4           60.00      3             50.00      3
   5           80.00      4             66.67      4
   6          100.00      6             83.33      5
   7          120.00      7            100.00      6
   8          140.00      8            116.67      7
zamanlayici: 8 guncelleme, kare 0..8 araligina yayildi, bos kare: 1
kare hizasi: 8 guncelleme, kare 0..7 araligina yayildi, bos kare: 0
kare butcesi: 16.67 ms | is: 4 ms | kalan: 12.67 ms

Kayma altıncı adımda görünür hâle gelir: beşinci kare hiçbir güncelleme almaz, altıncı kare bir sonraki güncellemeyi alır. Kullanıcının gördüğü şey donma değil, düzensizliktir — hareket bir an duraksar. Aynı sayıda güncelleme kare hizasında yapıldığında boş kare kalmaz.

Sabit gecikmeli zamanlayıcının ikinci sorunu, karesi olmayan anlarda da çalışmasıdır. Sekme arka plana alındığında ekran yenilenmez; zamanlayıcı yine de tetiklenir ve hiç görünmeyecek kareler hesaplanır. Kare geri çağırımı bu durumda çağrılmaz ve iş kendiliğinden durur.

Kare Geri Çağırımının Kuralları

Kare geri çağırımı tek seferliktir: bir sonraki kare için yeniden kaydolmak gerekir. Süregelen bir hareket, geri çağırımın içinden kendini yeniden kaydeder; durdurmak, kaydı yenilememek ya da bekleyen kaydı iptal etmektir.

Geri çağırım, karenin zaman damgasını argüman olarak alır. Hareketin ilerlemesi bu damgadan hesaplanır: “her çağrıda bir piksel” yerine “geçen süreye göre şu kadar piksel”. Birinci biçim, kare hızı değiştiğinde hareketin hızını da değiştirir; ikinci biçim cihazdan bağımsızdır. Aynı damga o karedeki bütün geri çağırımlara verilir, böylece aynı karede güncellenen ögeler birbiriyle tutarlı kalır.

Kullanıcının hareket tercihi ayrı bir kuraldır. İşletim sisteminde hareketin azaltılması istenmişse, bu tercih biçem tarafında sorgulanabilir ve program tarafında da okunabilir; tercih varsa hareket kaldırılır ya da anlık geçişe indirilir.

Okuma ve Yazmayı Ayırmak

Kare içinde yapılan işin ucuz kalması için ikinci bir kural gerekir. Bir ögenin ölçüsünü sorgulamak, bekleyen bütün yazmaların o anda işlenmesini zorlar; Gözlemciler dersinde anılan zorlama budur. Ölçüm ile yazmanın sırayla yapılması bu zorlamayı her turda tetikler.

// okuma.mjs — okuma ile yazmanin siralanmasinin zorladigi yerlesim sayisi
function kurulum() {
  return {
    cubuklar: [40, 55, 30, 72, 61].map((g, i) => ({ ad: `T-0${i + 1}`, genislik: g })),
    kirli: false,          // bekleyen yazma var mi
    yerlesim: 0,           // zorlanmis yerlesim hesabi sayisi
  };
}
const oku = (d, cubuk) => {
  if (d.kirli) { d.yerlesim += 1; d.kirli = false; }   // olcum, bekleyen yazmalari isletir
  return cubuk.genislik;
};
const yaz = (d, cubuk, deger) => { cubuk.genislik = deger; d.kirli = true; };

// Birinci yol: her cubukta once oku sonra yaz.
const a = kurulum();
for (const cubuk of a.cubuklar) yaz(a, cubuk, oku(a, cubuk) + 10);
console.log("araya girmis :", a.yerlesim, "zorlanmis yerlesim |",
  a.cubuklar.map((c) => c.genislik).join(" "));

// Ikinci yol: once butun olcumler, sonra butun yazmalar.
const b = kurulum();
const olculen = b.cubuklar.map((cubuk) => oku(b, cubuk));
b.cubuklar.forEach((cubuk, i) => yaz(b, cubuk, olculen[i] + 10));
console.log("ayrilmis     :", b.yerlesim, "zorlanmis yerlesim |",
  b.cubuklar.map((c) => c.genislik).join(" "));
console.log("sonuclar ayni:", JSON.stringify(a.cubuklar) === JSON.stringify(b.cubuklar));
araya girmis : 4 zorlanmis yerlesim | 50 65 40 82 71
ayrilmis     : 0 zorlanmis yerlesim | 50 65 40 82 71
sonuclar ayni: true

İki yol aynı sonucu üretir; maliyetleri farklıdır. Ayrılmış sırada ölçümler tek seferde alınır, yazmalar birikir ve yerleşim karenin kendi aşamasında bir kez yapılır.

Buradaki sayı yine bir başarım ölçümü değil, çağrı sayımıdır; gerçek maliyet belgeye ve biçeme bağlıdır. Sabit olan şey, tetiklenen hesap sayısının dörtten sıfıra inmesidir. Kural, öge sayısı arttıkça daha da belirginleşir: ayrılmış sıra sabit sayıda hesap üretir, araya girmiş sıra öge sayısıyla doğru orantılı.

Uzun İşi Bölmek

Kare bütçesini aşan tek bir iş, o karede yapılacak her şeyi bekletir: girdi işlenmez, hareket ilerlemez. Bin ölçümü tek turda süzen bir işlev bunu yapar.

Çözüm işi bölmektir. Parça işlenir, denetim çalışma zamanına geri verilir, sonraki parça bir sonraki turda işlenir. Böylece girdi olayları aradaki boşluklarda işlenir ve arayüz yanıt vermeyi sürdürür. Bölmenin ölçüsü sabit bir parça sayısı değil, geçen süredir: her turda süre ölçülür ve bütçe dolduğunda tur bitirilir.

Kimi işler karede yapılmak zorunda değildir — kütüğe yazma, önbellek temizliği, sonraki sayfanın hazırlanması. Bunlar için tarayıcı boşta çalışma sınıfından bir geri çağırım sunar: kare işini bitirdiğinde kalan süre varsa çağrılır ve kalan süreyi bildirir. Boşta çalışan geri çağırıma verilen iş, hiç çalışmama olasılığına karşı bir zaman aşımıyla sınırlandırılır.

Ağır hesabın bir başka çözümü, işi ana iş parçacığından tümüyle çıkarmaktır; bu Web İşçileri dersinin konusudur.

Özet

  • Tarayıcı ekranı kare kare yeniler; kare geri çağırımları biçem ve yerleşimden önce çalıştığı için orada yapılan değişiklik aynı karede görünür.
  • Sabit gecikmeli zamanlayıcı, gecikmeyi işin bitişinden saydığı için kayma biriktirir ve kimi kareler güncelleme almaz; kare geri çağırımı hizayı korur.
  • Kare geri çağırımı tek seferliktir, zaman damgası alır ve ekranı yenilenmeyen sekmede çağrılmaz; hareket geçen süreye göre hesaplanır.
  • Ölçüm ile yazmanın sırayla yapılması her turda yerleşim hesabı zorlar; önce bütün ölçümler sonra bütün yazmalar yapıldığında bu zorlama ortadan kalkar.
  • Kare bütçesini aşan iş parçalara bölünür ve parçalar arasında denetim çalışma zamanına geri verilir; ertelenebilir işler boşta çalışma geri çağırımına bırakılır.

Sonraki Adım

Kare içinde yapılan işin bir bölümü metin üretmektir: ölçüm değerinin yanına birimi yazmak, zaman damgasını okunur bir tarihe çevirmek, listeyi ada göre sıralamak. Bu işlemlerin hiçbiri evrensel değildir. Ondalık ayırıcı her yerde aynı karakter değildir, tarih alanlarının sırası dile göre değişir, harflerin sıralaması abeceye bağlıdır ve Türkçede noktalı ile noktasız harf ayrı harflerdir. Elle yazılmış biçimleme kodu bu farkları taşıyamaz. Sonraki ders, tarayıcının bu işleri yerel ayara göre yapan arayüzünü ve o arayüzün kurulum maliyetini ele alır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat