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();