Web Workers to technologia umożliwiająca wykonywanie JavaScript w osobnych wątkach, niezależnie od głównego wątku UI (User Interface). Pozwala to na przeprowadzanie ciężkich obliczeń bez blokowania interfejsu użytkownika, co jest kluczowe dla utrzymania responsywności aplikacji webowych.
1// Problem: Blokujące obliczenia w głównym wątku
2function heavyCalculation(n) {
3 console.log('Rozpoczynanie ciężkich obliczeń...');
4
5 // Symulacja intensywnych obliczeń
6 let result = 0;
7 for (let i = 0; i < n; i++) {
8 for (let j = 0; j < 1000000; j++) {
9 result += Math.sqrt(i * j);
10 }
11 }
12
13 console.log('Obliczenia zakończone');
14 return result;
15}
16
17// To zablokuje UI na kilka sekund!
18document.getElementById('calculate-button').addEventListener('click', () => {
19 const result = heavyCalculation(5000);
20 document.getElementById('result').textContent = result;
21});
22
23// Podczas obliczeń użytkownik nie może:
24// - Kliknąć innych przycisków
25// - Przewijać strony
26// - Używać animacji
27// - Wpisywać tekst w formularze1// Benchmark: Main Thread vs Web Worker
2const performanceTest = {
3 // Test bez Web Worker - blokuje UI
4 async testMainThread() {
5 console.time('Main Thread Calculation');
6
7 const start = performance.now();
8 const result = this.calculatePrimes(100000);
9 const end = performance.now();
10
11 console.timeEnd('Main Thread Calculation');
12
13 return {
14 result: result.length,
15 time: end - start,
16 blocked: true
17 };
18 },
19
20 // Test z Web Worker - nie blokuje UI
21 async testWebWorker() {
22 console.time('Web Worker Calculation');
23
24 const start = performance.now();
25 const result = await this.calculatePrimesWorker(100000);
26 const end = performance.now();
27
28 console.timeEnd('Web Worker Calculation');
29
30 return {
31 result: result.length,
32 time: end - start,
33 blocked: false
34 };
35 },
36
37 calculatePrimes(max) {
38 const primes = [];
39 for (let i = 2; i <= max; i++) {
40 if (this.isPrime(i)) {
41 primes.push(i);
42 }
43 }
44 return primes;
45 },
46
47 isPrime(num) {
48 for (let i = 2; i <= Math.sqrt(num); i++) {
49 if (num % i === 0) return false;
50 }
51 return true;
52 }
53};1// main.js - główny wątk
2class WebWorkerManager {
3 constructor() {
4 this.worker = null;
5 this.setupWorker();
6 }
7
8 setupWorker() {
9 // Sprawdzenie wsparcia dla Web Workers
10 if (typeof Worker !== 'undefined') {
11 this.worker = new Worker('worker.js');
12
13 // Obsługa wiadomości z worker
14 this.worker.onmessage = (event) => {
15 this.handleWorkerMessage(event.data);
16 };
17
18 // Obsługa błędów
19 this.worker.onerror = (error) => {
20 console.error('Worker error:', error);
21 };
22
23 console.log('Web Worker został utworzony');
24 } else {
25 console.warn('Web Workers nie są wspierane w tej przeglądarce');
26 }
27 }
28
29 // Wysyłanie zadania do worker
30 sendTask(taskType, data) {
31 if (this.worker) {
32 this.worker.postMessage({
33 type: taskType,
34 data: data,
35 timestamp: Date.now()
36 });
37 }
38 }
39
40 // Obsługa odpowiedzi z worker
41 handleWorkerMessage(message) {
42 switch (message.type) {
43 case 'CALCULATION_COMPLETE':
44 this.displayResult(message.result);
45 break;
46 case 'PROGRESS_UPDATE':
47 this.updateProgress(message.progress);
48 break;
49 case 'ERROR':
50 this.handleError(message.error);
51 break;
52 }
53 }
54
55 // Zakończenie pracy worker
56 terminateWorker() {
57 if (this.worker) {
58 this.worker.terminate();
59 this.worker = null;
60 console.log('Web Worker został zakończony');
61 }
62 }
63}
64
65// Użycie
66const workerManager = new WebWorkerManager();
67
68// Wysłanie zadania obliczeń
69document.getElementById('start-calculation').addEventListener('click', () => {
70 workerManager.sendTask('CALCULATE_PRIMES', { max: 100000 });
71});1// worker.js - kod wykonywany w osobnym wątku
2class WorkerCalculations {
3 constructor() {
4 this.setupMessageHandler();
5 }
6
7 setupMessageHandler() {
8 // Główny handler wiadomości z głównego wątku
9 self.onmessage = (event) => {
10 const { type, data, timestamp } = event.data;
11
12 try {
13 this.handleTask(type, data, timestamp);
14 } catch (error) {
15 this.sendError(error.message);
16 }
17 };
18 }
19
20 handleTask(type, data, timestamp) {
21 switch (type) {
22 case 'CALCULATE_PRIMES':
23 this.calculatePrimes(data.max);
24 break;
25 case 'PROCESS_IMAGE':
26 this.processImage(data.imageData);
27 break;
28 case 'SORT_LARGE_ARRAY':
29 this.sortArray(data.array);
30 break;
31 case 'MONTE_CARLO_PI':
32 this.calculatePiMonteCarlo(data.iterations);
33 break;
34 default:
35 this.sendError(`Unknown task type: ${type}`);
36 }
37 }
38
39 // Obliczanie liczb pierwszych z raportowaniem postępu
40 calculatePrimes(max) {
41 const primes = [];
42 const updateInterval = Math.floor(max / 100); // Update co 1%
43
44 for (let i = 2; i <= max; i++) {
45 if (this.isPrime(i)) {
46 primes.push(i);
47 }
48
49 // Raportowanie postępu
50 if (i % updateInterval === 0) {
51 const progress = (i / max) * 100;
52 this.sendProgress(progress);
53 }
54 }
55
56 this.sendResult('CALCULATION_COMPLETE', {
57 primes: primes,
58 count: primes.length,
59 executionTime: performance.now()
60 });
61 }
62
63 isPrime(num) {
64 if (num < 2) return false;
65 for (let i = 2; i <= Math.sqrt(num); i++) {
66 if (num % i === 0) return false;
67 }
68 return true;
69 }
70
71 // Przetwarzanie obrazu (filtrowanie, transformacje)
72 processImage(imageData) {
73 const { data, width, height, filterType } = imageData;
74
75 switch (filterType) {
76 case 'grayscale':
77 this.applyGrayscaleFilter(data);
78 break;
79 case 'blur':
80 this.applyBlurFilter(data, width, height);
81 break;
82 case 'edge':
83 this.applyEdgeDetection(data, width, height);
84 break;
85 }
86
87 this.sendResult('IMAGE_PROCESSED', {
88 processedData: data,
89 filterApplied: filterType
90 });
91 }
92
93 applyGrayscaleFilter(data) {
94 for (let i = 0; i < data.length; i += 4) {
95 const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
96 data[i] = gray; // Red
97 data[i + 1] = gray; // Green
98 data[i + 2] = gray; // Blue
99 // Alpha (i + 3) remains unchanged
100 }
101 }
102
103 // Sortowanie dużych tablic
104 sortArray(array) {
105 const startTime = performance.now();
106
107 // Implementacja merge sort dla lepszej wydajności
108 const sortedArray = this.mergeSort(array);
109
110 const endTime = performance.now();
111
112 this.sendResult('SORT_COMPLETE', {
113 sortedArray: sortedArray,
114 originalLength: array.length,
115 executionTime: endTime - startTime
116 });
117 }
118
119 mergeSort(arr) {
120 if (arr.length <= 1) return arr;
121
122 const mid = Math.floor(arr.length / 2);
123 const left = this.mergeSort(arr.slice(0, mid));
124 const right = this.mergeSort(arr.slice(mid));
125
126 return this.merge(left, right);
127 }
128
129 merge(left, right) {
130 const result = [];
131 let leftIndex = 0;
132 let rightIndex = 0;
133
134 while (leftIndex < left.length && rightIndex < right.length) {
135 if (left[leftIndex] <= right[rightIndex]) {
136 result.push(left[leftIndex]);
137 leftIndex++;
138 } else {
139 result.push(right[rightIndex]);
140 rightIndex++;
141 }
142 }
143
144 return result.concat(left.slice(leftIndex), right.slice(rightIndex));
145 }
146
147 // Obliczanie Pi metodą Monte Carlo
148 calculatePiMonteCarlo(iterations) {
149 let pointsInCircle = 0;
150 const updateInterval = Math.floor(iterations / 100);
151
152 for (let i = 0; i < iterations; i++) {
153 const x = Math.random() * 2 - 1; // -1 do 1
154 const y = Math.random() * 2 - 1; // -1 do 1
155
156 if (x * x + y * y <= 1) {
157 pointsInCircle++;
158 }
159
160 if (i % updateInterval === 0) {
161 const currentPi = (pointsInCircle / i) * 4;
162 const progress = (i / iterations) * 100;
163
164 this.sendProgress(progress, { currentPi, iteration: i });
165 }
166 }
167
168 const finalPi = (pointsInCircle / iterations) * 4;
169
170 this.sendResult('PI_CALCULATION_COMPLETE', {
171 pi: finalPi,
172 iterations: iterations,
173 accuracy: Math.abs(Math.PI - finalPi)
174 });
175 }
176
177 // Metody pomocnicze do komunikacji
178 sendResult(type, result) {
179 self.postMessage({
180 type: type,
181 result: result,
182 timestamp: Date.now()
183 });
184 }
185
186 sendProgress(progress, additionalData = {}) {
187 self.postMessage({
188 type: 'PROGRESS_UPDATE',
189 progress: progress,
190 ...additionalData,
191 timestamp: Date.now()
192 });
193 }
194
195 sendError(errorMessage) {
196 self.postMessage({
197 type: 'ERROR',
198 error: errorMessage,
199 timestamp: Date.now()
200 });
201 }
202}
203
204// Inicjalizacja worker
205new WorkerCalculations();1// WorkerPool.js - Zarządzanie wieloma worker'ami
2class WorkerPool {
3 constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
4 this.workerScript = workerScript;
5 this.poolSize = poolSize;
6 this.workers = [];
7 this.taskQueue = [];
8 this.activeTasksCount = 0;
9
10 this.initializePool();
11 }
12
13 initializePool() {
14 for (let i = 0; i < this.poolSize; i++) {
15 const worker = {
16 instance: new Worker(this.workerScript),
17 busy: false,
18 id: i
19 };
20
21 worker.instance.onmessage = (event) => {
22 this.handleWorkerMessage(worker, event.data);
23 };
24
25 worker.instance.onerror = (error) => {
26 this.handleWorkerError(worker, error);
27 };
28
29 this.workers.push(worker);
30 }
31
32 console.log(`Worker pool initialized with ${this.poolSize} workers`);
33 }
34
35 // Wykonanie zadania z wykorzystaniem pool
36 execute(taskData) {
37 return new Promise((resolve, reject) => {
38 const task = {
39 data: taskData,
40 resolve: resolve,
41 reject: reject,
42 id: this.generateTaskId()
43 };
44
45 const availableWorker = this.getAvailableWorker();
46
47 if (availableWorker) {
48 this.assignTaskToWorker(availableWorker, task);
49 } else {
50 this.taskQueue.push(task);
51 }
52 });
53 }
54
55 getAvailableWorker() {
56 return this.workers.find(worker => !worker.busy);
57 }
58
59 assignTaskToWorker(worker, task) {
60 worker.busy = true;
61 worker.currentTask = task;
62 this.activeTasksCount++;
63
64 worker.instance.postMessage({
65 taskId: task.id,
66 ...task.data
67 });
68 }
69
70 handleWorkerMessage(worker, message) {
71 const task = worker.currentTask;
72
73 if (message.type === 'TASK_COMPLETE') {
74 task.resolve(message.result);
75 this.releaseWorker(worker);
76 } else if (message.type === 'TASK_ERROR') {
77 task.reject(new Error(message.error));
78 this.releaseWorker(worker);
79 } else if (message.type === 'PROGRESS_UPDATE') {
80 // Przekazanie aktualizacji postępu (opcjonalne)
81 if (task.onProgress) {
82 task.onProgress(message.progress);
83 }
84 }
85 }
86
87 releaseWorker(worker) {
88 worker.busy = false;
89 worker.currentTask = null;
90 this.activeTasksCount--;
91
92 // Sprawdzenie czy są zadania w kolejce
93 if (this.taskQueue.length > 0) {
94 const nextTask = this.taskQueue.shift();
95 this.assignTaskToWorker(worker, nextTask);
96 }
97 }
98
99 handleWorkerError(worker, error) {
100 console.error(`Worker ${worker.id} error:`, error);
101
102 if (worker.currentTask) {
103 worker.currentTask.reject(error);
104 this.releaseWorker(worker);
105 }
106 }
107
108 // Równoległe przetwarzanie dużych zbiorów danych
109 async processInParallel(dataArray, chunkProcessor) {
110 const chunkSize = Math.ceil(dataArray.length / this.poolSize);
111 const chunks = [];
112
113 // Podział danych na chunks
114 for (let i = 0; i < dataArray.length; i += chunkSize) {
115 chunks.push(dataArray.slice(i, i + chunkSize));
116 }
117
118 // Równoległe przetwarzanie chunks
119 const promises = chunks.map(chunk =>
120 this.execute({
121 type: 'PROCESS_CHUNK',
122 chunk: chunk,
123 processor: chunkProcessor.toString()
124 })
125 );
126
127 const results = await Promise.all(promises);
128
129 // Połączenie wyników
130 return results.flat();
131 }
132
133 generateTaskId() {
134 return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
135 }
136
137 // Zamknięcie wszystkich worker'ów
138 terminate() {
139 this.workers.forEach(worker => {
140 worker.instance.terminate();
141 });
142
143 this.workers = [];
144 this.taskQueue = [];
145 console.log('Worker pool terminated');
146 }
147
148 // Statystyki pool
149 getStats() {
150 return {
151 poolSize: this.poolSize,
152 activeWorkers: this.workers.filter(w => w.busy).length,
153 queueLength: this.taskQueue.length,
154 activeTasks: this.activeTasksCount
155 };
156 }
157}
158
159// Użycie Worker Pool
160const workerPool = new WorkerPool('calculation-worker.js', 8);
161
162// Przykład równoległego przetwarzania
163async function processLargeDataset() {
164 const largeArray = Array.from({ length: 1000000 }, (_, i) => Math.random() * 1000);
165
166 try {
167 const results = await workerPool.processInParallel(largeArray, (chunk) => {
168 // Funkcja przetwarzania będzie wykonana w worker
169 return chunk.map(x => Math.sqrt(x * x + 1));
170 });
171
172 console.log('Przetwarzanie zakończone:', results.length);
173 console.log('Stats:', workerPool.getStats());
174 } catch (error) {
175 console.error('Błąd przetwarzania:', error);
176 }
177}1// SharedArrayBufferWorker.js - Współdzielona pamięć między worker'ami
2class SharedArrayBufferManager {
3 constructor() {
4 this.checkSupport();
5 }
6
7 checkSupport() {
8 if (typeof SharedArrayBuffer === 'undefined') {
9 console.warn('SharedArrayBuffer nie jest wspierany');
10 this.fallbackToTransferableObjects();
11 return false;
12 }
13 return true;
14 }
15
16 // Tworzenie współdzielonej pamięci do obliczeń macierzy
17 createSharedMatrixBuffer(rows, cols) {
18 const bufferSize = rows * cols * Float64Array.BYTES_PER_ELEMENT;
19 const sharedBuffer = new SharedArrayBuffer(bufferSize);
20
21 return {
22 buffer: sharedBuffer,
23 matrix: new Float64Array(sharedBuffer),
24 rows: rows,
25 cols: cols,
26
27 // Metody pomocnicze
28 get: (row, col) => {
29 return this.matrix[row * cols + col];
30 },
31
32 set: (row, col, value) => {
33 this.matrix[row * cols + col] = value;
34 },
35
36 fill: (value) => {
37 this.matrix.fill(value);
38 }
39 };
40 }
41
42 // Równoległe mnożenie macierzy
43 async parallelMatrixMultiply(matrixA, matrixB, workerCount = 4) {
44 const rowsA = matrixA.rows;
45 const colsA = matrixA.cols;
46 const colsB = matrixB.cols;
47
48 // Tworzenie współdzielonej macierzy wynikowej
49 const resultMatrix = this.createSharedMatrixBuffer(rowsA, colsB);
50
51 // Podział pracy między worker'y
52 const rowsPerWorker = Math.ceil(rowsA / workerCount);
53 const workers = [];
54
55 for (let i = 0; i < workerCount; i++) {
56 const startRow = i * rowsPerWorker;
57 const endRow = Math.min(startRow + rowsPerWorker, rowsA);
58
59 if (startRow < rowsA) {
60 const worker = new Worker('matrix-worker.js');
61
62 const promise = new Promise((resolve, reject) => {
63 worker.onmessage = (event) => {
64 if (event.data.type === 'MATRIX_COMPLETE') {
65 worker.terminate();
66 resolve(event.data.result);
67 }
68 };
69
70 worker.onerror = reject;
71 });
72
73 worker.postMessage({
74 type: 'MULTIPLY_MATRIX_ROWS',
75 matrixABuffer: matrixA.buffer,
76 matrixBBuffer: matrixB.buffer,
77 resultBuffer: resultMatrix.buffer,
78 dimensions: {
79 rowsA: rowsA,
80 colsA: colsA,
81 colsB: colsB
82 },
83 rowRange: { start: startRow, end: endRow }
84 });
85
86 workers.push(promise);
87 }
88 }
89
90 await Promise.all(workers);
91 return resultMatrix;
92 }
93
94 fallbackToTransferableObjects() {
95 console.log('Używanie Transferable Objects jako alternatywy');
96 // Implementacja z Transferable Objects
97 }
98}
99
100// matrix-worker.js - Worker do obliczeń macierzy
101self.onmessage = function(event) {
102 const { type, matrixABuffer, matrixBBuffer, resultBuffer, dimensions, rowRange } = event.data;
103
104 if (type === 'MULTIPLY_MATRIX_ROWS') {
105 const matrixA = new Float64Array(matrixABuffer);
106 const matrixB = new Float64Array(matrixBBuffer);
107 const result = new Float64Array(resultBuffer);
108
109 const { rowsA, colsA, colsB } = dimensions;
110 const { start, end } = rowRange;
111
112 // Mnożenie macierzy dla przydzielonych wierszy
113 for (let i = start; i < end; i++) {
114 for (let j = 0; j < colsB; j++) {
115 let sum = 0;
116 for (let k = 0; k < colsA; k++) {
117 sum += matrixA[i * colsA + k] * matrixB[k * colsB + j];
118 }
119 result[i * colsB + j] = sum;
120 }
121 }
122
123 self.postMessage({
124 type: 'MATRIX_COMPLETE',
125 result: `Rows ${start}-${end} completed`
126 });
127 }
128};1// ServiceWorkerManager.js - Zarządzanie długotrwałymi zadaniami w tle
2class ServiceWorkerManager {
3 constructor() {
4 this.serviceWorker = null;
5 this.messageChannel = null;
6 this.setupServiceWorker();
7 }
8
9 async setupServiceWorker() {
10 if ('serviceWorker' in navigator) {
11 try {
12 const registration = await navigator.serviceWorker.register('background-worker.js');
13
14 // Oczekiwanie na aktywację service worker
15 await navigator.serviceWorker.ready;
16
17 this.serviceWorker = registration.active || registration.waiting || registration.installing;
18 this.setupMessageChannel();
19
20 console.log('Service Worker zarejestrowany i gotowy');
21 } catch (error) {
22 console.error('Błąd rejestracji Service Worker:', error);
23 }
24 }
25 }
26
27 setupMessageChannel() {
28 this.messageChannel = new MessageChannel();
29
30 // Obsługa wiadomości od Service Worker
31 this.messageChannel.port1.onmessage = (event) => {
32 this.handleServiceWorkerMessage(event.data);
33 };
34
35 // Wysłanie portu do Service Worker
36 navigator.serviceWorker.controller?.postMessage(
37 { type: 'INIT_PORT' },
38 [this.messageChannel.port2]
39 );
40 }
41
42 // Zlecenie długotrwałego zadania do Service Worker
43 scheduleBackgroundTask(taskConfig) {
44 if (this.messageChannel) {
45 this.messageChannel.port1.postMessage({
46 type: 'SCHEDULE_TASK',
47 task: {
48 id: this.generateTaskId(),
49 ...taskConfig,
50 scheduledAt: Date.now()
51 }
52 });
53 }
54 }
55
56 // Przykład: Okresowe synchronizowanie danych
57 scheduleDataSync(interval = 300000) { // 5 minut
58 this.scheduleBackgroundTask({
59 type: 'DATA_SYNC',
60 interval: interval,
61 endpoint: '/api/sync',
62 recurring: true
63 });
64 }
65
66 // Przykład: Przetwarzanie plików w tle
67 scheduleFileProcessing(files) {
68 files.forEach(file => {
69 this.scheduleBackgroundTask({
70 type: 'FILE_PROCESSING',
71 fileName: file.name,
72 fileData: file,
73 processor: 'image-compression'
74 });
75 });
76 }
77
78 handleServiceWorkerMessage(message) {
79 switch (message.type) {
80 case 'TASK_COMPLETED':
81 this.onTaskCompleted(message.task, message.result);
82 break;
83 case 'TASK_FAILED':
84 this.onTaskFailed(message.task, message.error);
85 break;
86 case 'SYNC_COMPLETED':
87 this.onSyncCompleted(message.result);
88 break;
89 }
90 }
91
92 onTaskCompleted(task, result) {
93 console.log(`Zadanie ${task.id} zakończone:`, result);
94
95 // Aktualizacja UI lub stanu aplikacji
96 this.updateApplicationState(task, result);
97 }
98
99 generateTaskId() {
100 return `bg_task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
101 }
102}
103
104// background-worker.js - Service Worker
105class BackgroundTaskProcessor {
106 constructor() {
107 this.activeTasks = new Map();
108 this.messagePort = null;
109 this.setupEventListeners();
110 }
111
112 setupEventListeners() {
113 self.addEventListener('message', (event) => {
114 if (event.data.type === 'INIT_PORT') {
115 this.messagePort = event.ports[0];
116 this.setupPortListener();
117 }
118 });
119
120 // Background Sync
121 self.addEventListener('sync', (event) => {
122 if (event.tag === 'background-data-sync') {
123 event.waitUntil(this.performDataSync());
124 }
125 });
126 }
127
128 setupPortListener() {
129 this.messagePort.onmessage = (event) => {
130 this.handleTask(event.data);
131 };
132 }
133
134 async handleTask(message) {
135 if (message.type === 'SCHEDULE_TASK') {
136 const task = message.task;
137
138 try {
139 const result = await this.executeTask(task);
140
141 this.messagePort.postMessage({
142 type: 'TASK_COMPLETED',
143 task: task,
144 result: result
145 });
146 } catch (error) {
147 this.messagePort.postMessage({
148 type: 'TASK_FAILED',
149 task: task,
150 error: error.message
151 });
152 }
153 }
154 }
155
156 async executeTask(task) {
157 switch (task.type) {
158 case 'DATA_SYNC':
159 return await this.performDataSync(task);
160 case 'FILE_PROCESSING':
161 return await this.processFile(task);
162 case 'BATCH_CALCULATION':
163 return await this.performBatchCalculation(task);
164 default:
165 throw new Error(`Unknown task type: ${task.type}`);
166 }
167 }
168
169 async performDataSync(task) {
170 try {
171 const response = await fetch(task.endpoint, {
172 method: 'POST',
173 headers: { 'Content-Type': 'application/json' },
174 body: JSON.stringify({ timestamp: Date.now() })
175 });
176
177 if (!response.ok) {
178 throw new Error(`Sync failed: ${response.status}`);
179 }
180
181 const data = await response.json();
182
183 // Zapisanie w IndexedDB lub Cache API
184 await this.storeDataLocally(data);
185
186 return { success: true, syncedRecords: data.length };
187 } catch (error) {
188 throw new Error(`Data sync failed: ${error.message}`);
189 }
190 }
191
192 async processFile(task) {
193 // Symulacja przetwarzania pliku
194 const { fileName, processor } = task;
195
196 switch (processor) {
197 case 'image-compression':
198 return await this.compressImage(task.fileData);
199 case 'video-transcoding':
200 return await this.transcodeVideo(task.fileData);
201 default:
202 throw new Error(`Unknown processor: ${processor}`);
203 }
204 }
205
206 async storeDataLocally(data) {
207 // Implementacja zapisu w IndexedDB
208 return new Promise((resolve, reject) => {
209 const request = indexedDB.open('AppDatabase', 1);
210
211 request.onsuccess = (event) => {
212 const db = event.target.result;
213 const transaction = db.transaction(['syncedData'], 'readwrite');
214 const store = transaction.objectStore('syncedData');
215
216 store.put({ id: 'latest', data: data, timestamp: Date.now() });
217
218 transaction.oncomplete = () => resolve();
219 transaction.onerror = () => reject(transaction.error);
220 };
221
222 request.onerror = () => reject(request.error);
223 });
224 }
225}
226
227new BackgroundTaskProcessor();1// WorkerPerformanceMonitor.js
2class WorkerPerformanceMonitor {
3 constructor() {
4 this.metrics = {
5 taskExecutionTimes: [],
6 memoryUsage: [],
7 errorRates: {},
8 throughput: []
9 };
10
11 this.startTime = performance.now();
12 }
13
14 // Monitorowanie wydajności worker'a
15 monitorWorkerPerformance(worker, taskId) {
16 const startTime = performance.now();
17 let memoryBefore = null;
18
19 // Pomiar pamięci (jeśli dostępny)
20 if (performance.memory) {
21 memoryBefore = performance.memory.usedJSHeapSize;
22 }
23
24 const originalPostMessage = worker.postMessage.bind(worker);
25 const originalOnMessage = worker.onmessage;
26
27 // Intercept postMessage
28 worker.postMessage = (message) => {
29 console.log(`[Worker] Sending task ${taskId}:`, message);
30 originalPostMessage(message);
31 };
32
33 // Intercept onmessage
34 worker.onmessage = (event) => {
35 const endTime = performance.now();
36 const executionTime = endTime - startTime;
37
38 // Zapisanie metryki czasu wykonania
39 this.metrics.taskExecutionTimes.push({
40 taskId: taskId,
41 executionTime: executionTime,
42 timestamp: Date.now()
43 });
44
45 // Pomiar pamięci po wykonaniu
46 if (performance.memory && memoryBefore) {
47 const memoryAfter = performance.memory.usedJSHeapSize;
48 const memoryDelta = memoryAfter - memoryBefore;
49
50 this.metrics.memoryUsage.push({
51 taskId: taskId,
52 memoryDelta: memoryDelta,
53 timestamp: Date.now()
54 });
55 }
56
57 console.log(`[Worker] Task ${taskId} completed in ${executionTime.toFixed(2)}ms`);
58
59 if (originalOnMessage) {
60 originalOnMessage(event);
61 }
62 };
63
64 return worker;
65 }
66
67 // Analiza wydajności
68 generatePerformanceReport() {
69 const report = {
70 totalRuntime: performance.now() - this.startTime,
71 taskCount: this.metrics.taskExecutionTimes.length,
72 averageExecutionTime: this.calculateAverageExecutionTime(),
73 memoryTrends: this.analyzeMemoryTrends(),
74 throughputAnalysis: this.calculateThroughput(),
75 recommendations: this.generateRecommendations()
76 };
77
78 return report;
79 }
80
81 calculateAverageExecutionTime() {
82 if (this.metrics.taskExecutionTimes.length === 0) return 0;
83
84 const total = this.metrics.taskExecutionTimes.reduce(
85 (sum, metric) => sum + metric.executionTime, 0
86 );
87
88 return total / this.metrics.taskExecutionTimes.length;
89 }
90
91 analyzeMemoryTrends() {
92 if (this.metrics.memoryUsage.length === 0) return null;
93
94 const memoryDeltas = this.metrics.memoryUsage.map(m => m.memoryDelta);
95 const averageMemoryDelta = memoryDeltas.reduce((a, b) => a + b, 0) / memoryDeltas.length;
96
97 return {
98 averageMemoryDelta: averageMemoryDelta,
99 maxMemoryDelta: Math.max(...memoryDeltas),
100 minMemoryDelta: Math.min(...memoryDeltas),
101 potentialMemoryLeak: averageMemoryDelta > 1024 * 1024 // > 1MB average
102 };
103 }
104
105 calculateThroughput() {
106 const timeWindow = 60000; // 1 minuta
107 const now = Date.now();
108 const recentTasks = this.metrics.taskExecutionTimes.filter(
109 task => now - task.timestamp < timeWindow
110 );
111
112 return {
113 tasksPerMinute: recentTasks.length,
114 averageTaskTime: recentTasks.length > 0
115 ? recentTasks.reduce((sum, task) => sum + task.executionTime, 0) / recentTasks.length
116 : 0
117 };
118 }
119
120 generateRecommendations() {
121 const recommendations = [];
122 const avgTime = this.calculateAverageExecutionTime();
123 const memoryTrends = this.analyzeMemoryTrends();
124
125 if (avgTime > 5000) { // > 5 sekund
126 recommendations.push('Rozważ podział długotrwałych zadań na mniejsze części');
127 }
128
129 if (memoryTrends?.potentialMemoryLeak) {
130 recommendations.push('Wykryto potencjalny memory leak - sprawdź zarządzanie pamięcią');
131 }
132
133 if (this.metrics.taskExecutionTimes.length > 1000) {
134 recommendations.push('Duża liczba zadań - rozważ implementację Worker Pool');
135 }
136
137 return recommendations;
138 }
139}
140
141// Użycie monitoring
142const performanceMonitor = new WorkerPerformanceMonitor();
143
144// Monitorowanie konkretnego worker'a
145const worker = new Worker('calculation-worker.js');
146const monitoredWorker = performanceMonitor.monitorWorkerPerformance(worker, 'task_001');
147
148// Generowanie raportu po pewnym czasie
149setTimeout(() => {
150 const report = performanceMonitor.generatePerformanceReport();
151 console.log('Performance Report:', report);
152}, 30000);1// OptimizedWorkerCommunication.js
2class OptimizedWorkerCommunication {
3 constructor() {
4 this.messageQueue = [];
5 this.batchSize = 10;
6 this.batchTimeout = 16; // ~60fps
7 this.compressionThreshold = 1024; // 1KB
8 }
9
10 // Batching wiadomości dla lepszej wydajności
11 sendBatchedMessage(worker, message) {
12 this.messageQueue.push({ worker, message });
13
14 if (this.messageQueue.length >= this.batchSize) {
15 this.flushMessageQueue();
16 } else {
17 // Flush po timeout jeśli batch nie jest pełny
18 setTimeout(() => {
19 if (this.messageQueue.length > 0) {
20 this.flushMessageQueue();
21 }
22 }, this.batchTimeout);
23 }
24 }
25
26 flushMessageQueue() {
27 const workerMessages = new Map();
28
29 // Grupowanie wiadomości po worker'ach
30 this.messageQueue.forEach(({ worker, message }) => {
31 if (!workerMessages.has(worker)) {
32 workerMessages.set(worker, []);
33 }
34 workerMessages.get(worker).push(message);
35 });
36
37 // Wysyłanie zbatchowanych wiadomości
38 workerMessages.forEach((messages, worker) => {
39 worker.postMessage({
40 type: 'BATCH_MESSAGES',
41 messages: messages,
42 timestamp: Date.now()
43 });
44 });
45
46 this.messageQueue = [];
47 }
48
49 // Kompresja dużych danych przed wysłaniem
50 async sendCompressedData(worker, data) {
51 const serializedData = JSON.stringify(data);
52
53 if (serializedData.length > this.compressionThreshold) {
54 try {
55 // Używanie Compression Streams API (jeśli dostępne)
56 const compressedData = await this.compressData(serializedData);
57
58 worker.postMessage({
59 type: 'COMPRESSED_DATA',
60 data: compressedData,
61 compressed: true,
62 originalSize: serializedData.length
63 });
64 } catch (error) {
65 // Fallback do niezkompresowanych danych
66 worker.postMessage({
67 type: 'RAW_DATA',
68 data: data,
69 compressed: false
70 });
71 }
72 } else {
73 worker.postMessage({
74 type: 'RAW_DATA',
75 data: data,
76 compressed: false
77 });
78 }
79 }
80
81 async compressData(data) {
82 // Implementacja kompresji (przykład z Compression Streams)
83 if ('CompressionStream' in window) {
84 const stream = new CompressionStream('gzip');
85 const writer = stream.writable.getWriter();
86 const reader = stream.readable.getReader();
87
88 writer.write(new TextEncoder().encode(data));
89 writer.close();
90
91 const chunks = [];
92 let done = false;
93
94 while (!done) {
95 const { value, done: readerDone } = await reader.read();
96 done = readerDone;
97 if (value) {
98 chunks.push(value);
99 }
100 }
101
102 return new Uint8Array(chunks.reduce((acc, chunk) => [...acc, ...chunk], []));
103 }
104
105 // Fallback - zwrócenie oryginalnych danych
106 return new TextEncoder().encode(data);
107 }
108
109 // Transferable Objects dla efektywnego transferu
110 sendTransferableData(worker, arrayBuffer) {
111 worker.postMessage({
112 type: 'TRANSFERABLE_DATA',
113 data: arrayBuffer
114 }, [arrayBuffer]); // Transfer ownership
115
116 console.log('Transferred ArrayBuffer to worker (zero-copy)');
117 }
118}1// ResilientWorkerManager.js
2class ResilientWorkerManager {
3 constructor(workerScript, options = {}) {
4 this.workerScript = workerScript;
5 this.options = {
6 maxRetries: 3,
7 retryDelay: 1000,
8 healthCheckInterval: 30000,
9 taskTimeout: 60000,
10 ...options
11 };
12
13 this.workers = new Map();
14 this.failedTasks = new Map();
15 this.healthCheckTimer = null;
16
17 this.startHealthChecking();
18 }
19
20 // Tworzenie worker'a z error handling
21 createResilientWorker(workerId) {
22 const worker = new Worker(this.workerScript);
23 const workerInfo = {
24 instance: worker,
25 id: workerId,
26 healthy: true,
27 taskCount: 0,
28 errorCount: 0,
29 lastActivity: Date.now(),
30 activeTasks: new Map()
31 };
32
33 // Error handling
34 worker.onerror = (error) => {
35 this.handleWorkerError(workerInfo, error);
36 };
37
38 // Message handling z timeout
39 worker.onmessage = (event) => {
40 this.handleWorkerMessage(workerInfo, event.data);
41 };
42
43 this.workers.set(workerId, workerInfo);
44 return workerInfo;
45 }
46
47 // Wykonanie zadania z retry logic
48 async executeTaskWithRetry(taskData, workerId = null) {
49 let attempts = 0;
50 let lastError = null;
51
52 while (attempts < this.options.maxRetries) {
53 try {
54 const result = await this.executeTask(taskData, workerId);
55
56 // Reset failed task counter po sukcesie
57 if (this.failedTasks.has(taskData.id)) {
58 this.failedTasks.delete(taskData.id);
59 }
60
61 return result;
62 } catch (error) {
63 attempts++;
64 lastError = error;
65
66 console.warn(`Task ${taskData.id} failed (attempt ${attempts}/${this.options.maxRetries}):`, error);
67
68 if (attempts < this.options.maxRetries) {
69 // Exponential backoff
70 const delay = this.options.retryDelay * Math.pow(2, attempts - 1);
71 await this.sleep(delay);
72
73 // Recreate worker jeśli potrzeba
74 if (error.type === 'WORKER_DIED') {
75 await this.recreateWorker(workerId);
76 }
77 }
78 }
79 }
80
81 // Zapisanie zadania jako failed
82 this.failedTasks.set(taskData.id, {
83 taskData: taskData,
84 attempts: attempts,
85 lastError: lastError,
86 failedAt: Date.now()
87 });
88
89 throw new Error(`Task ${taskData.id} failed after ${attempts} attempts: ${lastError.message}`);
90 }
91
92 executeTask(taskData, workerId) {
93 return new Promise((resolve, reject) => {
94 const worker = workerId ? this.workers.get(workerId) : this.getHealthyWorker();
95
96 if (!worker || !worker.healthy) {
97 reject(new Error('No healthy worker available'));
98 return;
99 }
100
101 const taskId = taskData.id || this.generateTaskId();
102 const timeoutId = setTimeout(() => {
103 worker.activeTasks.delete(taskId);
104 reject(new Error(`Task ${taskId} timed out`));
105 }, this.options.taskTimeout);
106
107 // Zapisanie zadania jako aktywne
108 worker.activeTasks.set(taskId, {
109 resolve: resolve,
110 reject: reject,
111 timeoutId: timeoutId,
112 startTime: Date.now()
113 });
114
115 worker.taskCount++;
116 worker.lastActivity = Date.now();
117
118 worker.instance.postMessage({
119 ...taskData,
120 taskId: taskId
121 });
122 });
123 }
124
125 handleWorkerMessage(workerInfo, message) {
126 const { taskId, type, result, error } = message;
127
128 if (workerInfo.activeTasks.has(taskId)) {
129 const task = workerInfo.activeTasks.get(taskId);
130
131 clearTimeout(task.timeoutId);
132 workerInfo.activeTasks.delete(taskId);
133 workerInfo.lastActivity = Date.now();
134
135 if (type === 'TASK_SUCCESS') {
136 task.resolve(result);
137 } else if (type === 'TASK_ERROR') {
138 workerInfo.errorCount++;
139 task.reject(new Error(error));
140 }
141 }
142 }
143
144 handleWorkerError(workerInfo, error) {
145 console.error(`Worker ${workerInfo.id} error:`, error);
146
147 workerInfo.healthy = false;
148 workerInfo.errorCount++;
149
150 // Odrzucenie wszystkich aktywnych zadań
151 workerInfo.activeTasks.forEach(task => {
152 clearTimeout(task.timeoutId);
153 task.reject(new Error('Worker died'));
154 });
155
156 workerInfo.activeTasks.clear();
157
158 // Auto-restart worker
159 setTimeout(() => {
160 this.recreateWorker(workerInfo.id);
161 }, this.options.retryDelay);
162 }
163
164 async recreateWorker(workerId) {
165 const oldWorker = this.workers.get(workerId);
166
167 if (oldWorker) {
168 oldWorker.instance.terminate();
169 this.workers.delete(workerId);
170 }
171
172 console.log(`Recreating worker ${workerId}`);
173 const newWorker = this.createResilientWorker(workerId);
174
175 // Health check dla nowego worker'a
176 await this.waitForWorkerReady(newWorker);
177 }
178
179 waitForWorkerReady(workerInfo, timeout = 5000) {
180 return new Promise((resolve, reject) => {
181 const timeoutId = setTimeout(() => {
182 reject(new Error(`Worker ${workerInfo.id} failed to initialize`));
183 }, timeout);
184
185 // Ping test
186 const pingTest = () => {
187 workerInfo.instance.postMessage({
188 type: 'PING',
189 timestamp: Date.now()
190 });
191 };
192
193 const handlePong = (event) => {
194 if (event.data.type === 'PONG') {
195 clearTimeout(timeoutId);
196 workerInfo.instance.removeEventListener('message', handlePong);
197 workerInfo.healthy = true;
198 resolve(workerInfo);
199 }
200 };
201
202 workerInfo.instance.addEventListener('message', handlePong);
203 pingTest();
204 });
205 }
206
207 // Okresowe sprawdzanie zdrowia worker'ów
208 startHealthChecking() {
209 this.healthCheckTimer = setInterval(() => {
210 this.performHealthCheck();
211 }, this.options.healthCheckInterval);
212 }
213
214 performHealthCheck() {
215 this.workers.forEach((workerInfo, workerId) => {
216 const timeSinceLastActivity = Date.now() - workerInfo.lastActivity;
217
218 // Sprawdzenie czy worker jest responsywny
219 if (timeSinceLastActivity > this.options.healthCheckInterval * 2) {
220 console.warn(`Worker ${workerId} appears unresponsive`);
221 this.recreateWorker(workerId);
222 }
223
224 // Sprawdzenie error rate
225 const errorRate = workerInfo.errorCount / Math.max(workerInfo.taskCount, 1);
226 if (errorRate > 0.1) { // > 10% error rate
227 console.warn(`Worker ${workerId} has high error rate: ${(errorRate * 100).toFixed(2)}%`);
228 }
229 });
230 }
231
232 getHealthyWorker() {
233 const healthyWorkers = Array.from(this.workers.values()).filter(w => w.healthy);
234
235 if (healthyWorkers.length === 0) {
236 return null;
237 }
238
239 // Wybór worker'a z najmniejszą liczbą aktywnych zadań
240 return healthyWorkers.reduce((best, current) =>
241 current.activeTasks.size < best.activeTasks.size ? current : best
242 );
243 }
244
245 sleep(ms) {
246 return new Promise(resolve => setTimeout(resolve, ms));
247 }
248
249 generateTaskId() {
250 return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
251 }
252
253 // Czyszczenie zasobów
254 terminate() {
255 if (this.healthCheckTimer) {
256 clearInterval(this.healthCheckTimer);
257 }
258
259 this.workers.forEach(workerInfo => {
260 workerInfo.instance.terminate();
261 });
262
263 this.workers.clear();
264 }
265
266 // Statystyki systemu
267 getSystemStats() {
268 const workers = Array.from(this.workers.values());
269
270 return {
271 totalWorkers: workers.length,
272 healthyWorkers: workers.filter(w => w.healthy).length,
273 totalTasks: workers.reduce((sum, w) => sum + w.taskCount, 0),
274 activeTasks: workers.reduce((sum, w) => sum + w.activeTasks.size, 0),
275 totalErrors: workers.reduce((sum, w) => sum + w.errorCount, 0),
276 failedTasksCount: this.failedTasks.size,
277 averageErrorRate: workers.length > 0
278 ? (workers.reduce((sum, w) => sum + (w.errorCount / Math.max(w.taskCount, 1)), 0) / workers.length * 100).toFixed(2) + '%'
279 : '0%'
280 };
281 }
282}1// 3DRenderingApp.js
2class WebWorker3DRenderer {
3 constructor(canvasId) {
4 this.canvas = document.getElementById(canvasId);
5 this.ctx = this.canvas.getContext('2d');
6 this.workerPool = new WorkerPool('3d-renderer-worker.js', 4);
7
8 this.scene = {
9 objects: [],
10 camera: { x: 0, y: 0, z: -10 },
11 lights: [{ x: 5, y: 5, z: -5, intensity: 1 }]
12 };
13
14 this.setupScene();
15 }
16
17 setupScene() {
18 // Dodanie obiektów 3D do sceny
19 this.scene.objects = [
20 { type: 'cube', position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 } },
21 { type: 'sphere', position: { x: 3, y: 0, z: 2 }, radius: 1 },
22 { type: 'cylinder', position: { x: -3, y: 0, z: 1 }, height: 2, radius: 0.5 }
23 ];
24 }
25
26 async render() {
27 const startTime = performance.now();
28
29 // Podział ekranu na tiles dla równoległego renderowania
30 const tileSize = 64;
31 const tilesX = Math.ceil(this.canvas.width / tileSize);
32 const tilesY = Math.ceil(this.canvas.height / tileSize);
33
34 const renderPromises = [];
35
36 for (let tileY = 0; tileY < tilesY; tileY++) {
37 for (let tileX = 0; tileX < tilesX; tileX++) {
38 const tileData = {
39 type: 'RENDER_TILE',
40 tile: {
41 x: tileX * tileSize,
42 y: tileY * tileSize,
43 width: Math.min(tileSize, this.canvas.width - tileX * tileSize),
44 height: Math.min(tileSize, this.canvas.height - tileY * tileSize)
45 },
46 scene: this.scene,
47 screenDimensions: {
48 width: this.canvas.width,
49 height: this.canvas.height
50 }
51 };
52
53 renderPromises.push(
54 this.workerPool.execute(tileData).then(result => ({
55 ...result,
56 tileX: tileX,
57 tileY: tileY
58 }))
59 );
60 }
61 }
62
63 // Oczekiwanie na wszystkie tiles
64 const renderedTiles = await Promise.all(renderPromises);
65
66 // Składanie tiles na canvas
67 renderedTiles.forEach(tile => {
68 const imageData = new ImageData(
69 new Uint8ClampedArray(tile.pixelData),
70 tile.width,
71 tile.height
72 );
73
74 this.ctx.putImageData(imageData, tile.x, tile.y);
75 });
76
77 const renderTime = performance.now() - startTime;
78 console.log(`Frame rendered in ${renderTime.toFixed(2)}ms`);
79 }
80
81 // Animacja sceny
82 animate() {
83 this.scene.objects.forEach(obj => {
84 if (obj.rotation) {
85 obj.rotation.y += 0.02;
86 obj.rotation.x += 0.01;
87 }
88 });
89
90 this.render().then(() => {
91 requestAnimationFrame(() => this.animate());
92 });
93 }
94}
95
96// 3d-renderer-worker.js
97class Tile3DRenderer {
98 constructor() {
99 self.onmessage = (event) => {
100 this.handleRenderTask(event.data);
101 };
102 }
103
104 handleRenderTask(data) {
105 if (data.type === 'RENDER_TILE') {
106 const result = this.renderTile(data.tile, data.scene, data.screenDimensions);
107
108 self.postMessage({
109 type: 'TASK_COMPLETE',
110 result: result
111 });
112 }
113 }
114
115 renderTile(tile, scene, screenDimensions) {
116 const { x, y, width, height } = tile;
117 const pixelData = new Uint8ClampedArray(width * height * 4);
118
119 for (let py = 0; py < height; py++) {
120 for (let px = 0; px < width; px++) {
121 const screenX = x + px;
122 const screenY = y + py;
123
124 // Przekształcenie współrzędnych ekranu na ray
125 const ray = this.screenToRay(screenX, screenY, screenDimensions, scene.camera);
126
127 // Ray tracing
128 const color = this.traceRay(ray, scene);
129
130 const pixelIndex = (py * width + px) * 4;
131 pixelData[pixelIndex] = color.r; // Red
132 pixelData[pixelIndex + 1] = color.g; // Green
133 pixelData[pixelIndex + 2] = color.b; // Blue
134 pixelData[pixelIndex + 3] = 255; // Alpha
135 }
136 }
137
138 return {
139 x: x,
140 y: y,
141 width: width,
142 height: height,
143 pixelData: Array.from(pixelData)
144 };
145 }
146
147 screenToRay(screenX, screenY, screenDimensions, camera) {
148 const normalizedX = (screenX / screenDimensions.width) * 2 - 1;
149 const normalizedY = 1 - (screenY / screenDimensions.height) * 2;
150
151 return {
152 origin: camera,
153 direction: {
154 x: normalizedX,
155 y: normalizedY,
156 z: 1
157 }
158 };
159 }
160
161 traceRay(ray, scene) {
162 let closestDistance = Infinity;
163 let hitColor = { r: 0, g: 0, b: 50 }; // Tło
164
165 // Sprawdzanie kolizji z obiektami
166 scene.objects.forEach(obj => {
167 const distance = this.rayObjectIntersection(ray, obj);
168
169 if (distance > 0 && distance < closestDistance) {
170 closestDistance = distance;
171 hitColor = this.calculateObjectColor(obj, ray, distance, scene.lights);
172 }
173 });
174
175 return hitColor;
176 }
177
178 rayObjectIntersection(ray, obj) {
179 switch (obj.type) {
180 case 'sphere':
181 return this.raySphereIntersection(ray, obj);
182 case 'cube':
183 return this.rayCubeIntersection(ray, obj);
184 default:
185 return -1;
186 }
187 }
188
189 raySphereIntersection(ray, sphere) {
190 const dx = ray.origin.x - sphere.position.x;
191 const dy = ray.origin.y - sphere.position.y;
192 const dz = ray.origin.z - sphere.position.z;
193
194 const a = ray.direction.x * ray.direction.x +
195 ray.direction.y * ray.direction.y +
196 ray.direction.z * ray.direction.z;
197
198 const b = 2 * (dx * ray.direction.x + dy * ray.direction.y + dz * ray.direction.z);
199 const c = dx * dx + dy * dy + dz * dz - sphere.radius * sphere.radius;
200
201 const discriminant = b * b - 4 * a * c;
202
203 if (discriminant < 0) return -1;
204
205 const t1 = (-b - Math.sqrt(discriminant)) / (2 * a);
206 const t2 = (-b + Math.sqrt(discriminant)) / (2 * a);
207
208 return t1 > 0 ? t1 : (t2 > 0 ? t2 : -1);
209 }
210
211 calculateObjectColor(obj, ray, distance, lights) {
212 // Uproszczone oświetlenie
213 const baseColor = obj.type === 'sphere'
214 ? { r: 255, g: 100, b: 100 }
215 : { r: 100, g: 255, b: 100 };
216
217 // Symulacja oświetlenia
218 const lightIntensity = 0.7 + 0.3 * Math.sin(distance * 0.1);
219
220 return {
221 r: Math.floor(baseColor.r * lightIntensity),
222 g: Math.floor(baseColor.g * lightIntensity),
223 b: Math.floor(baseColor.b * lightIntensity)
224 };
225 }
226}
227
228new Tile3DRenderer();Web Workers umożliwiają:
Web Workers to potężne narzędzie do tworzenia wydajnych, responsywnych aplikacji webowych, które mogą konkurować z aplikacjami natywnymi pod względem płynności działania.