Web Workers untuk logika game
Web Workers memungkinkan Anda menjalankan JavaScript di thread latar belakang. Untuk game, ini berarti fisika, AI, dan generasi prosedural dapat berjalan tanpa menyebabkan penurunan frame rate.
1) Kapan harus menggunakan Workers
Cocok digunakan untuk:
- Simulasi fisika
- Pencarian jalur (A*, navmesh)
- Pengambilan keputusan AI
- Generasi prosedural
- Pemrosesan aset (manipulasi gambar, kompresi)
- Matematika kompleks (FFT, deteksi tabrakan)
Kurang ideal untuk:
- Rendering (Workers tidak dapat mengakses DOM/Canvas secara langsung)
- Tugas yang sangat kecil (overhead thread tidak sepadan)
2) Membuat Worker dasar
worker.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) {
// Expensive work here
return input * 2
}main.js:
const worker = new Worker('worker.js')
worker.onmessage = (e) => {
const { type, data } = e.data
if (type === 'result') {
console.log('Got result:', data)
}
}
worker.postMessage({ type: 'calculate', data: 42 })3) Worker inline (tanpa berkas terpisah)
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) Worker modul ES
Anda dapat menulis kode worker sebagai modul ES dan menggunakan import di dalamnya sebagai pengganti importScripts. Teruskan { type: 'module' } ke konstruktor:
const worker = new Worker('physics-worker.js', { type: 'module' })Di dalam worker, Anda kemudian dapat menggunakan import untuk helper matematika, tabrakan, atau AI bersama dengan cara yang sama seperti di thread utama, sehingga logika game tidak perlu diduplikasi. Worker modul didukung di Chrome dan Edge 80+, Safari 15+, serta Firefox 114+.
5) Contoh Worker fisika
physics-worker.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) {
// Initialize physics world
}
function step() {
// Update physics
for (const body of bodies) {
body.vy += 9.8 * FIXED_DT // Gravity
body.x += body.vx * FIXED_DT
body.y += body.vy * FIXED_DT
}
// Send positions back
self.postMessage({
type: 'positions',
data: bodies.map(b => ({ id: b.id, x: b.x, y: b.y, rotation: b.rotation }))
})
}6) Objek transferable untuk performa
Data berukuran besar dapat ditransfer tanpa disalin:
// Main thread
const positions = new Float32Array(1000)
worker.postMessage(positions, [positions.buffer])
// positions is now unusable here (transferred)
// Worker
self.onmessage = (e) => {
const positions = e.data
// Work with positions
self.postMessage(positions, [positions.buffer])
}7) Worker pencarian jalur
pathfinding-worker.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* implementation
const openSet = [start]
const cameFrom = new Map()
const gScore = new Map()
gScore.set(key(start), 0)
while (openSet.length > 0) {
// ... A* logic
}
return reconstructPath(cameFrom, end)
}
function key(pos) {
return `${pos.x},${pos.y}`
}8) Pool Worker untuk tugas paralel
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) SharedArrayBuffer untuk sinkronisasi real-time
Dengan isolasi lintas origin yang diaktifkan, Anda dapat berbagi memori:
// Main thread
const shared = new SharedArrayBuffer(1024)
const positions = new Float32Array(shared)
worker.postMessage({ type: 'init', buffer: shared })
// Worker reads/writes directly to shared memory
// No postMessage overhead for position updates10) OffscreenCanvas untuk Workers
Lakukan rendering di dalam Worker. OffscreenCanvas kini tersedia secara luas sebagai Baseline di Chrome, Edge, Firefox, dan Safari (Safari menambahkan dukungan pada versi 17.0 di macOS dan iOS):
// Main thread
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)
// Draw...
requestAnimationFrame(render)
}
render()
}11) Penanganan error
worker.onerror = (e) => {
console.error('Worker error:', e.message, e.filename, e.lineno)
}
// In worker
self.onerror = (e) => {
self.postMessage({ type: 'error', message: e.message })
}Terkait
- Rilis game web yang dimuat dengan cepat
- Aktifkan thread Wasm (SharedArrayBuffer)
- Game loop Canvas 2D
- Pustaka fisika game — menjalankan Rapier atau Cannon-es di dalam Worker
- Pemuatan aset secara streaming — mendekode aset di luar thread utama
- Stack teknologi game web pada 2026 — cara Workers terintegrasi ke dalam stack WebGL/WebGPU/Wasm
Sumber Daya Eksternal
- MDN: API Web Workers — referensi API lengkap
- MDN: Menggunakan Web Workers — panduan langkah demi langkah
- MDN: Objek transferable — transfer data tanpa penyalinan
- MDN: SharedArrayBuffer — memori bersama antar-thread