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