163 lines
5.0 KiB
JavaScript
163 lines
5.0 KiB
JavaScript
/* 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 <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;
|
|
}
|
|
}
|