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