canvasframework 0.5.48 → 0.5.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/CanvasFramework.js +252 -90
- package/package.json +1 -1
package/core/CanvasFramework.js
CHANGED
|
@@ -149,6 +149,213 @@ const FIXED_COMPONENT_TYPES = new Set([
|
|
|
149
149
|
* @property {number} scrollVelocity - Vitesse de défilement
|
|
150
150
|
* @property {number} scrollFriction - Friction du défilement
|
|
151
151
|
*/
|
|
152
|
+
|
|
153
|
+
// ========================================
|
|
154
|
+
// ✨ NOUVEAU: WorkerPool System
|
|
155
|
+
// ========================================
|
|
156
|
+
|
|
157
|
+
class WorkerPool {
|
|
158
|
+
constructor(options = {}) {
|
|
159
|
+
this.maxWorkers = options.maxWorkers || navigator.hardwareConcurrency || 4;
|
|
160
|
+
this.minWorkers = options.minWorkers || 1;
|
|
161
|
+
this.workerScript = options.workerScript || null;
|
|
162
|
+
|
|
163
|
+
this.workers = [];
|
|
164
|
+
this.availableWorkers = [];
|
|
165
|
+
this.busyWorkers = new Set();
|
|
166
|
+
this.taskQueue = [];
|
|
167
|
+
this.taskIdCounter = 0;
|
|
168
|
+
this.pendingTasks = new Map();
|
|
169
|
+
|
|
170
|
+
this._initializeMinWorkers();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
_initializeMinWorkers() {
|
|
174
|
+
for (let i = 0; i < this.minWorkers; i++) {
|
|
175
|
+
this._createWorker();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_createWorker() {
|
|
180
|
+
const blob = new Blob([this._getDefaultWorkerCode()], { type: 'application/javascript' });
|
|
181
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
182
|
+
const worker = new Worker(workerUrl);
|
|
183
|
+
const workerWrapper = this._wrapWorker(worker);
|
|
184
|
+
|
|
185
|
+
this.workers.push(workerWrapper);
|
|
186
|
+
this.availableWorkers.push(workerWrapper);
|
|
187
|
+
URL.revokeObjectURL(workerUrl);
|
|
188
|
+
|
|
189
|
+
return workerWrapper;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
_wrapWorker(worker) {
|
|
193
|
+
const wrapper = {
|
|
194
|
+
worker,
|
|
195
|
+
id: `worker-${Date.now()}-${Math.random()}`,
|
|
196
|
+
currentTask: null,
|
|
197
|
+
lastUsed: Date.now()
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
worker.onmessage = (e) => this._handleWorkerMessage(wrapper, e);
|
|
201
|
+
worker.onerror = (e) => this._handleWorkerError(wrapper, e);
|
|
202
|
+
|
|
203
|
+
return wrapper;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_getDefaultWorkerCode() {
|
|
207
|
+
return `
|
|
208
|
+
let state = {};
|
|
209
|
+
|
|
210
|
+
self.onmessage = async function(e) {
|
|
211
|
+
const { taskId, type, payload } = e.data;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
let result;
|
|
215
|
+
|
|
216
|
+
switch(type) {
|
|
217
|
+
case 'SET_STATE':
|
|
218
|
+
state = payload;
|
|
219
|
+
result = { state };
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case 'EXECUTE':
|
|
223
|
+
const fn = new Function('state', 'args', payload.fnString);
|
|
224
|
+
result = await fn(state, payload.args);
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case 'COMPUTE':
|
|
228
|
+
result = payload.data;
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
default:
|
|
232
|
+
throw new Error('Unknown task type: ' + type);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
self.postMessage({
|
|
236
|
+
taskId,
|
|
237
|
+
type: 'SUCCESS',
|
|
238
|
+
result
|
|
239
|
+
});
|
|
240
|
+
} catch (error) {
|
|
241
|
+
self.postMessage({
|
|
242
|
+
taskId,
|
|
243
|
+
type: 'ERROR',
|
|
244
|
+
error: error.message
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
_handleWorkerMessage(wrapper, e) {
|
|
252
|
+
const { taskId, type, result, error } = e.data;
|
|
253
|
+
const task = this.pendingTasks.get(taskId);
|
|
254
|
+
|
|
255
|
+
if (!task) return;
|
|
256
|
+
|
|
257
|
+
this.pendingTasks.delete(taskId);
|
|
258
|
+
this.busyWorkers.delete(wrapper);
|
|
259
|
+
this.availableWorkers.push(wrapper);
|
|
260
|
+
wrapper.currentTask = null;
|
|
261
|
+
wrapper.lastUsed = Date.now();
|
|
262
|
+
|
|
263
|
+
if (type === 'ERROR') {
|
|
264
|
+
task.reject(new Error(error));
|
|
265
|
+
} else {
|
|
266
|
+
task.resolve(result);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this._processQueue();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_handleWorkerError(wrapper, error) {
|
|
273
|
+
console.error('Worker error:', error);
|
|
274
|
+
|
|
275
|
+
if (wrapper.currentTask) {
|
|
276
|
+
const task = this.pendingTasks.get(wrapper.currentTask);
|
|
277
|
+
if (task) {
|
|
278
|
+
task.reject(error);
|
|
279
|
+
this.pendingTasks.delete(wrapper.currentTask);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.busyWorkers.delete(wrapper);
|
|
284
|
+
const index = this.workers.indexOf(wrapper);
|
|
285
|
+
if (index > -1) {
|
|
286
|
+
this.workers.splice(index, 1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (this.workers.length < this.minWorkers) {
|
|
290
|
+
this._createWorker();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this._processQueue();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
execute(type, payload) {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
const taskId = ++this.taskIdCounter;
|
|
299
|
+
|
|
300
|
+
this.taskQueue.push({
|
|
301
|
+
taskId,
|
|
302
|
+
type,
|
|
303
|
+
payload,
|
|
304
|
+
resolve,
|
|
305
|
+
reject,
|
|
306
|
+
timestamp: Date.now()
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
this._processQueue();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_processQueue() {
|
|
314
|
+
while (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
|
|
315
|
+
const task = this.taskQueue.shift();
|
|
316
|
+
const wrapper = this.availableWorkers.shift();
|
|
317
|
+
|
|
318
|
+
this.busyWorkers.add(wrapper);
|
|
319
|
+
wrapper.currentTask = task.taskId;
|
|
320
|
+
this.pendingTasks.set(task.taskId, task);
|
|
321
|
+
|
|
322
|
+
wrapper.worker.postMessage({
|
|
323
|
+
taskId: task.taskId,
|
|
324
|
+
type: task.type,
|
|
325
|
+
payload: task.payload
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (this.taskQueue.length > 0 &&
|
|
330
|
+
this.availableWorkers.length === 0 &&
|
|
331
|
+
this.workers.length < this.maxWorkers) {
|
|
332
|
+
this._createWorker();
|
|
333
|
+
this._processQueue();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
getStats() {
|
|
338
|
+
return {
|
|
339
|
+
totalWorkers: this.workers.length,
|
|
340
|
+
availableWorkers: this.availableWorkers.length,
|
|
341
|
+
busyWorkers: this.busyWorkers.size,
|
|
342
|
+
queuedTasks: this.taskQueue.length,
|
|
343
|
+
pendingTasks: this.pendingTasks.size
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
terminateAll() {
|
|
348
|
+
this.workers.forEach(wrapper => {
|
|
349
|
+
wrapper.worker.terminate();
|
|
350
|
+
});
|
|
351
|
+
this.workers = [];
|
|
352
|
+
this.availableWorkers = [];
|
|
353
|
+
this.busyWorkers.clear();
|
|
354
|
+
this.taskQueue = [];
|
|
355
|
+
this.pendingTasks.clear();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
152
359
|
class CanvasFramework {
|
|
153
360
|
/**
|
|
154
361
|
* Crée une instance de CanvasFramework
|
|
@@ -316,21 +523,14 @@ class CanvasFramework {
|
|
|
316
523
|
}
|
|
317
524
|
});
|
|
318
525
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// Envoyer l'état initial au worker
|
|
329
|
-
this.logicWorker.postMessage({
|
|
330
|
-
type: 'SET_STATE',
|
|
331
|
-
payload: this.state
|
|
332
|
-
});
|
|
333
|
-
|
|
526
|
+
// WorkerPool pour le logic
|
|
527
|
+
this.workerPool = new WorkerPool({
|
|
528
|
+
maxWorkers: options.maxLogicWorkers || navigator.hardwareConcurrency || 4,
|
|
529
|
+
minWorkers: options.minLogicWorkers || 1
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
this.logicWorkerState = {};
|
|
533
|
+
|
|
334
534
|
// Gestion des événements
|
|
335
535
|
this.isDragging = false;
|
|
336
536
|
this.lastTouchY = 0;
|
|
@@ -1483,37 +1683,7 @@ class CanvasFramework {
|
|
|
1483
1683
|
return new Worker(URL.createObjectURL(blob));
|
|
1484
1684
|
}
|
|
1485
1685
|
|
|
1486
|
-
createLogicWorker() {
|
|
1487
|
-
const workerCode = `
|
|
1488
|
-
let state = {};
|
|
1489
1686
|
|
|
1490
|
-
self.onmessage = async function(e) {
|
|
1491
|
-
const { type, payload } = e.data;
|
|
1492
|
-
|
|
1493
|
-
switch(type) {
|
|
1494
|
-
case 'SET_STATE':
|
|
1495
|
-
state = payload;
|
|
1496
|
-
self.postMessage({ type: 'STATE_UPDATED', payload: state });
|
|
1497
|
-
break;
|
|
1498
|
-
|
|
1499
|
-
case 'EXECUTE':
|
|
1500
|
-
try {
|
|
1501
|
-
const fn = new Function('state', 'args', payload.fnString);
|
|
1502
|
-
const result = await fn(state, payload.args);
|
|
1503
|
-
self.postMessage({ type: 'EXECUTION_RESULT', payload: result });
|
|
1504
|
-
} catch (err) {
|
|
1505
|
-
self.postMessage({ type: 'EXECUTION_ERROR', payload: err.message });
|
|
1506
|
-
}
|
|
1507
|
-
break;
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
`;
|
|
1511
|
-
|
|
1512
|
-
const blob = new Blob([workerCode], {
|
|
1513
|
-
type: 'application/javascript'
|
|
1514
|
-
});
|
|
1515
|
-
return new Worker(URL.createObjectURL(blob));
|
|
1516
|
-
}
|
|
1517
1687
|
|
|
1518
1688
|
// Set Theme dynamique
|
|
1519
1689
|
setTheme(theme) {
|
|
@@ -1588,49 +1758,39 @@ class CanvasFramework {
|
|
|
1588
1758
|
});
|
|
1589
1759
|
}
|
|
1590
1760
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
case 'EXECUTION_RESULT':
|
|
1604
|
-
// Résultat d'une tâche spécifique envoyée au worker
|
|
1605
|
-
if (this.onWorkerResult) this.onWorkerResult(payload);
|
|
1606
|
-
break;
|
|
1607
|
-
|
|
1608
|
-
case 'EXECUTION_ERROR':
|
|
1609
|
-
console.error('Logic Worker Error:', payload);
|
|
1610
|
-
break;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Exécute une tâche dans le pool de workers
|
|
1763
|
+
* @param {string} fnString - Code de la fonction à exécuter
|
|
1764
|
+
* @param {*} args - Arguments pour la fonction
|
|
1765
|
+
* @returns {Promise} Résultat de l'exécution
|
|
1766
|
+
*/
|
|
1767
|
+
async executeTask(fnString, args = {}) {
|
|
1768
|
+
return this.workerPool.execute('EXECUTE', {
|
|
1769
|
+
fnString,
|
|
1770
|
+
args
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1613
1773
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1774
|
+
/**
|
|
1775
|
+
* Met à jour l'état dans tous les workers
|
|
1776
|
+
* @param {Object} newState - Nouvel état
|
|
1777
|
+
*/
|
|
1778
|
+
async updateWorkerState(newState) {
|
|
1779
|
+
this.logicWorkerState = {
|
|
1780
|
+
...this.logicWorkerState,
|
|
1781
|
+
...newState
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
return this.workerPool.execute('SET_STATE', this.logicWorkerState);
|
|
1785
|
+
}
|
|
1623
1786
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
payload: this.logicWorkerState
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Obtient les statistiques du pool de workers
|
|
1789
|
+
* @returns {Object} Statistiques
|
|
1790
|
+
*/
|
|
1791
|
+
getWorkerPoolStats() {
|
|
1792
|
+
return this.workerPool.getStats();
|
|
1793
|
+
}
|
|
1634
1794
|
|
|
1635
1795
|
detectPlatform() {
|
|
1636
1796
|
const ua = navigator.userAgent.toLowerCase();
|
|
@@ -2985,12 +3145,14 @@ class CanvasFramework {
|
|
|
2985
3145
|
if (this.scrollWorker) {
|
|
2986
3146
|
this.scrollWorker.terminate();
|
|
2987
3147
|
}
|
|
2988
|
-
|
|
3148
|
+
|
|
3149
|
+
if (this.worker) {
|
|
2989
3150
|
this.worker.terminate();
|
|
2990
3151
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3152
|
+
|
|
3153
|
+
if (this.workerPool) {
|
|
3154
|
+
this.workerPool.terminateAll();
|
|
3155
|
+
}
|
|
2994
3156
|
|
|
2995
3157
|
if (this.ctx && typeof this.ctx.destroy === 'function') {
|
|
2996
3158
|
this.ctx.destroy();
|