1. /*
  2. Plexer.js v.1.1
  3. Bootstraps a function to a web worker, if available, else creates an instance
  4. By Norbert Landsteiner 2013, mass:werk - media environments, www.masswerk.at
  5. License: MIT-License (Please keep this header anyway)
  6. Project page: http://www.masswerk.at/plexer
  7. */
  8.  
  9. function Plexer( funcRef, forceInstance ) {
  10. /*
  11. new Plexer( <func-ref> [, <force-instance> [, <rest>* ]] )
  12. func-ref: Function - function to bootstrap
  13. force-instance: Boolean - do not use worker, force instance (for testing), optional
  14. rest: Function(s) - any external functions to be included to the worker
  15. */
  16.  
  17. var instance, worker, urlConstructor, blobUrl, terminated,
  18. usesWorker=false, hasTransferables=false, runmode,
  19. workerCallbacks={}, externalRefs=[];
  20.  
  21. function bootstrap() {
  22. var blob, t, srcString, i, l;
  23. terminated=false;
  24. if (!forceInstance) {
  25. try {
  26. urlConstructor = self.URL || self.webkitURL;
  27. if ((self.Blob || self.BlobBuilder) && self.Worker && urlConstructor) {
  28. srcString=funcRef.toString();
  29. srcString='('+srcString.replace(/\{/, '{\n'+_workerMessageReceiver.toString()+'\nself.addEventListener("message", _workerMessageReceiver, false);\n')+')()';
  30. for (i=0, l=externalRefs.length; i<l; i++) srcString+='\n'+externalRefs[i].toString();
  31. if (self.Blob) {
  32. blob=new Blob([srcString], {type: 'text/javascript'});
  33. }
  34. else {
  35. blob=new BlobBuilder().append(srcString).getBlob('text/javascript');
  36. }
  37. blobUrl=urlConstructor.createObjectURL(blob);
  38. worker=new Worker(blobUrl);
  39. if (!worker.postMessage) worker.postMessage=worker.webkitPostMessage;
  40. try {
  41. t=new ArrayBuffer(1);
  42. worker.postMessage(t, [t]);
  43. if (!t.byteLength) hasTransferables=true;
  44. }
  45. catch(e2) {}
  46. worker.addEventListener('message', _workerMessageHandler, false);
  47. worker.addEventListener('error', _workerErrorHandler, false);
  48. runmode='worker';
  49. }
  50. }
  51. catch(e) {
  52. if (urlConstructor && blobUrl) {
  53. try {
  54. urlConstructor.revokeObjectURL(blobUrl);
  55. }
  56. catch(e) {}
  57. }
  58. worker=urlConstructor=blobUrl=null;
  59. }
  60. }
  61. if (!worker) {
  62. instance=new funcRef();
  63. runmode='instance';
  64. }
  65. if (self.addEventListener) {
  66. self.addEventListener('unload', terminateReference, false);
  67. }
  68. else if (self.attachEvent) {
  69. self.attachEvent('onunload', terminateReference);
  70. }
  71. }
  72. // will be injected into the worker
  73. function _workerMessageReceiver(msg) {
  74. var args, d, i, l, n, method, f, r, map, data;
  75. if (typeof msg.data == 'object' && typeof msg.data.plexer_job == 'string') {
  76. d=msg.data;
  77. if (d.length) {
  78. args=[];
  79. for (i=0, l=d.length; i<l; i++) args.push(d['a'+i]);
  80. }
  81. else {
  82. args=d.args;
  83. }
  84. method=d.plexer_job;
  85. f=self[method];
  86. if (typeof f == 'function') {
  87. r=f.apply(f, args);
  88. if (d.ticket) {
  89. if (!self.postMessage) self.postMessage=self.webkitPostMessage;
  90. data = {'plexer_receipt': method, 'ticket': d.ticket};
  91. if (d.preserve) data.preserve=true;
  92. n=Number(d.transferables);
  93. if (!isNaN(n) && n && r!==undefined) {
  94. if (Object.prototype.toString.call(r)!='[object Array]') r=[r];
  95. map=[];
  96. for (i=0, l=r.length; i<l; i++) {
  97. data['r'+i]=r[i];
  98. if (n>0) {
  99. map.push(r[i]);
  100. n--;
  101. }
  102. }
  103. data.length=l;
  104. self.postMessage(data, map);
  105. }
  106. else {
  107. data.result=r;
  108. self.postMessage(data);
  109. }
  110. }
  111. }
  112. else {
  113. throw new Error('Method "'+method+'" not found or not a function.');
  114. }
  115. }
  116. }
  117. function _workerMessageHandler(msg) {
  118. var result, d, i, l, method, f;
  119. if (typeof msg.data == 'object' && typeof msg.data.plexer_receipt == 'string' && typeof msg.data.ticket == 'string') {
  120. d=msg.data;
  121. if (d.length) {
  122. result=[];
  123. for (i=0, l=d.length; i<l; i++) result.push(d['r'+i]);
  124. }
  125. else {
  126. result=d.result;
  127. }
  128. f=workerCallbacks[d.ticket];
  129. delete workerCallbacks[d.ticket]
  130. if (typeof f == 'function') {
  131. if (d.length || (!d.preserve && Object.prototype.toString.call(result)=='[object Array]')) {
  132. f.apply(f, result);
  133. }
  134. else {
  135. f(result);
  136. }
  137. }
  138. }
  139. }
  140. function _workerErrorHandler(e) {
  141. if (e && self.console) {
  142. console.log('Plexer-Worker: '+e.message);
  143. }
  144. }
  145.  
  146. // public functions / interface
  147.  
  148. /*
  149. public/private
  150. takes no argument as method .@terminate(): void,
  151. takes an event as agrument when called internally as a handler (onunload)
  152. returns: void
  153. */
  154. function terminateReference(event) {
  155. if (worker) {
  156. try {
  157. worker.removeEventListener('message', _workerMessageHandler);
  158. worker.removeEventListener('error', _workerErrorHandler);
  159. worker.terminate();
  160. }
  161. catch(e) {}
  162. }
  163. if (urlConstructor && blobUrl) {
  164. try {
  165. urlConstructor.revokeObjectURL(blobUrl);
  166. }
  167. catch(e) {}
  168. }
  169. worker=urlConstructor=blobUrl=instance=null;
  170. workerCallbacks={};
  171. if (self.addEventListener) {
  172. self.removeEventListener('unload', terminateReference);
  173. }
  174. else if (self.attachEvent) {
  175. self.detachEvent('onunload', terminateReference);
  176. }
  177. if (event || window.event) {
  178. if (!event) event=window.event;
  179. if (event.type=='unload') {
  180. funcRef=null;
  181. externalRefs.length=0;
  182. }
  183. }
  184. terminated=true;
  185. }
  186. /*
  187. .@run( <method-name> [, <arguments-array> [, <callback-function> [, <transferables-up> [, <transferables-down> [, <preserve-arrays>]]]]] ): void
  188. method-name: String - name of the method to be called (required)
  189. arguments-array: Array of arguments to be passed to the method or empty (null | undefined), default: []
  190. callback-function: Function to receive any results, default: null
  191. transferables-up: Number of first n arguments to be sent to the worker as transferable objects, default: 0
  192. transferables-down: Number of first n arguments to be sent back by the worker as transferable objects, default: 0
  193. preserve-arrays: Boolean - false: apply an array-result as the callback's arguments-array (default)
  194. - true: if the result is an array, return the array-reference
  195. returns: void
  196. */
  197. function run(method, args, callback, transferablesUp, transferablesDown, preserveArrays) {
  198. var data, map, ticket, i, l, f, r;
  199. if (Object.prototype.toString.call(method)!='[object String]') {
  200. throw new Error('Plexer: 1st argument (method-name) must be a string.');
  201. }
  202. if (args === null || args === undefined) {
  203. args = [];
  204. }
  205. else if (Object.prototype.toString.call(args)!='[object Array]') {
  206. throw new Error('Plexer: 2nd argument (call-arguments) must be an array.');
  207. }
  208. if (callback && typeof callback != 'function') {
  209. throw new Error('Plexer: Callback is not a function.');
  210. }
  211. if (terminated) bootstrap();
  212. if (worker) {
  213. if (!hasTransferables) {
  214. transferablesUp=transferablesDown=0;
  215. }
  216. else {
  217. if (typeof transferablesUp != 'string') Number(transferablesUp);
  218. if (typeof transferablesDown != 'string') Number(transferablesDown);
  219. if (!isNaN(transferablesUp) && transferablesUp>0) {
  220. transferablesUp=Math.floor(transferablesUp);
  221. }
  222. else {
  223. transferablesUp=0;
  224. }
  225. if (!isNaN(transferablesDown) && transferablesDown>0) {
  226. transferablesDown=Math.floor(transferablesDown);
  227. }
  228. else {
  229. transferablesDown=0;
  230. }
  231. }
  232. data = {'plexer_job': method};
  233. if (callback) {
  234. ticket = Number(Math.floor(Math.random()*Math.pow(2,32))).toString(16)+'_'+Number(new Date().getTime()).toString(16);
  235. data.ticket=ticket;
  236. workerCallbacks[ticket]=callback;
  237. }
  238. if (preserveArrays) data.preserve=true;
  239. if (transferablesDown) data.transferables=transferablesDown;
  240. if (transferablesUp && args.length) {
  241. map=[];
  242. for (i=0, l=args.length; i<l; i++) {
  243. data['a'+i]=args[i];
  244. if (transferablesUp) {
  245. map.push(args[i]);
  246. transferablesUp--;
  247. }
  248. }
  249. data.length=l;
  250. worker.postMessage(data, map);
  251. }
  252. else {
  253. data.args=args;
  254. worker.postMessage(data);
  255. }
  256. }
  257. else {
  258. f=instance[method];
  259. if (typeof f == 'function') {
  260. r=f.apply(f, args);
  261. if (callback) {
  262. if (preserveArrays || Object.prototype.toString.call(r)!='[object Array]') {
  263. setTimeout( function() { callback(r); }, 1);
  264. }
  265. else {
  266. setTimeout( function() { callback.apply(callback, r); }, 1);
  267. }
  268. }
  269. }
  270. else {
  271. throw new Error('Plexer (as in instance): Method "'+method+'" not found or not a function.');
  272. }
  273. }
  274. }
  275.  
  276. /*
  277. .@isTerminated(): Boolean
  278. returns: Boolean (true: yes, is terminated)
  279. */
  280. function isTerminated() {
  281. return terminated;
  282. }
  283.  
  284. /*
  285. .@getRunmode(): String
  286. returns: String ('worker' | 'instance')
  287. */
  288. function getRunmode() {
  289. return runmode;
  290. }
  291.  
  292. /*
  293. constructor tasks
  294. */
  295. if (typeof funcRef == 'function') {
  296. for (var i=2; i<arguments.length; i++) {
  297. if (typeof arguments[i] == 'function') externalRefs.push(arguments[i]);
  298. }
  299. bootstrap();
  300. }
  301. else {
  302. throw new Error('Plexer: First argument must be a function reference.');
  303. }
  304. return new function() {
  305. this.run=run;
  306. this.terminate=terminateReference;
  307. this.isTerminated=isTerminated;
  308. this.getRunmode=getRunmode;
  309. }
  310. }

Back to Plexer.js | Download plexer.zip (v.1.1, full-version, minified, and docs).