1
+ angular . module ( 'FredrikSandell.worker-pool-pi-calculator' , [ ] ) . service ( 'PiCalculatorService' , [
2
+ '$q' ,
3
+ function ( $q ) {
4
+ var that = { } ;
5
+ /**
6
+ * If this is done correctly really doesn't matter. What we need is a long running task which hogs CPU-time.
7
+ * Number of decimals to calculate for PI
8
+ * @param num
9
+ */
10
+ that . calculatePi = function ( num ) {
11
+ var deferred = $q . defer ( ) ;
12
+ var pi = 4 , top = 4 , bot = 3 , minus = true ;
13
+ var startTime = Date . now ( ) ;
14
+ var somewhatExactPi = next ( pi , top , bot , minus , num , deferred ) ;
15
+ deferred . resolve ( {
16
+ pi : somewhatExactPi ,
17
+ runtime : calculateRuntime ( startTime , Date . now ( ) )
18
+ } ) ;
19
+ return deferred . promise ;
20
+ } ;
21
+ function next ( pi , top , bot , minus , num , deferred ) {
22
+ for ( var i = 0 ; i < num ; i ++ ) {
23
+ pi += minus ? - ( top / bot ) : top / bot ;
24
+ minus = ! minus ;
25
+ bot += 2 ;
26
+ if ( i % 1000 === 0 ) {
27
+ deferred . notify ( pi ) ;
28
+ }
29
+ }
30
+ return pi ;
31
+ }
32
+ function calculateRuntime ( start , end ) {
33
+ var total = end - start ;
34
+ if ( total >= 1000 ) {
35
+ total = total / 1000 + 'seconds' ;
36
+ } else {
37
+ total += 'ms' ;
38
+ }
39
+ return total ;
40
+ }
41
+ return that ;
42
+ }
43
+ ] ) ;
44
+ angular . module ( 'FredrikSandell.worker-pool' , [ ] ) . service ( 'WorkerService' , [
45
+ '$q' ,
46
+ function ( $q ) {
47
+ var that = { } ;
48
+ //this should be configured from the app in the future
49
+ var urlToAngular = 'http://localhost:9876/base/bower_components/angular/angular.js' ;
50
+ var serviceToUrlMap = { } ;
51
+ that . setAngularUrl = function ( urlToAngularJs ) {
52
+ urlToAngular = urlToAngularJs ;
53
+ } ;
54
+ function createAngularWorkerTemplate ( ) {
55
+ /*jshint laxcomma:true */
56
+ /*jshint quotmark: false */
57
+ var workerTemplate = [
58
+ '' ,
59
+ '//try {' ,
60
+ 'var window = self;' ,
61
+ 'self.history = {};' ,
62
+ 'var document = {' ,
63
+ ' readyState: \'complete\',' ,
64
+ ' cookie: \'\',' ,
65
+ ' querySelector: function () {},' ,
66
+ ' createElement: function () {' ,
67
+ ' return {' ,
68
+ ' pathname: \'\',' ,
69
+ ' setAttribute: function () {}' ,
70
+ ' };' ,
71
+ ' }' ,
72
+ '};' ,
73
+ 'importScripts(\'<URL_TO_ANGULAR>\');' ,
74
+ '<CUSTOM_DEP_INCLUDES>' ,
75
+ 'angular = window.angular;' ,
76
+ 'var workerApp = angular.module(\'WorkerApp\', [<DEP_MODULES>]);' ,
77
+ 'workerApp.run([\'$q\'<STRING_DEP_NAMES>, function ($q<DEP_NAMES>) {' ,
78
+ ' self.addEventListener(\'message\', function(e) {' ,
79
+ ' var input = e.data;' ,
80
+ ' var output = $q.defer();' ,
81
+ ' var promise = output.promise;' ,
82
+ ' promise.then(function(success) {' ,
83
+ ' self.postMessage({event:\'success\', data : success});' ,
84
+ ' }, function(reason) {' ,
85
+ ' self.postMessage({event:\'failure\', data : reason});' ,
86
+ ' }, function(update) {' ,
87
+ ' self.postMessage({event:\'update\', data : update});' ,
88
+ ' });' ,
89
+ ' <WORKER_FUNCTION>;' ,
90
+ ' });' ,
91
+ ' self.postMessage({event:\'initDone\'});' ,
92
+ '}]);' ,
93
+ 'angular.bootstrap(null, [\'WorkerApp\']);' ,
94
+ '//} catch(e) {self.postMessage(JSON.stringify(e));}'
95
+ ] ;
96
+ return workerTemplate . join ( '\n' ) ;
97
+ }
98
+ var workerTemplate = createAngularWorkerTemplate ( ) ;
99
+ that . addDependency = function ( serviceName , moduleName , url ) {
100
+ serviceToUrlMap [ serviceName ] = {
101
+ url : url ,
102
+ moduleName : moduleName
103
+ } ;
104
+ return that ;
105
+ } ;
106
+ function createIncludeStatements ( listOfServiceNames ) {
107
+ var includeString = '' ;
108
+ angular . forEach ( listOfServiceNames , function ( serviceName ) {
109
+ if ( serviceToUrlMap [ serviceName ] ) {
110
+ includeString += 'importScripts(\'' + serviceToUrlMap [ serviceName ] . url + '\');' ;
111
+ }
112
+ } ) ;
113
+ return includeString ;
114
+ }
115
+ function createModuleList ( listOfServiceNames ) {
116
+ var moduleNameList = [ ] ;
117
+ angular . forEach ( listOfServiceNames , function ( serviceName ) {
118
+ if ( serviceToUrlMap [ serviceName ] ) {
119
+ moduleNameList . push ( '\'' + serviceToUrlMap [ serviceName ] . moduleName + '\'' ) ;
120
+ }
121
+ } ) ;
122
+ return moduleNameList . join ( ',' ) ;
123
+ }
124
+ function createDependencyMetaData ( dependencyList ) {
125
+ var dependencyServiceNames = dependencyList . filter ( function ( dep ) {
126
+ return dep !== 'input' && dep !== 'output' && dep !== '$q' ;
127
+ } ) ;
128
+ var depMetaData = {
129
+ dependencies : dependencyServiceNames ,
130
+ moduleList : createModuleList ( dependencyServiceNames ) ,
131
+ angularDepsAsStrings : dependencyServiceNames . length > 0 ? ',' + dependencyServiceNames . map ( function ( dep ) {
132
+ return '\'' + dep + '\'' ;
133
+ } ) . join ( ',' ) : '' ,
134
+ angularDepsAsParamList : dependencyServiceNames . length > 0 ? ',' + dependencyServiceNames . join ( ',' ) : '' ,
135
+ servicesIncludeStatements : createIncludeStatements ( dependencyServiceNames )
136
+ } ;
137
+ depMetaData . workerFuncParamList = 'input,output' + depMetaData . angularDepsAsParamList ;
138
+ return depMetaData ;
139
+ }
140
+ function populateWorkerTemplate ( workerFunc , dependencyMetaData ) {
141
+ return workerTemplate . replace ( '<URL_TO_ANGULAR>' , urlToAngular ) . replace ( '<CUSTOM_DEP_INCLUDES>' , dependencyMetaData . servicesIncludeStatements ) . replace ( '<DEP_MODULES>' , dependencyMetaData . moduleList ) . replace ( '<STRING_DEP_NAMES>' , dependencyMetaData . angularDepsAsStrings ) . replace ( '<DEP_NAMES>' , dependencyMetaData . angularDepsAsParamList ) . replace ( '<WORKER_FUNCTION>' , workerFunc . toString ( ) ) ;
142
+ }
143
+ var buildAngularWorker = function ( initializedWorker ) {
144
+ var that = { } ;
145
+ that . worker = initializedWorker ;
146
+ that . run = function ( input ) {
147
+ var deferred = $q . defer ( ) ;
148
+ initializedWorker . addEventListener ( 'message' , function ( e ) {
149
+ var eventId = e . data . event ;
150
+ //console.log(e.data);
151
+ if ( eventId === 'initDone' ) {
152
+ throw 'Received worker initialization in run method. This should already have occurred!' ;
153
+ } else if ( eventId === 'success' ) {
154
+ deferred . resolve ( e . data . data ) ;
155
+ } else if ( eventId === 'failure' ) {
156
+ deferred . reject ( e . data . data ) ;
157
+ } else if ( eventId === 'update' ) {
158
+ deferred . notify ( e . data . data ) ;
159
+ } else {
160
+ deferred . reject ( e ) ;
161
+ }
162
+ } ) ;
163
+ initializedWorker . postMessage ( input ) ;
164
+ return deferred . promise ;
165
+ } ;
166
+ that . terminate = function ( ) {
167
+ initializedWorker . terminate ( ) ;
168
+ } ;
169
+ return that ;
170
+ } ;
171
+ var extractDependencyList = function ( depFuncList ) {
172
+ return depFuncList . slice ( 0 , depFuncList . length - 1 ) ;
173
+ } ;
174
+ var workerFunctionToString = function ( func , paramList ) {
175
+ return '(' + func . toString ( ) + ')(' + paramList + ')' ;
176
+ } ;
177
+ /**
178
+ * example call:
179
+ * WorkerService.createAngularWorker(['input', 'output', '$http', function(input, output, $http)
180
+ * {body of function}]);
181
+ * Parameters "input" and "output" is required. Not defining them will cause a runtime error.
182
+ * Declaring services to be injected, as '$http' is above, requires the web worker to be able to resolve them.
183
+ * '$http' service is a part of the standard angular package which means it will resolve without additional information
184
+ * since angular source is always loaded in the web worker.
185
+ * But if a custom service was to be injected the WorkerService would need be be informed on how to resolve the.
186
+ * @param depFuncList
187
+ */
188
+ that . createAngularWorker = function ( depFuncList ) {
189
+ //validate the input
190
+ if ( ! Array . isArray ( depFuncList ) || depFuncList . length < 3 || typeof depFuncList [ depFuncList . length - 1 ] !== 'function' ) {
191
+ throw 'Input needs to be: [\'workerInput\',\'deferredOutput\'/*optional additional dependencies*/,\n' + ' function(workerInput, deferredOutput /*optional additional dependencies*/)\n' + ' {/*worker body*/}' + ']' ;
192
+ }
193
+ var deferred = $q . defer ( ) ;
194
+ var dependencyMetaData = createDependencyMetaData ( extractDependencyList ( depFuncList ) ) ;
195
+ var blobURL = ( window . webkitURL ? webkitURL : URL ) . createObjectURL ( new Blob ( [ populateWorkerTemplate ( workerFunctionToString ( depFuncList [ depFuncList . length - 1 ] , dependencyMetaData . workerFuncParamList ) , dependencyMetaData ) ] , { type : 'application/javascript' } ) ) ;
196
+ var worker = new Worker ( blobURL ) ;
197
+ //wait for the worker to load resources
198
+ worker . addEventListener ( 'message' , function ( e ) {
199
+ var eventId = e . data . event ;
200
+ console . log ( e . data ) ;
201
+ if ( eventId === 'initDone' ) {
202
+ deferred . resolve ( buildAngularWorker ( worker ) ) ;
203
+ } else {
204
+ deferred . reject ( e ) ;
205
+ }
206
+ } ) ;
207
+ return deferred . promise ;
208
+ } ;
209
+ return that ;
210
+ }
211
+ ] ) ;
0 commit comments