Fossil SCM

fossil-scm / extsrc / pikchr-worker.js
Blame History Raw 223 lines
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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button