1 //** Start JS Lint Settings ** 2 /*globals self,Worker,Blob,rtn*/ 3 /*jslint vars:true, devel:true, browser:true, evil:true*/ 4 //** End JS Lint Settings ** 5 6 /* 7 * Title: WebHamsters 8 * Description: Javascript library to add multi-threading support to javascript by exploiting concurrent web workers 9 * Author: Austin K. Smith 10 * Contact: austin@asmithdev.com 11 * Copyright: 2015 Austin K. Smith - austin@asmithdev.com 12 * License: Artistic License 2.0 13 */ 14 var hamsters = { 15 version: '2.2', 16 debug: false, 17 cache: false, 18 maxThreads: Math.ceil((navigator.hardwareConcurrency || 1) * 1.25), 19 tools: {}, 20 runtime: { 21 cache: null, 22 legacy: false, 23 queue: { 24 running: [], 25 pending: [] 26 }, 27 tasks: [], 28 errors: [], 29 setup: {} 30 } 31 }; 32 33 /** 34 * @description: Initializes and sets up library functionality 35 * @method wakeUp 36 * @return 37 */ 38 hamsters.runtime.wakeUp = function() { 39 "use strict"; 40 41 /** 42 * @description: Detect Internet Explorer by Version IE10 and below 43 * @method isIE 44 * @param {integer} version 45 * @return CallExpression 46 */ 47 hamsters.tools.isIE = function(version) { 48 return (new RegExp('msie' + (!isNaN(version) ? ('\\s'+version) : ''), 'i').test(navigator.userAgent)); 49 }; 50 51 /** 52 * Description 53 * @description: Detect browser support for web workers 54 * @method isLegacy 55 * @return 56 */ 57 hamsters.runtime.setup.isLegacy = function() { 58 if(!window.Worker || navigator.userAgent.indexOf('Kindle/3.0') !== -1 || navigator.userAgent.indexOf('Mobile/8F190') !== -1 || navigator.userAgent.indexOf('IEMobile') !== -1 || hamsters.tools.isIE(10)) { 59 hamsters.runtime.legacy = true; 60 } else if(navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) { 61 window.firefox = window.firefox || true; 62 } 63 }; 64 65 /** 66 * @description: Return browser support for library 67 * @method isLegacy 68 * @return MemberExpression 69 */ 70 hamsters.isLegacy = function() { 71 return hamsters.runtime.legacy; 72 }; 73 74 /** 75 * @description: Method for checking runtime error log 76 * @method checkErrors 77 * @return ObjectExpression 78 */ 79 hamsters.checkErrors = function() { 80 var errors = hamsters.runtime.errors || []; 81 return { 82 'msg': 'There are currently ' + errors.length + ' errors captured in the runtime', 83 'total': errors.length, 84 'errors': errors 85 }; 86 }; 87 88 /** 89 * @description: Splits an array into equal sized subarrays for individual workers 90 * @constructor 91 * @method splitArray 92 * @param {array} array - incoming array to be split 93 * @param {integer} n - total subarrays 94 * @return ArrayExpression 95 */ 96 hamsters.tools.splitArray = function(array, n) { 97 if(array.length && !array.slice) { 98 array = hamsters.runtime.normalizeArray(array); 99 } 100 var tasks = []; 101 var i = 0; 102 if(array) { 103 var len = array.length; 104 var size = Math.ceil((len/n)); 105 while (i < len) { 106 tasks.push(array.slice(i, i += size)); 107 } 108 return tasks; 109 } 110 return []; 111 }; 112 113 /** 114 * @description: Generates a worker which generates an array of random numbers for testing 115 * @constructor 116 * @function randomArray 117 * @method randomArray 118 * @param {integer} count - array size 119 * @param {function} callback - callback when array ready 120 * @return 121 */ 122 hamsters.tools.randomArray = function(count, callback) { 123 if(!count || !callback) { 124 hamsters.runtime.errors = hamsters.runtime.errors.concat({ 125 'msg': 'Unable to generate random array, missing required params' 126 }); 127 return; 128 } 129 var params = { 130 'count': count 131 }; 132 hamsters.run(params, function() { 133 var total = params.count; 134 var i = 0; 135 while(i < total) { 136 rtn.data[rtn.data.length] = Math.round(Math.random() * (100 - 1) + 1); 137 i++; 138 } 139 }, function(output) { 140 if(callback) { 141 callback(output); 142 } 143 }, 1, false, 'Int32'); 144 }; 145 146 /** 147 * Description 148 * @method cacheResult 149 * @param {string} fn 150 * @param {array} input 151 * @param {array} output 152 * @param {string} dataType 153 * @return 154 */ 155 hamsters.runtime.cacheResult = function(fn, input, output, dataType) { 156 if(hamsters.runtime.checkCache(fn, input, dataType)) { 157 return; 158 } 159 try { 160 sessionStorage.setItem(sessionStorage.length, JSON.stringify({'func': fn, 'input': input, 'output': output, 'dataType': dataType})); 161 } catch(eve) { 162 if(eve.name === 'QuotaExceededError') { 163 sessionStorage.clear(); 164 try { 165 sessionStorage.setItem(sessionStorage.length, JSON.stringify({'func': fn, 'input': input, 'output': output, 'dataType': dataType})); 166 } catch(e) { //Do nothing, can't cache this result..too large 167 return; 168 } 169 } 170 } 171 }; 172 173 /** 174 * Description 175 * @method compareArrays 176 * @param {array} array1 177 * @param {array} array2 178 * @return CallExpression 179 */ 180 hamsters.runtime.compareArrays = function (array1, array2) { 181 if (array1.length !== array2.length) { 182 return false; 183 } 184 return array1.every(function (el, i) { 185 return (el === array2[i]); 186 }); 187 }; 188 189 /** 190 * Description 191 * @method checkCache 192 * @param {string} fn 193 * @param {array} input 194 * @param {string} dataType 195 * @return 196 */ 197 hamsters.runtime.checkCache = function(fn, input, dataType) { 198 var item; 199 for (var i = 0, len = sessionStorage.length; i < len; i++) { 200 item = JSON.parse(sessionStorage[i]); 201 var equals = hamsters.runtime.compareArrays(item.input, input); 202 if(item && item.func === fn && equals && !item.dataType && !dataType) { 203 return item.output; 204 } else if(item && item.func === fn && equals && item.dataType === dataType) { 205 return hamsters.runtime.processDataType(item.dataType, item.output); 206 } 207 } 208 }; 209 210 /** 211 * @description: Setups dom objects for web worker use with library boilerplate 212 * @constructor 213 * @function populateElements 214 * @method populateElements 215 * @param {integer} count 216 * @param {function} callback - optional callback once thread boilerplates setup 217 * @return 218 */ 219 hamsters.runtime.setup.populateElements = function(count, callback) { 220 for (var i = 0, len = count; i < len; i++) { 221 hamsters.runtime.setup.getOrCreateElement(i); 222 } 223 if(callback) { 224 callback.call(); 225 } 226 }; 227 228 /** 229 * @description: Setups dom objects for web worker use with library boilerplate 230 * @constructor 231 * @method getOrCreateElement 232 * @param {integer} id - thread # to populate 233 * @return script 234 */ 235 hamsters.runtime.setup.getOrCreateElement = function(id) { 236 var script = (document.getElementById('hamster'+id) || null); 237 if(!script) { 238 var work = hamsters.runtime.giveHamsterWork(); 239 script = document.createElement('script'); 240 script.type = ('javascript/worker'); 241 script.id = ('hamster'+id); 242 script.text = '(' + work.toString() + '());'; 243 document.getElementsByTagName('head')[0].appendChild(script); 244 return script; 245 } 246 return script; 247 }; 248 249 /** 250 * @description: Creates boiler plate logic for worker thread 251 * @constructor 252 * @method giveHamsterWork 253 * @return work 254 */ 255 hamsters.runtime.giveHamsterWork = function() { 256 /** 257 * Description 258 * @method work 259 * @return 260 */ 261 var work = function() { 262 var params; 263 264 /** 265 * Description 266 * @method respond 267 * @param {object} rtn 268 * @param {string} msg 269 * @return 270 */ 271 var respond = function(rtn, msg) { 272 if(params.dataType) { 273 var output = processDataType(params.dataType, rtn.data); 274 rtn.data = output.buffer; 275 rtn.dataType = params.dataType; 276 self.postMessage({ 277 'results': rtn || null, 278 'msg': msg || '' 279 }, [output.buffer]); 280 } else { 281 self.postMessage({ 282 'results': rtn || null, 283 'msg': msg || '' 284 }); 285 } 286 }; 287 288 /** 289 * Description 290 * @method processDataType 291 * @param {string} dataType 292 * @param {array} buffer 293 * @return arr 294 */ 295 var processDataType = function(dataType, buffer) { 296 if(!dataType) { 297 return buffer; 298 } 299 var arr; 300 switch(dataType.toLowerCase()) { 301 case 'uint32': 302 arr = new Uint32Array(buffer); 303 break; 304 case 'uint16': 305 arr = new Uint16Array(buffer); 306 break; 307 case 'uint8': 308 arr = new Uint8Array(buffer); 309 break; 310 case 'uint8clamped': 311 arr = new Uint8ClampedArray(buffer); 312 break; 313 case 'int32': 314 arr = new Int32Array(buffer); 315 break; 316 case 'int16': 317 arr = new Int16Array(buffer); 318 break; 319 case 'int8': 320 arr = new Int8Array(buffer); 321 break; 322 case 'float32': 323 arr = new Float32Array(buffer); 324 break; 325 case 'float64': 326 arr = new Float64Array(buffer); 327 break; 328 default: 329 arr = buffer; 330 break; 331 } 332 return arr; 333 }; 334 335 /** 336 * Description 337 * @method onmessage 338 * @param {object} e 339 * @return 340 */ 341 self.onmessage = function(e) { 342 var rtn = { 343 'success': true, 344 'data': [] 345 }; 346 params = e.data; 347 if(typeof params === 'string') { 348 params = JSON.parse(e.data); 349 } 350 if(params.dataType && params.array && typeof params.array === 'object') { 351 params.array = processDataType(params.dataType, params.array); 352 } 353 if(params.fn) { 354 var fn = eval('('+params.fn+')'); 355 if(fn && typeof fn === 'function') { 356 fn(); 357 respond(rtn); 358 } else { 359 rtn.success = false; 360 rtn.error = 'Missing function'; 361 rtn.msg = 'Error encounted check errors for details'; 362 respond(rtn); 363 } 364 } 365 }; 366 }; 367 return work; 368 }; 369 370 /** 371 * @description: Sorts an array of objects based on incoming property param 372 * @constructor 373 * @method sort 374 * @param {string} property - property to sort by 375 * @return FunctionExpression 376 */ 377 hamsters.runtime.sort = function(property) { 378 if(hamsters.debug === 'verbose') { 379 console.info("Sorting array using index: " + property); 380 } 381 var order = 1; 382 if(property[0] === "-") { 383 order = -1; 384 property = property.substr(1); 385 } 386 return function (a,b) { 387 var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0; 388 return result * order; 389 }; 390 }; 391 392 /** 393 * @description: Takes an incoming sequential function and automatically splits the work across as many defined threads for paralell execution 394 * @constructor 395 * @method run 396 * @param {object} params - incoming params object for task 397 * @param {function} fn - Sequential function to execute 398 * @param {function} callback - task callback when all threads complete 399 * @param {integer} workers - total number of threads to use 400 * @param {boolean} aggregate - boolean aggregate individual thread outputs into final array 401 * @param {string} dataType 402 * @return 403 */ 404 hamsters.run = function(params, fn, callback, workers, aggregate, dataType) { 405 if(!params || !fn) { 406 return 'Error processing for loop, missing params or function'; 407 } 408 var taskid = hamsters.runtime.tasks.length; 409 workers = workers || hamsters.maxThreads; 410 hamsters.runtime.tasks.push({ 411 'id': taskid, 412 'workers': [], 413 'count': 0, 414 'threads': workers, 415 'input': params.array || [], 416 'dataType': dataType || null, 417 'fn': fn || null, 418 'output': [], 419 'callback': callback 420 }); 421 var task = hamsters.runtime.tasks[taskid]; 422 callback = (callback || null); 423 var hamsterfood = {'array':[]}; 424 hamsterfood.fn = fn.toString(); 425 if(hamsters.cache && params.array) { 426 var result = hamsters.runtime.checkCache(hamsterfood.fn, task.input, dataType); 427 if(result && callback) { 428 setTimeout(function() { 429 hamsters.runtime.tasks[taskid] = null; //Clean up our task, not needed any longer 430 callback(result); 431 }, 4); 432 return; 433 } 434 } 435 var key; 436 for(key in params) { 437 if(params.hasOwnProperty(key)) { 438 if(key !== 'array') { 439 hamsterfood[key] = params[key]; 440 } 441 } 442 } 443 hamsterfood.dataType = dataType || null; 444 var workArray = params.array || null; 445 if(params.array && task.threads !== 1) { 446 workArray = hamsters.tools.splitArray(params.array, task.threads); //Divide our array into equal array sizes 447 } 448 var i = 0; 449 while(i < task.threads) { 450 if(workArray && task.threads !== 1) { 451 hamsters.runtime.newWheel(workArray[i], hamsterfood, aggregate, callback, taskid, i); 452 } else { 453 hamsters.runtime.newWheel(workArray, hamsterfood, aggregate, callback, taskid, i); 454 } 455 i++; 456 } 457 }; 458 459 /** 460 * @description: Creates new worker thread with body of work to be completed 461 * @constructor 462 * @method newWheel 463 * @param {array} inputArray 464 * @param {object} hamsterfood - incoming params object for worker 465 * @param {boolean} aggregate - boolean aggregate individual thread outputs into final array 466 * @param {function} callback - task callback when all hamsters complete 467 * @param {integer} taskid - global runtime task id 468 * @param {integer} threadid - global runtime threadid 469 * @param {object} hamster - web worker 470 * @param {blob} dataBlob 471 * @return 472 */ 473 hamsters.runtime.newWheel = function(inputArray, hamsterfood, aggregate, callback, taskid, threadid, hamster, dataBlob) { 474 var task = hamsters.runtime.tasks[taskid]; 475 if(!task) { 476 hamsters.runtime.errors.push({ 477 'msg': 'Error, unable to match thread to task, throwing exception', 478 'params': hamsterfood, 479 'aggregate': aggregate, 480 'callback': callback 481 }); 482 console.error('Error, unable to match thread to task ' + taskid + ', throwing exception. Check errors for more details'); 483 return; 484 } 485 var queue = hamsters.runtime.queue; 486 if(hamsters.maxThreads && hamsters.maxThreads <= queue.running.length) { 487 queue.pending.push({ 488 'input': inputArray, 489 'params': hamsterfood, 490 'workerid': threadid, 491 'callback': callback, 492 'taskid': taskid, 493 'aggregate': aggregate 494 }); 495 return; 496 } 497 var thread = (threadid || task.count); //Determine threadid depending on currently running threads 498 var debug = hamsters.debug; 499 if(debug) { 500 task.input.push({ 501 'input': inputArray, 502 'workerid': thread, 503 'taskid': taskid, 504 'params': hamsterfood, 505 'start': new Date().getTime() 506 }); 507 if(debug === 'verbose') { 508 console.info('Spawning Hamster #' + thread + ' @ ' + new Date().getTime()); 509 } 510 } 511 if(hamsters.isLegacy()) { //Legacy fallback for IE10 and older mobile devices.. these don't support web workers properly 512 hamsters.runtime.legacyProcessor(hamsterfood, inputArray, function(output) { 513 task.count++; //Thread finished 514 task.output[threadid] = output.data; 515 if(task.count === task.threads) { //Task complete get output and return 516 var rtn = hamsters.runtime.getOutput(task.output); 517 hamsters.runtime.tasks[taskid] = null; //Clean up our task, not needed any longer 518 if(aggregate) { 519 rtn = hamsters.tools.aggregate(rtn, null); 520 } 521 if(callback) { 522 if(debug) { 523 console.info('Execution Complete! Elapsed: ' + ((new Date().getTime() - task.input[0].start)/1000) + 's'); 524 } 525 callback(rtn); 526 } 527 } 528 }); 529 return; 530 } 531 task.workers[task.workers.length] = thread; //Keep track of threads scoped to current task 532 queue.running[queue.running.length] = thread; //Keep track of all currently running threads 533 var blobObject = (dataBlob || null); 534 var hamsterData; 535 if(!hamster) { 536 hamsterData = hamsters.runtime.createHamster(thread); 537 hamster = hamsterData.worker; 538 blobObject = {'blob': hamsterData.dataBlob, 'uri': hamsterData.blobUri}; 539 } 540 hamsters.runtime.trainHamster(thread, aggregate, callback, taskid, thread, hamster, blobObject); 541 hamsters.runtime.feedHamster(hamster, hamsterfood, inputArray); 542 task.count++; //Increment count, thread is running 543 }; 544 545 /** 546 * @description: Simulates threading for execution on devices that don't support workers 547 * @constructor 548 * @method legacyProcessor 549 * @param {object} food - Input params object 550 * @param {array} inputArray - Input array 551 * @param {function} callback - Callback function to return response 552 * @return 553 */ 554 hamsters.runtime.legacyProcessor = function(food, inputArray, callback) { 555 setTimeout(function() { 556 var params = food; 557 var rtn = { 558 'success': true, 559 'data': [] 560 }; 561 if(params.fn) { 562 params.array = inputArray; 563 var fn = eval('('+params.fn+')'); 564 if(fn && typeof fn === 'function') { 565 try { 566 fn(); 567 if(callback) { 568 callback(rtn); // Return legacy output 569 } 570 } catch(exception) { 571 rtn.success = false; 572 rtn.error = exception; 573 rtn.msg = 'Error encounted check errors for details'; 574 if(callback) { 575 callback(rtn); // Return legacy output 576 } 577 } 578 } 579 } 580 }, 4); //4ms delay (HTML5 spec minimum), simulate threading 581 }; 582 583 /** 584 * @description: Creates web worker thread 585 * @constructor 586 * @method createHamster 587 * @param {integer} thread - Thread # 588 * @return ObjectExpression 589 */ 590 hamsters.runtime.createHamster = function(thread) { 591 var hamster = hamsters.runtime.setup.getOrCreateElement(thread); 592 var blob = hamsters.runtime.createBlob(hamster.textContent); 593 var uri = window.URL.createObjectURL(blob); 594 hamster = new Worker(uri); 595 return {'worker': hamster, 'dataBlob': blob, 'blobUri': uri}; 596 }; 597 598 /** 599 * @description: Sends ajax request 600 * @constructor 601 * @method sendRequest 602 * @param {string} type - 'GET' || 'POST' 603 * @param {string} url - Url to connect to 604 * @param {string} responseType - 'ArrayBuffer','','blob','document','json','text' 605 * @param {string} callback - Callback to return response 606 * @return 607 */ 608 hamsters.runtime.sendRequest = function(type, url, responseType, callback) { 609 var xhr = new XMLHttpRequest(); 610 xhr.open(type, url); 611 xhr.responseType = responseType; 612 /** 613 * Description 614 * @method onload 615 * @return 616 */ 617 xhr.onload = function() { 618 callback(this.response); 619 }; 620 xhr.send(); 621 }; 622 623 /** 624 * @description: Creates array buffer by issuing a fake ajax request with response of arraybuffer 625 * @constructor 626 * @method fetchArrayBuffer 627 * @param {string} string - input params object 628 * @param {function} callback - callback function to return buffer to 629 * @return 630 */ 631 hamsters.runtime.fetchArrayBuffer = function(string, callback) { 632 var url = window.URL.createObjectURL(hamsters.runtime.createBlob(string)); 633 hamsters.runtime.sendRequest('GET', url, 'arraybuffer', function(arrayBuffer) { 634 if(callback) { 635 callback(arrayBuffer); 636 } 637 }); 638 }; 639 640 /** 641 * @description: Creates dataBlob for worker generation 642 * @constructor 643 * @method createBlob 644 * @param {string} textContent - Web worker boiler plate 645 * @return blob 646 */ 647 hamsters.runtime.createBlob = function(textContent) { 648 var blob; 649 try { 650 blob = new Blob([textContent], {type: 'application/javascript'}); 651 } catch(err) { 652 var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; 653 if(BlobBuilder) { //Fallback for browsers that don't support blob constructor 654 blob = new BlobBuilder(); 655 blob.append([textContent], {type: 'application/javascript'}); 656 blob = blob.getBlob(); 657 } 658 } 659 return blob; 660 }; 661 662 /** 663 * @description: Aggregates individual hamster outputs into a single array 664 * @constructor 665 * @method aggregate 666 * @param {array} input - incoming array of subarrays 667 * @param {string} dataType 668 * @return output 669 */ 670 hamsters.tools.aggregate = function(input, dataType) { 671 if(!input) { 672 console.error("Missing array"); 673 return; 674 } 675 if(input.length > 20) { 676 return input; 677 } 678 if(dataType) { 679 return hamsters.runtime.aggregateTypedArrays(input, dataType); 680 } 681 var output = []; 682 output = input.reduce(function(a, b) { 683 return a.concat(b); 684 }); 685 input = null; 686 return output; 687 }; 688 689 /** 690 * @description: Get our nested output values from each task, return array of subarrays 691 * @constructor 692 * @method getOutput 693 * @param {array} output - incoming task output 694 * @param {boolean} aggregate 695 * @param {string} dataType 696 * @return rtn 697 */ 698 hamsters.runtime.getOutput = function(output, aggregate, dataType) { 699 var rtn; 700 if(aggregate) { 701 rtn = hamsters.tools.aggregate(output, dataType); 702 } else { 703 rtn = output; 704 } 705 return rtn; 706 }; 707 708 /** 709 * @description: Process next item in queue 710 * @constructor 711 * @method processQueue 712 * @param {object} hamster - Most recently finished web worker, for reuse 713 * @param {blob} dataBlob 714 * @return 715 */ 716 hamsters.runtime.processQueue = function(hamster, dataBlob) { 717 var item = hamsters.runtime.queue.pending.shift(); //Get and remove first item from queue 718 if(item) { 719 hamsters.runtime.newWheel(item.input, item.params, item.aggregate, item.callback, item.taskid, item.workerid, hamster, dataBlob); //Assign most recently finished thread to queue item 720 } 721 }; 722 723 /** 724 * @description: Cleans up memory used by dataBlob 725 * @constructor 726 * @method terminateHamster 727 * @param {object} dataBlob - dataBlob to free from memory, critical for IE11 support 728 * @return 729 */ 730 hamsters.runtime.terminateHamster = function(dataBlob) { 731 if(dataBlob) { 732 window.URL.revokeObjectURL(dataBlob.uri); 733 if(dataBlob.blob.close) { 734 dataBlob.blob.close(); 735 } 736 if(dataBlob.blob.msClose) { 737 dataBlob.blob.msClose(); 738 } 739 delete dataBlob.blob; 740 delete dataBlob.uri; 741 dataBlob = null; 742 } 743 }; 744 745 /** 746 * @description: Handle response from worker thread, setup error handling 747 * @constructor 748 * @method trainHamster 749 * @param {integer} id - global runtime threadid 750 * @param {boolean} aggregate - boolean aggregate individual thread outputs into final array 751 * @param {function} callback - task callback when all hamsters complete 752 * @param {integer} taskid - global runtime task id 753 * @param {integer} workerid - worker runtime threadid 754 * @param {object} hamster - web worker 755 * @param {blob} dataBlob 756 * @return 757 */ 758 hamsters.runtime.trainHamster = function(id, aggregate, callback, taskid, workerid, hamster, dataBlob) { 759 760 /** 761 * @description: Runs when a hamster (thread) finishes it's work 762 * @constructor 763 * @method onmessage 764 * @param {object} e - Web Worker event object 765 * @return 766 */ 767 hamster.onmessage = function(e) { 768 var queue = hamsters.runtime.queue; 769 if(queue.pending.length === 0) { 770 hamster.terminate(); //Kill the thread only if no items waiting to run (20-22% performance improvement observed during testing, repurposing threads vs recreating them) 771 setTimeout(hamsters.runtime.terminateHamster(dataBlob), 4); 772 } 773 queue.running.splice(queue.running.indexOf(id), 1); //Remove thread from running pool 774 var task = hamsters.runtime.tasks[taskid]; 775 if(!task) { 776 hamsters.runtime.errors = hamsters.runtime.errors.concat( 777 { 778 'timeStamp': e.timeStamp, 779 'msg': 'Error, unable to match thread to task, throwing exception', 780 'taskid': taskid, 781 'workerid': workerid, 782 'aggregate': aggregate, 783 'callback': callback 784 } 785 ); 786 console.error('Fatal Exception, unable to match thread #'+workerid+' to task #'+ taskid + ', cannot continue. Check errors for more details'); 787 return; 788 } 789 task.workers.splice(task.workers.indexOf(workerid), 1); //Remove thread from task running pool 790 var taskComplete = false; 791 if(task.workers.length === 0 && task.count === task.threads) { 792 taskComplete = true; 793 } 794 var results = e.data.results; 795 if(results.dataType && typeof results.data === 'object') { 796 results.data = hamsters.runtime.processDataType(results.dataType, results.data); 797 } 798 task.output[workerid] = results.data; 799 var debug = hamsters.debug; 800 if(debug === 'verbose') { 801 console.info('Hamster #' + id + ' finished ' + '@ ' + e.timeStamp); 802 } 803 if(taskComplete) { //Task complete, finish up 804 var output = hamsters.runtime.getOutput(task.output, aggregate, results.dataType); 805 hamsters.runtime.tasks[taskid] = null; //Clean up our task, not needed any longer 806 if(callback) { 807 if(debug) { 808 console.info('Execution Complete! Elapsed: ' + ((e.timeStamp - task.input[0].start)/1000) + 's'); 809 } 810 callback(output); 811 if(hamsters.cache) { 812 if(task.threads === 1) { 813 output = output[0]; 814 } 815 if(task.input.length > 0 && output.length > 0 && !task.dataType) { 816 hamsters.runtime.cacheResult(String(task.fn), task.input, output); 817 } else if(task.input.length > 0 && output.length > 0 && task.dataType) { 818 hamsters.runtime.cacheResult(String(task.fn), task.input, hamsters.runtime.normalizeArray(output), task.dataType); 819 } 820 } 821 } 822 } 823 if(queue.pending.length !== 0) { 824 hamsters.runtime.processQueue(hamster, dataBlob); 825 } 826 }; 827 828 /** 829 * @description: Setup error handling 830 * @constructor 831 * @method onerror 832 * @param {object} e - Web Worker event object 833 * @return 834 */ 835 hamster.onerror = function(e) { 836 hamster.terminate(); //Kill the thread 837 var msg = 'Error Hamster #' + id + ': Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message; 838 hamsters.runtime.errors = hamsters.runtime.errors.concat( 839 { 840 'msg': msg 841 } 842 ); 843 console.error(msg); 844 }; 845 }; 846 847 /** 848 * @description: Normalizes typed array into normal array 849 * @constructor 850 * @method normalizeArray 851 * @param {object} input - typedArray input 852 * @return arr 853 */ 854 hamsters.runtime.normalizeArray = function(input) { 855 var arr = []; 856 for (var n = 0, len = input.length; n < len; n++) { 857 arr.push(input[n]); 858 } 859 input = null; 860 return arr; 861 }; 862 863 /** 864 * Description 865 * @method aggregateTypedArrays 866 * @param {array} input 867 * @param {string} dataType 868 * @return output 869 */ 870 hamsters.runtime.aggregateTypedArrays = function(input, dataType) { 871 var output; 872 var length = 0; 873 var offset = 0; 874 for (var i = 0, len = input.length; i < len; i++) { 875 length += input[i].length; 876 } 877 output = hamsters.runtime.processDataType(dataType, length); 878 for (var n = 0, len2 = input.length; n < len2; n++) { 879 output.set(input[n], offset); 880 offset += input[n].length; 881 } 882 input = null; 883 return output; 884 }; 885 886 /** 887 * @description: Converts array buffer or normal array into a typed array 888 * @constructor 889 * @method processDataType 890 * @param {string} dataType - dataType config param 891 * @param {object} buffer - buffer object or normal array 892 * @return arr 893 */ 894 hamsters.runtime.processDataType = function(dataType, buffer) { 895 if(!dataType) { 896 return buffer; 897 } 898 var arr; 899 switch(dataType.toLowerCase()) { 900 case 'uint32': 901 arr = new Uint32Array(buffer); 902 break; 903 case 'uint16': 904 arr = new Uint16Array(buffer); 905 break; 906 case 'uint8': 907 arr = new Uint8Array(buffer); 908 break; 909 case 'uint8clamped': 910 arr = new Uint8ClampedArray(buffer); 911 break; 912 case 'int32': 913 arr = new Int32Array(buffer); 914 break; 915 case 'int16': 916 arr = new Int16Array(buffer); 917 break; 918 case 'int8': 919 arr = new Int8Array(buffer); 920 break; 921 case 'float32': 922 arr = new Float32Array(buffer); 923 break; 924 case 'float64': 925 arr = new Float64Array(buffer); 926 break; 927 default: 928 arr = buffer; 929 break; 930 } 931 return arr; 932 }; 933 934 /** 935 * @description: Sends message to worker thread to invoke execution 936 * @constructor 937 * @method feedHamster 938 * @param {object} hamster - web worker 939 * @param {object} food - params object for worker 940 * @param {array} inputArray 941 * @return 942 */ 943 hamsters.runtime.feedHamster = function(hamster, food, inputArray) { 944 if(inputArray && food.dataType) { //Transferable object transfer if using typed array 945 food.array = hamsters.runtime.processDataType(food.dataType, inputArray); 946 } else if(inputArray) { 947 food.array = inputArray; 948 } 949 if(food.array && food.array.buffer) { 950 var buffer = food.array.buffer; 951 food.array = buffer; 952 hamster.postMessage(food, [buffer]); 953 } else if((window.chrome || window.firefox) && food.array.length <= 35000000) { //Stringify data for chrome/firefox on less than 35M array size, good performance boost 954 hamster.postMessage(JSON.stringify(food)); 955 } else { 956 hamster.postMessage(food); //Use structured cloning for Safari/IE or for data sets > 40M+ 957 } 958 }; 959 //Setup 960 hamsters.runtime.setup.isLegacy(); 961 hamsters.runtime.setup.populateElements(hamsters.maxThreads); 962 }; 963 //Wake 'em up 964 hamsters.runtime.wakeUp();