Use Asyncify to process ties in web UI
This commit is contained in:
parent
a64110b6a1
commit
a5a61731b5
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
/target
|
||||
/html/opentally.js
|
||||
/html/opentally_bg.wasm
|
||||
/html/opentally_*.wasm
|
||||
|
@ -1,3 +1,19 @@
|
||||
#!/bin/sh
|
||||
PATH=$PATH:$HOME/.cargo/bin
|
||||
|
||||
# Build cargo
|
||||
PROFILE=${1:-release}
|
||||
cargo build --lib --target wasm32-unknown-unknown --$PROFILE && /home/runassudo/.cargo/bin/wasm-bindgen --target no-modules target/wasm32-unknown-unknown/$PROFILE/opentally.wasm --out-dir html --no-typescript
|
||||
if [ $PROFILE == 'debug' ]; then
|
||||
cargo build --lib --target wasm32-unknown-unknown
|
||||
else
|
||||
cargo build --lib --target wasm32-unknown-unknown --$PROFILE
|
||||
fi
|
||||
|
||||
# Apply wasm-bindgen
|
||||
wasm-bindgen --target no-modules target/wasm32-unknown-unknown/$PROFILE/opentally.wasm --out-dir html --no-typescript
|
||||
|
||||
# Apply Asyncify
|
||||
MANGLED=$(wasm-dis html/opentally_bg.wasm | grep '(import "wbg" "__wbg_getuserinput_' | awk '{print $3;}' | tr -d '"')
|
||||
wasm-opt -O2 --asyncify --pass-arg asyncify-imports@wbg.$MANGLED html/opentally_bg.wasm -o html/opentally_async.wasm
|
||||
|
||||
rm html/opentally_bg.wasm
|
||||
|
@ -295,7 +295,6 @@
|
||||
<div id="printWarning">Printing directly from this page is not supported. Use the ‘Print result’ button to generate a printer-friendly report.</div>
|
||||
|
||||
<script src="vendor/vanilla-js-dropdown.min.js"></script>
|
||||
<script src="opentally.js?v=GITVERSION"></script>
|
||||
<script src="index.js?v=GITVERSION"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,9 +1,18 @@
|
||||
importScripts('opentally.js');
|
||||
|
||||
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() {
|
||||
await wasm_bindgen('opentally_bg.wasm');
|
||||
wasmRaw = await wasm_bindgen('opentally_async.wasm');
|
||||
|
||||
new Int32Array(wasmRaw.memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]);
|
||||
|
||||
postMessage({'type': 'init', 'version': wasm.version()});
|
||||
}
|
||||
initWasm();
|
||||
@ -59,27 +68,30 @@ onmessage = function(evt) {
|
||||
|
||||
stageNum = 2;
|
||||
|
||||
resume_count();
|
||||
resumeCount();
|
||||
|
||||
} else if (evt.data.type == 'userInput') {
|
||||
user_input_buffer = evt.data.response;
|
||||
resume_count();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function resume_count() {
|
||||
function resumeCount() {
|
||||
for (;; stageNum++) {
|
||||
try {
|
||||
let isDone = wasm['count_one_stage_' + numbers](state, opts);
|
||||
if (isDone) {
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
if (ex === "RequireInput") {
|
||||
return;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
let isDone = wasm['count_one_stage_' + numbers](state, opts);
|
||||
|
||||
if (unwindingStack) {
|
||||
// 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)});
|
||||
@ -90,15 +102,27 @@ function resume_count() {
|
||||
postMessage({'type': 'finalResultSummary', 'summary': wasm['final_result_summary_' + numbers](state, opts)});
|
||||
}
|
||||
|
||||
var user_input_buffer = null;
|
||||
var unwindingStack = false;
|
||||
var userInputBuffer = null;
|
||||
|
||||
function read_user_input_buffer(message) {
|
||||
if (user_input_buffer === 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);
|
||||
unwindingStack = true;
|
||||
|
||||
// No further WebAssembly will be executed and control will return to resumeCount
|
||||
return null;
|
||||
} else {
|
||||
let user_input = user_input_buffer;
|
||||
user_input_buffer = null;
|
||||
return user_input;
|
||||
// We have reached the point the stack was originally unwound, so resume normal execution
|
||||
unwindingStack = false;
|
||||
wasmRaw.asyncify_stop_rewind();
|
||||
|
||||
// Return the correct result to WebAssembly
|
||||
let userInput = userInputBuffer;
|
||||
userInputBuffer = null;
|
||||
return userInput;
|
||||
}
|
||||
}
|
||||
|
40
src/ties.rs
40
src/ties.rs
@ -198,7 +198,6 @@ where
|
||||
}
|
||||
|
||||
/// Prompt the candidate for input, depending on CLI or WebAssembly target
|
||||
// FIXME: This may have unexpected behaviour if the tie occurs in the middle of a stage
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> {
|
||||
println!("Multiple tied candidates:");
|
||||
@ -231,7 +230,7 @@ fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
fn read_user_input_buffer(s: &str) -> Option<String>;
|
||||
fn get_user_input(s: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@ -242,25 +241,30 @@ fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError
|
||||
}
|
||||
message.push_str(&format!("Which candidate to select? [1-{}] ", candidates.len()));
|
||||
|
||||
match read_user_input_buffer(&message) {
|
||||
Some(response) => {
|
||||
match response.trim().parse::<usize>() {
|
||||
Ok(val) => {
|
||||
if val >= 1 && val <= candidates.len() {
|
||||
return Ok(candidates[val - 1]);
|
||||
} else {
|
||||
let _ = read_user_input_buffer(&message);
|
||||
return Err(STVError::RequireInput);
|
||||
loop {
|
||||
let response = get_user_input(&message);
|
||||
|
||||
match response {
|
||||
Some(response) => {
|
||||
match response.trim().parse::<usize>() {
|
||||
Ok(val) => {
|
||||
if val >= 1 && val <= candidates.len() {
|
||||
return Ok(candidates[val - 1]);
|
||||
} else {
|
||||
// Invalid selection
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Invalid selection
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = read_user_input_buffer(&message);
|
||||
return Err(STVError::RequireInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(STVError::RequireInput);
|
||||
None => {
|
||||
// No available user input in buffer - stack will be unwound
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user