Skip to content

Oyun mantığı için Web Workers ​

Web Workers, JavaScript'i bir arka plan iş parçacığında çalıştırmanızı sağlar. Oyunlarda bu, fizik, yapay zekâ ve prosedürel üretimin kare kaybına yol açmadan çalışabileceği anlamına gelir.

1) Workers ne zaman kullanılmalı? ​

Uygun kullanım alanları:

  • Fizik simülasyonu
  • Yol bulma (A*, navmesh)
  • Yapay zekâ karar verme
  • Prosedürel üretim
  • Varlık işleme (görüntü düzenleme, sıkıştırma)
  • Karmaşık matematiksel işlemler (FFT, çarpışma algılama)

Şunlar için ideal değildir:

  • Render işlemleri (Workers, DOM/Canvas'a doğrudan erişemez)
  • Çok küçük görevler (iş parçacığı ek yüküne değmez)

2) Temel bir Worker oluşturma ​

worker.js:

js
self.onmessage = (e) => {
  const { type, data } = e.data
  
  if (type === 'calculate') {
    const result = heavyCalculation(data)
    self.postMessage({ type: 'result', data: result })
  }
}

function heavyCalculation(input) {
  // Yoğun işlem burada
  return input * 2
}

main.js:

js
const worker = new Worker('worker.js')

worker.onmessage = (e) => {
  const { type, data } = e.data
  if (type === 'result') {
    console.log('Sonuç alındı:', data)
  }
}

worker.postMessage({ type: 'calculate', data: 42 })

3) Satır içi Workers (ayrı dosya olmadan) ​

js
function createInlineWorker(fn) {
  const blob = new Blob([`(${fn.toString()})()`], { type: 'text/javascript' })
  return new Worker(URL.createObjectURL(blob))
}

const worker = createInlineWorker(() => {
  self.onmessage = (e) => {
    const result = e.data * 2
    self.postMessage(result)
  }
})

4) ES modülü Workers ​

Worker kodunu bir ES modülü olarak yazabilir ve importScripts yerine içinde import kullanabilirsiniz. Oluşturucuya { type: 'module' } iletin:

js
const worker = new Worker('physics-worker.js', { type: 'module' })

Ardından Worker içinde paylaşılan matematik, çarpışma veya yapay zekâ yardımcılarını ana iş parçacığında olduğu gibi import edebilirsiniz; böylece oyun mantığı yinelenmez. Modül Workers, Chrome ve Edge 80+, Safari 15+ ve Firefox 114+ sürümlerinde desteklenir.

5) Fizik Worker örneği ​

physics-worker.js:

js
const bodies = []
const FIXED_DT = 1 / 60

self.onmessage = (e) => {
  const { type, data } = e.data
  
  switch (type) {
    case 'init':
      initWorld(data)
      break
    case 'step':
      step()
      break
    case 'addBody':
      bodies.push(data)
      break
  }
}

function initWorld(config) {
  // Fizik dünyasını başlat
}

function step() {
  // Fiziği güncelle
  for (const body of bodies) {
    body.vy += 9.8 * FIXED_DT // Yer çekimi
    body.x += body.vx * FIXED_DT
    body.y += body.vy * FIXED_DT
  }
  
  // Konumları geri gönder
  self.postMessage({
    type: 'positions',
    data: bodies.map(b => ({ id: b.id, x: b.x, y: b.y, rotation: b.rotation }))
  })
}

6) Performans için aktarılabilir nesneler ​

Büyük veriler kopyalanmadan aktarılabilir:

js
// Ana iş parçacığı
const positions = new Float32Array(1000)
worker.postMessage(positions, [positions.buffer])
// positions artık burada kullanılamaz (aktarıldı)

// Worker
self.onmessage = (e) => {
  const positions = e.data
  // Konumlarla çalış
  self.postMessage(positions, [positions.buffer])
}

7) Yol bulma Worker'ı ​

pathfinding-worker.js:

js
let grid = null

self.onmessage = (e) => {
  const { type, data } = e.data
  
  if (type === 'setGrid') {
    grid = data
  }
  
  if (type === 'findPath') {
    const path = aStar(data.start, data.end, grid)
    self.postMessage({ type: 'path', id: data.id, path })
  }
}

function aStar(start, end, grid) {
  // A* uygulaması
  const openSet = [start]
  const cameFrom = new Map()
  const gScore = new Map()
  gScore.set(key(start), 0)
  
  while (openSet.length > 0) {
    // ... A* mantığı
  }
  
  return reconstructPath(cameFrom, end)
}

function key(pos) {
  return `${pos.x},${pos.y}`
}

8) Paralel görevler için Worker havuzu ​

js
class WorkerPool {
  constructor(workerUrl, size = navigator.hardwareConcurrency || 4) {
    this.workers = []
    this.queue = []
    this.available = []
    
    for (let i = 0; i < size; i++) {
      const worker = new Worker(workerUrl)
      worker.onmessage = (e) => this.handleResult(worker, e)
      this.workers.push(worker)
      this.available.push(worker)
    }
  }
  
  run(data) {
    return new Promise((resolve) => {
      const task = { data, resolve }
      
      if (this.available.length > 0) {
        this.dispatch(this.available.pop(), task)
      } else {
        this.queue.push(task)
      }
    })
  }
  
  dispatch(worker, task) {
    worker._currentTask = task
    worker.postMessage(task.data)
  }
  
  handleResult(worker, e) {
    const task = worker._currentTask
    task.resolve(e.data)
    
    if (this.queue.length > 0) {
      this.dispatch(worker, this.queue.shift())
    } else {
      this.available.push(worker)
    }
  }
  
  terminate() {
    this.workers.forEach(w => w.terminate())
  }
}

9) Gerçek zamanlı senkronizasyon için SharedArrayBuffer ​

Kaynaklar arası yalıtım etkinleştirildiğinde belleği paylaşabilirsiniz:

js
// Ana iş parçacığı
const shared = new SharedArrayBuffer(1024)
const positions = new Float32Array(shared)

worker.postMessage({ type: 'init', buffer: shared })

// Worker, paylaşılan belleği doğrudan okur/yazar
// Konum güncellemelerinde postMessage ek yükü yoktur

10) Workers için OffscreenCanvas ​

Render işlemlerini bir Worker içinde gerçekleştirin. OffscreenCanvas artık Chrome, Edge, Firefox ve Safari genelinde Baseline Widely Available durumundadır (Safari desteği macOS ve iOS'ta 17.0 sürümüyle eklendi):

js
// Ana iş parçacığı
const canvas = document.getElementById('game')
const offscreen = canvas.transferControlToOffscreen()
worker.postMessage({ canvas: offscreen }, [offscreen])

// Worker
self.onmessage = (e) => {
  const canvas = e.data.canvas
  const ctx = canvas.getContext('2d')
  
  function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    // Çiz...
    requestAnimationFrame(render)
  }
  render()
}

11) Hata yönetimi ​

js
worker.onerror = (e) => {
  console.error('Worker hatası:', e.message, e.filename, e.lineno)
}

// Worker içinde
self.onerror = (e) => {
  self.postMessage({ type: 'error', message: e.message })
}

İlgili içerikler ​

Harici Kaynaklar ​