HTML5 Canvas · Render döngüsü
Update vs render: mantık ve piksel sunumunu ayırmak
Sürekli çalışan bir canvas uygulamasında iki farklı iş vardır: birincisi dünya durumunu — konumlar, skor, animasyon fazı, seçim mantığı — güncellemek ( update); ikincisi o durumu piksel buffer’ına çizmek ( render). İkisini aynı fonksiyon içinde yazmak küçük demolar için işe yarar; fakat kare atlama, sabit adım simülasyon, ağır çizim veya girdi fırtınası olduğunda ayrımı bilinçli tutmak hata ayıklamayı ve performansı iyileştirir. Bu sayfa, bu iki aşamanın canvas üreticisi gözünden sınırını, sırasını ve tipik kısayollarını sabitler.
Zaman damgası ve adım süresi Delta time ile; kare planlama requestAnimationFrame ile; piksel sıfırlama Clear & redraw ile — burada yalnızca «önce neyi hesaplıyorum, sonra neyi boyuyorum?» sorusu ön plandadır.
Özet: iki aşamanın karşılaştırması
| Aşama | Tipik iş | Canvas’ta dikkat |
|---|---|---|
| Güncelleme | Durum, fizik adımı, kaydırılan içerik, sayaçlar | Bağlam (ctx) okuması nadirdir; saf veri tercih edilir |
| Sunum | clearRect, yol, dolgu, drawImage, metin |
Yoğun piksel işi; tek turda atomik «kare» tamamlanmalıdır |
| Birleşik döngü | Küçük örneklerde step+draw tek geri çağrıda |
Büyüyünce modüllere bölmek maliyeti düşürür |
| Olay güdümlü sunum | Güncelleme olayda, çizim birleştirilmiş rAF’ta | Aynı karede onlarca draw yerine tek planlı çağrı |
Güncelleme ve çizim: iki ayrı sorumluluk
Zihinsel model: güncelleme saf JavaScript nesneleri üzerinde çalışır —
dizi, yapı, oyun durumu — ve mümkün olduğunda CanvasRenderingContext2D API’sine
dokunmaz. Böylece aynı durumu başka bir yüzeye çizmek, ekran görüntüsü üretmek veya birim
testinde doğrulamak kolaylaşır. Sunum ise yalnızca o anki durumu okuyup
komut dizisi ile piksele döker; iş mantığı içinde gizli yan etkiler (ör. yanlışlıkla global
fillStyle değiştirmek) üretmemelidir.
Pratikte küçük projede tek tick(dt) içinde önce alt rutinlerle durumu ilerletip
sonra draw() çağırmak yeterlidir — önemli olan isimlerin ve dosya
sınırlarının iki aşamayı ayırmasıdır. «Çizim içinde rastgele skor artırma» gibi
desenler, mod büyüdüğünde yarış durumu ve telafisi zor bug’lar üretir.
Sunum katmanı, durumun tam kopyasını tutmak zorunda değildir; yeterince göstermek için türetilmiş alanlar (ekran uzayındaki önbellekli yol, son çizilen metin genişliği) kullanılabilir — fakat «tek doğruluk kaynağı» çoğu ekipte yine güncelleme çıktısıdır; çizim önbelleği ile oyun durumu karıştırılmamalıdır.
Kare döngüsünde çağrı sırası ve atomik sunum
Tipik requestAnimationFrame geri çağrısında sıra genelde şöyledir: ölçülen süreyi hesapla → güncelle → sun (temizlik + dünya + HUD). Kullanıcıya yansıyan bitmap, bu turun sonunda oluşan halidir; ara durumda ekranın yarım çizilmiş kalması üretim kalitesi açısından hatalıdır — istisna yalnızca kasıtlı hata ayıklama modlarıdır.
Bazı uygulamalar önce sunumu, sonra güncellemeyi dener; bu, bir kare gecikmesi yaratır ve girdinin ekranda hissedilir biçimde «bir adım geriden» gelmesine yol açar. Canvas odaklı etkileşimli sahnelerde varsayılan kural: bu karenin girdisi → bu karenin yeni durumu → bu karenin çizimi.
Çizim içinde ağır hesap (ör. grafik veri setini her karede sıralamak) sunum bütçesini yer; mümkünse sonuçları güncelleme aşamasında önbelleğe alıp sunumda yalnız okuyun. Yoğun iş güncellemede bloklanırsa yine kare süresi uzar — fakat kod okunurluğu ve profil ayırımı için blokların etiketlenmesi değerlidir.
/**
* world: saf veri nesnesi. update sadece world döndürür; render sadece okur.
*/
function tickFrame(ctx, canvas, world, dtSeconds, update, render) {
const next = update(world, dtSeconds, canvas);
render(ctx, canvas, next);
return next;
}
Tek görünür karede birden fazla simülasyon adımı
Gerçek süre iki kare arasında büyük gelebilir; sabit küçük adımlarla ilerleyen simülasyonlarda tek görünür kare içinde birden fazla güncelleme çalıştırılıp yine de yalnızca bir kez çizim yapılır. Bu, görsel ara pozların ihmal edildiği «adım adım doğru, çizimde kare başına tek snapshot» modelidir; ara görüntüyü yumuşatmak için interpolasyon ( interpolation) ayrı karardır — birikimli artık ve sabit adım ayrıntısı Delta time · sabit adım ile sınırlı örtüşür, burada yalnızca «güncelleme sayısı ≠ çizim sayısı» ilkesi vurgulanır.
Çok adım + tek çizim, CPU maliyetini aynı görünür karede toplar; ekranda donma hissi yaratmamak için birikim tavanı ve maksimum adım sayısı ( cap) kullanılır — aksi halde uzun sekme donmasından sonra saniyelerce yakalama döngüsüne girilir.
Tam tersi desen — tek güncelleme ama ara çizim denemeleri — genelde yanlıştır; kullanıcı ara bitmap’i görmemelidir. Özel durum: uzun işi kesip ilerleme çubuğu çizmek gibi bilinçli ara sunum, ayrı ürün kararıdır.
Girdi olayları, mantık güncellemesi ve sunum
İmleç ve dokunma olayları ana iş parçacığında tetiklenir; her olayda draw
çağırmak, aynı karede onlarca piksel geçişi üretebilir. Yerleşik desen: olayda yalnızca
durumu güncelle veya bir «kirli» bayrak kaldır, asıl çizimi
requestAnimationFrame ile birleştir — böylece tek
kompozit yolunda toplanır.
Tuşa basılı tutma gibi sürekli girdi, ya rAF içinde «basılı mı?» bayrakları ile okunur ya da olay birleştirilir. Canvas tek başınaysa girdi genelde DOM üzerinden gelir; güncelleme fonksiyonuna parametre olarak iletilen ham olayları, mümkünse erken aşamaya indirgenmiş niceliklere (dünya x/y) çevirmek sunum kodunu sade tutar.
Girdi ile sunum sırasını karıştırmamak: önce yeni girdiyi duruma işle, sonra o duruma göre çiz — böylece kullanıcı gördüğü kare, en güncel girişle uyumludur (tek iş parçacığı varsayımıyla).
/**
* Olaylarda sadece world güncellenir; çizim en fazla bir planlı rAF ile yapılır.
*/
function createCoalescedPainter(ctx, canvas, initialWorld, updateFromEvent, render) {
let world = initialWorld;
let rafId = 0;
let scheduled = false;
function frame() {
scheduled = false;
render(ctx, canvas, world);
}
function schedulePaint() {
if (scheduled) return;
scheduled = true;
rafId = requestAnimationFrame(frame);
}
function onEvent(e) {
world = updateFromEvent(world, e, canvas);
schedulePaint();
}
function dispose() {
cancelAnimationFrame(rafId);
scheduled = false;
}
return { onEvent, schedulePaint, dispose, getWorld: () => world };
}
Kirli bayrak ve gerektiğinde çizim
Statik veya nadiren değişen sahnelerde her rAF’ta tam clear + yeniden çizmek
pil ve CPU tüketir. Kirli bayrak deseni: yalnız durum değiştiğinde çizim
planlayın; animasyon durduğunda döngüyü durdurma (
optional) ile birleştirilebilir — fakat yeni girdi geldiğinde yeniden
başlatmayı unutmak yaygın hatadır.
Bayrağı «nesne değişti» düzeyinde tutmak yerine, bazen görsel etki alanı notasyonu ( invalidation) daha nettir: örneğin yalnız metin önbelleği kirliyse tam sahneyi değil metin bloğunu yeniden ölçüp çizersiniz. Özet tek bit bile yeterli olabilir; ekip içinde bayrağın anlamını (tam kare mi, alt düğüm mü) yazılı sözleşmeye bağlayın.
Kısmi kirli bölgeler ( Clear & redraw · kirli bölge ) ile birlikte düşünüldüğünde güncelleme hangi dikdörtgenleri etkilediyse sunumda yalnız o bölgeleri yenilemek mümkündür; maliyet hesabı sahne karmaşıklığına bağlıdır. Güncelleme tarafı bu dikdörtgenleri üretmezse sunum tarafı tahmin etmek zorunda kalır — kirli bölge sınırını mühendislik sızıntısı olmaması için ya duruma taşıyın ya da tek bir «görünür alan hesaplayıcı» modülde toplayın.
resize, cihaz piksel oranı veya yazı tipi yüklemesi gibi dış olaylar bazen
durumu numerik olarak değiştirmeden sunumu geçersiz kılar; kirli bayrak
yalnızca mantık deltasını izliyorsa ekran boş veya eski ölçekte kalabilir. Bu tür
sınırlarda tam yeniden çizim zorunluluğunu ayrı bayrakla veya sabit «her boyut değişiminde
tam repaint» kuralıyla kapatın (
Resize
mantığı kısa köprü).
Sürekli akış (oyun döngüsü) ile «olay olduğunda çiz» modeli aynı kodda karışırsa çift çizim
veya hiç çizilmeme oluşabilir; ekip içinde birincil motoru (
always-on rAF mu, olay güdümlü mü) seçin ve diğerini istisna olarak
dokümante edin. İkisi birden aktifse biri schedulePaint, diğeri doğrudan
render çağırıyor mu bir tabloyla sabitleyin — kod gözden geçirmede çelişki
böyle görünür olur.
Katman düzeni: güncelleme modülleri ve çizim sırası
Büyük projede güncelleme bileşenleri (fizik, otomasyon — ör. kaydırma veya zamanlayıcı —, kullanıcı aracı) ayrı fonksiyon veya sınıflar olur; çağrı sırası oyun kuralına göre sabitlenir. Bu sıra çoğu zaman bağımlılık grafına benzer: önce girdi işlendi mi, sonra fizik mi, en sonda oyun kuralları mı — sunum tarafı bu sıranın aynasını izlemek zorunda değildir.
Sunum tarafında ise tipik sıra arka plan → dünya nesneleri → ön plan / HUD korunur — güncelleme sırası ile çizim sırası aynı olmak zorunda değildir; önemli olan «hangi veri hangi sprite’a karşılık geliyor?» eşlemesinin tutarlı olmasıdır. Canvas 2D’de derinlik tamamen çağrı sırasıdır; önce çizilen altta kalır — z-index yoktur, bu yüzden sunum sırası doğrudan okunabilirlik ve çarpışma maskesi ile ilgilidir, güncelleme çağrı zinciri ile değil.
Aynı kare anlık görüntüsü için sunum başında dünya durumunun bir okunur
kopyası
veya tutarlı referansı kullanılır; güncelleme hâlâ sürerken render ortasında başka modülün
nesneyi taşıması «yarım kare» üretir. Pratik çözüm: tick içinde önce tüm güncellemeleri
bitir, sonra tek render(snapshot) — ara içeride paylaşılan nesneyi mutasyona
uğratmayın.
Aynı nesne için güncelleme iki yerde yapılıyorsa ( double update) hareket iki kat hızlanır veya dalgalanır. Bunu önlemek için tek sahiplik kuralı: her simülasyon niceliği için tek yazıcı modül seçin. İnce ayar: bileşenler arası ileti olayları ( message bus) kullanıyorsanız aynı olayı iki dinleyicinin iki kez tüketmediğinden emin olun.
Özel birleştirme geçişleri ve gölgeler sunum aşamasında kısa dallara bölünür; bu dallar
güncelleme mantığına karışmamalıdır — aksi halde bir sonraki karede stil sızıntısı üretir
(
State
sistemi ). Gölge veya filter gibi pahalı nitelikler yalnız HUD öğesine
uygulanacaksa, dünya çiziminden sonra dar bir dalda açılıp kapatılması okunabilirliği korur.
Anti-kalıplar: iç içe geçmiş aşamalar
Sunum içinde gizli güncelleme: Çizim fonksiyonu yan etkiyle skor veya
konumu değiştirirse hata ayıklama ve test imkânsızlaşır — sunum salt okunur kalsın.
Özellikle
fillText ile metin ölçümü yaparken metin kutusunu büyütmek için yanlışlıkla
durum alanını güncellemek, «neden sayaç iki kez arttı?» sorununu gizler.
Güncellemede doğrudan ctx komutu: Durum ile bağlam birbirine
yapışır; farklı yüzeye, baskı yoluna veya sunucu tarafı önizlemeye geçiş zorlaşır. İstisna:
gerçekten ölçüm amaçlı (
measureText ) çağrıları — bunları da mümkünse sunum öncesi önbelleğe alın.
Olay başına tam çizim: Performans ve titreşim artar; birleştirilmiş rAF tercih edin. İstisna: düşük frekanslı (ör. yılda bir kez açılan modal) tamamen statik arayüzler — yine de iki olayın aynı karede üst üste binmesi göz önünde bulundurulmalıdır.
Güncelleme sonra sunum önce: Bir kare gecikmesi ve hantal his — sırayı düzeltin. Pratik gösterge: girdi olayında durum anında değiştiği halde ekranda bir önceki kare görünüyorsa, tur içi sırayı veya kirli bayrak birleşimini gözden geçirin.
Asenkron güncelleme ile çakışma: Ağ veya worker cevabı geldiğinde dünya
düşünülmeden mutasyonlanıyorsa, o sırada çalışan render yarı tutarlı veri
okuyabilir. Çözüm: atomik değiş tokuş (
swap snapshot ), kilit veya tek iş parçacığı kuralı — konunun tamamı
bu sayfanın dışındadır, fakat «güncelle ve sun ayrımı» varken yarışlar yine görülür.
Test ve snapshot: Saf update(state, action) → state yazımı,
birim testinde kolaydır; render ise görsel regresyon veya ekran görüntüsü ile
doğrulanır. İkisi birbirine yapışık olunca test piramidi çöker — ayrımın faydası burada da
kendini gösterir.
Bu sayfanın sınırı
Çok iş parçacıklı motorlar, Web Worker ile veri paylaşımı ve kilitlenmesiz yapılar bu sayfada işlenmez; varsayım tek ana iş parçacığı ve klasik 2D bağlamdır.
Entity–component mimarisi veya büyük ölçekli saha grafı ayrı tasarım konularıdır; ilke düzeyi «güncelle / sun» ayrımı burada sabittir.
- Güncelleme fonksiyonu
ctxyazıyor mu (istenmeyen yan etki)? - Aynı turda girdi → güncelle → sun sırası korunuyor mu?
- Olay fırtınasında çizim birleştirildi mi?
- Çift güncelleme veya çift çizim riski var mı?
- Kirli bayrak yalnız mantık mı izliyor; resize / font sonrası tam boyama tetiklendi mi?
- Asenkron cevap dünyayı mutasyonlarken
renderyarışta mı?