Fossil SCM

fossil-scm / extsrc / pikchr-worker.js
Source Blame History 222 lines
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 })();

Keyboard Shortcuts

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