OpenTally/html/worker.js

163 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* OpenTally: Open-source election vote counting
* Copyright © 20212022 Lee Yingtong Li (RunasSudo)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
importScripts('opentally.js?v=GITVERSION');
var wasm = wasm_bindgen;
var wasmRaw;
// For asyncify
const DATA_ADDR = 16;
const DATA_START = DATA_ADDR + 8;
const DATA_END = 50 * 1024; // Needs to be increased compared with Asyncify default
async function initWasm() {
wasmRaw = await wasm_bindgen('opentally_async.wasm?v=GITVERSION');
new Int32Array(wasmRaw.memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]);
postMessage({'type': 'init', 'version': wasm.version()});
}
initWasm();
var reportStyle;
var numbers, election, opts, state, stageNum;
onmessage = function(evt) {
try {
if (evt.data.type === 'countElection') {
errored = false;
if (evt.data.numbers === 'fixed') {
numbers = 'Fixed';
wasm.fixed_set_dps(evt.data.decimals);
} else if (evt.data.numbers === 'gfixed') {
numbers = 'GuardedFixed';
wasm.gfixed_set_dps(evt.data.decimals);
} else if (evt.data.numbers === 'float64') {
numbers = 'NativeFloat64';
} else if (evt.data.numbers === 'rational') {
numbers = 'Rational';
} else {
throw 'Unknown --numbers';
}
reportStyle = evt.data.reportStyle;
// Init STV options
opts = wasm.STVOptions.new.apply(null, evt.data.optsStr);
// Validate options
opts.validate();
// Init election
election = wasm['election_from_blt_' + numbers](evt.data.bltData);
wasm['preprocess_election_' + numbers](election, opts);
// Init constraints if applicable
if (evt.data.conData) {
wasm['election_load_constraints_' + numbers](election, evt.data.conData, opts);
}
// Describe count
postMessage({'type': 'describeCount', 'content': wasm['describe_count_' + numbers](evt.data.bltPath, election, opts)});
// Init results table
postMessage({'type': 'initResultsTable', 'content': wasm['init_results_table_' + numbers](election, opts, reportStyle)});
// Step election
state = wasm['CountState' + numbers].new(election);
stageNum = 1;
resumeCount();
} else if (evt.data.type == 'userInput') {
userInputBuffer = evt.data.response;
// Rewind the stack
// Asyncify will retrace the function calls in the stack until again reaching get_user_input
wasmRaw.asyncify_start_rewind(DATA_ADDR);
resumeCount();
}
} catch (ex) {
if (errored) {
// Panic already logged and sent to UI
} else {
throw ex;
}
}
}
function resumeCount() {
for (;; stageNum++) {
let isDone;
if (stageNum <= 1) {
isDone = wasm['count_init_' + numbers](state, opts);
} else {
isDone = wasm['count_one_stage_' + numbers](state, opts);
}
if (wasmRaw.asyncify_get_state() !== 0) {
// This stage caused a stack unwind in get_user_input so ignore the result
// We will resume execution when a userInput message is received
return;
}
if (isDone) {
break;
}
postMessage({'type': 'updateResultsTable', 'result': wasm['update_results_table_' + numbers](stageNum, state, opts, reportStyle)});
postMessage({'type': 'updateStageComments', 'comment': wasm['update_stage_comments_' + numbers](state, stageNum), 'stageNum': stageNum});
let transfers_table = state.transfer_table_render_html(opts);
if (transfers_table) {
postMessage({'type': 'updateDetailedTransfers', 'table': transfers_table, 'stageNum': stageNum});
}
}
postMessage({'type': 'updateResultsTable', 'result': wasm['finalise_results_table_' + numbers](state, reportStyle)});
postMessage({'type': 'finalResultSummary', 'summary': wasm['final_result_summary_' + numbers](state, opts)});
}
var errored = false;
function wasm_error(message) {
postMessage({'type': 'errorMessage', 'message': message});
errored = true;
}
var userInputBuffer = null;
function get_user_input(message) {
if (userInputBuffer === null) {
postMessage({'type': 'requireInput', 'message': message});
// Record the current state of the stack
wasmRaw.asyncify_start_unwind(DATA_ADDR);
// No further WebAssembly will be executed and control will return to resumeCount
return null;
} else {
// We have reached the point the stack was originally unwound, so resume normal execution
wasmRaw.asyncify_stop_rewind();
// Return the correct result to WebAssembly
let userInput = userInputBuffer;
userInputBuffer = null;
return userInput;
}
}