/* OpenTally: Open-source election vote counting * Copyright © 2021–2022 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 . */ 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; } }