|
1
|
/* |
|
2
|
2022-05-20 |
|
3
|
|
|
4
|
The author disclaims copyright to this source code. In place of a |
|
5
|
legal notice, here is a blessing: |
|
6
|
|
|
7
|
* May you do good and not evil. |
|
8
|
* May you find forgiveness for yourself and forgive others. |
|
9
|
* May you share freely, never taking more than you give. |
|
10
|
|
|
11
|
*********************************************************************** |
|
12
|
|
|
13
|
This is a JS Worker file for use with the pikchr wasm build. It |
|
14
|
loads the pikchr wasm module and offers access to it via the Worker |
|
15
|
message-passing interface. |
|
16
|
|
|
17
|
Because we can have only a single message handler, as opposed to an |
|
18
|
arbitrary number of discrete event listeners like with DOM elements, |
|
19
|
we have to define a lower-level message API. Messages abstractly |
|
20
|
look like: |
|
21
|
|
|
22
|
{ type: string, data: type-specific value } |
|
23
|
|
|
24
|
Where 'type' is used for dispatching and 'data' is a |
|
25
|
'type'-dependent value. |
|
26
|
|
|
27
|
The 'type' values expected by each side of the main/worker |
|
28
|
connection vary. The types are described below but subject to |
|
29
|
change at any time as this experiment evolves. |
|
30
|
|
|
31
|
Main-to-Worker message types: |
|
32
|
|
|
33
|
- pikchr: data=pikchr-format text to render or an object: |
|
34
|
|
|
35
|
{ |
|
36
|
pikchr: source code for the pikchr, |
|
37
|
darkMode: boolean true to adjust colors for a dark color scheme, |
|
38
|
cssClass: CSS class name to add to the SVG |
|
39
|
} |
|
40
|
|
|
41
|
Workers-to-Main message types: |
|
42
|
|
|
43
|
- stdout, stderr: indicate stdout/stderr output from the wasm |
|
44
|
layer. The data property is the string of the output, noting |
|
45
|
that the emscripten binding emits these one line at a time. Thus, |
|
46
|
if a C-side puts() emits multiple lines in a single call, the JS |
|
47
|
side will see that as multiple calls. Example: |
|
48
|
|
|
49
|
{type:'stdout', data: 'Hi, world.'} |
|
50
|
|
|
51
|
- module: Status text. This is intended to alert the main thread |
|
52
|
about module loading status so that, e.g., the main thread can |
|
53
|
update a progress widget and DTRT when the module is finished |
|
54
|
loading and available for work. Status messages come in the form |
|
55
|
|
|
56
|
{type:'module', data:{ |
|
57
|
type:'status', |
|
58
|
data: {text:string|null, step:1-based-integer} |
|
59
|
} |
|
60
|
|
|
61
|
with an incrementing step value for each subsequent message. When |
|
62
|
the module loading is complete, a message with a text value of |
|
63
|
null is posted. |
|
64
|
|
|
65
|
- pikchr: |
|
66
|
|
|
67
|
{type: 'pikchr', |
|
68
|
data:{ |
|
69
|
pikchr: input text, |
|
70
|
result: rendered result (SVG on success, HTML on error), |
|
71
|
isError: bool, true if .pikchr holds an error report, |
|
72
|
flags: integer: flags used to configure the pikchr rendering, |
|
73
|
width: if !isError, width (integer pixels) of the SVG, |
|
74
|
height: if !isError, height (integer pixels) of the SVG |
|
75
|
} |
|
76
|
} |
|
77
|
|
|
78
|
*/ |
|
79
|
|
|
80
|
"use strict"; |
|
81
|
(function(){ |
|
82
|
/** |
|
83
|
Posts a message in the form {type,data} unless passed more than |
|
84
|
2 args, in which case it posts {type, data:[arg1...argN]}. |
|
85
|
*/ |
|
86
|
const wMsg = function(type,data){ |
|
87
|
postMessage({ |
|
88
|
type, |
|
89
|
data: arguments.length<3 |
|
90
|
? data |
|
91
|
: Array.prototype.slice.call(arguments,1) |
|
92
|
}); |
|
93
|
}; |
|
94
|
|
|
95
|
const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));}; |
|
96
|
|
|
97
|
self.onerror = function(/*message, source, lineno, colno, error*/) { |
|
98
|
const err = arguments[4]; |
|
99
|
if(err && 'ExitStatus'==err.name){ |
|
100
|
/* This "cannot happen" for this wasm binding, but just in |
|
101
|
case... */ |
|
102
|
pikchrModule.isDead = true; |
|
103
|
stderr("FATAL ERROR:", err.message); |
|
104
|
stderr("Restarting the app requires reloading the page."); |
|
105
|
wMsg('error', err); |
|
106
|
} |
|
107
|
pikchrModule.setStatus('Exception thrown, see JavaScript console: '+err); |
|
108
|
}; |
|
109
|
|
|
110
|
self.onmessage = function f(ev){ |
|
111
|
ev = ev.data; |
|
112
|
switch(ev.type){ |
|
113
|
/** |
|
114
|
Runs the given text through pikchr and emits a 'pikchr' |
|
115
|
message result (output format documented above). |
|
116
|
|
|
117
|
Fires a working/start event before it starts and |
|
118
|
working/end event when it finishes. |
|
119
|
*/ |
|
120
|
case 'pikchr': |
|
121
|
if(pikchrModule.isDead){ |
|
122
|
stderr("wasm module has exit()ed. Cannot pikchr."); |
|
123
|
return; |
|
124
|
} |
|
125
|
if(!f._){ |
|
126
|
f._ = pikchrModule.cwrap('pikchr', 'string', [ |
|
127
|
'string'/*script*/, 'string'/*CSS class*/, 'number'/*flags*/, |
|
128
|
'number'/*output: SVG width*/, 'number'/*output: SVG height*/ |
|
129
|
]); |
|
130
|
} |
|
131
|
wMsg('working','start'); |
|
132
|
const stack = pikchrModule.stackSave(); |
|
133
|
try { |
|
134
|
const pnWidth = pikchrModule.stackAlloc(4), |
|
135
|
pnHeight = pikchrModule.stackAlloc(4); |
|
136
|
let script = '', flags = 0, cssClass = null; |
|
137
|
if('string'===typeof ev.data){ |
|
138
|
script = ev.data; |
|
139
|
}else if(ev.data && 'object'===typeof ev.data){ |
|
140
|
script = ev.data.pikchr; |
|
141
|
flags = ev.data.darkMode ? 0x02 : 0; |
|
142
|
if(ev.data.cssClass) cssClass = ev.data.cssClass; |
|
143
|
} |
|
144
|
pikchrModule.setValue(pnWidth, 0, "i32"); |
|
145
|
pikchrModule.setValue(pnHeight, 0, "i32"); |
|
146
|
const msg = { |
|
147
|
pikchr: script, |
|
148
|
result: (f._(script, cssClass, flags, pnWidth, pnHeight) || "").trim(), |
|
149
|
flags: flags |
|
150
|
}; |
|
151
|
msg.isError = !!(msg.result && msg.result.startsWith('<div')); |
|
152
|
if(msg.isError){ |
|
153
|
msg.width = msg.height = null; |
|
154
|
}else{ |
|
155
|
msg.width = pikchrModule.getValue(pnWidth, "i32"); |
|
156
|
msg.height = pikchrModule.getValue(pnHeight, "i32"); |
|
157
|
} |
|
158
|
wMsg('pikchr', msg); |
|
159
|
} finally { |
|
160
|
pikchrModule.stackRestore(stack); |
|
161
|
wMsg('working','end'); |
|
162
|
} |
|
163
|
return; |
|
164
|
}; |
|
165
|
console.warn("Unknown pikchr-worker message type:",ev); |
|
166
|
}; |
|
167
|
|
|
168
|
/** |
|
169
|
emscripten module for use with build mode -sMODULARIZE. |
|
170
|
*/ |
|
171
|
const pikchrModule = { |
|
172
|
print: function(){wMsg('stdout', Array.prototype.slice.call(arguments));}, |
|
173
|
printErr: stderr, |
|
174
|
/** |
|
175
|
Intercepts status updates from the emscripting module init |
|
176
|
and fires worker events with a type of 'status' and a |
|
177
|
payload of: |
|
178
|
|
|
179
|
{ |
|
180
|
text: string | null, // null at end of load process |
|
181
|
step: integer // starts at 1, increments 1 per call |
|
182
|
} |
|
183
|
|
|
184
|
We have no way of knowing in advance how many steps will |
|
185
|
be processed/posted, so creating a "percentage done" view is |
|
186
|
not really practical. One can be approximated by giving it a |
|
187
|
current value of message.step and max value of message.step+1, |
|
188
|
though. |
|
189
|
|
|
190
|
When work is finished, a message with a text value of null is |
|
191
|
submitted. |
|
192
|
|
|
193
|
After a message with text==null is posted, the module may later |
|
194
|
post messages about fatal problems, e.g. an exit() being |
|
195
|
triggered, so it is recommended that UI elements for posting |
|
196
|
status messages not be outright removed from the DOM when |
|
197
|
text==null, and that they instead be hidden until/unless |
|
198
|
text!=null. |
|
199
|
*/ |
|
200
|
setStatus: function f(text){ |
|
201
|
if(!f.last) f.last = { step: 0, text: '' }; |
|
202
|
else if(text === f.last.text) return; |
|
203
|
f.last.text = text; |
|
204
|
wMsg('module',{ |
|
205
|
type:'status', |
|
206
|
data:{step: ++f.last.step, text: text||null} |
|
207
|
}); |
|
208
|
} |
|
209
|
}; |
|
210
|
|
|
211
|
importScripts('pikchr-v8806526039.js'); |
|
212
|
/** |
|
213
|
initPikchrModule() is installed via pikchr.js due to |
|
214
|
building with: |
|
215
|
|
|
216
|
emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initPikchrModule |
|
217
|
*/ |
|
218
|
initPikchrModule(pikchrModule).then(function(thisModule){ |
|
219
|
//globalThis.M = pikchrModule; console.warn("pikchrModule=globalThis.M=",globalThis.M); |
|
220
|
wMsg('pikchr-ready', pikchrModule.ccall('pikchr_version','string')); |
|
221
|
}); |
|
222
|
})(); |
|
223
|
|