From cda119022ff0c7e326a06e320b55a9e844fa6fb2 Mon Sep 17 00:00:00 2001 From: wirelyre <wirelyre@gmail.com> Date: Sat, 13 Nov 2021 19:28:55 -0500 Subject: [PATCH] [PC solver] Add progress bar --- gomen/pc-solver.html | 27 +++++++++++++++++++++ gomen/worker.js | 11 +++++++++ solver/src/lib.rs | 5 ++++ solver/src/solver.rs | 57 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/gomen/pc-solver.html b/gomen/pc-solver.html index dbcb851..6ed45e3 100644 --- a/gomen/pc-solver.html +++ b/gomen/pc-solver.html @@ -173,6 +173,8 @@ <h1>PC solver</h1> <label><input id="hold" type="checkbox" checked> Use hold</label> </div> + <progress id="progress" style="grid-column: span 2;"></progress> + </div> <div id="loading">Loading solver...</div> @@ -187,7 +189,9 @@ <h1>PC solver</h1> let queue = document.getElementById("queue"); let queueErrors = document.getElementById("queue-errors"); let hold = document.getElementById("hold"); + let progress = document.getElementById("progress"); let solutions = document.getElementById("solutions"); + let progressTimeout = window.setTimeout(() => { }, 0); worker.onmessage = message => { if (message.data.kind == "ready") { @@ -195,6 +199,8 @@ <h1>PC solver</h1> if (work != null) { worker.postMessage(work); + } else { + hideProgress(); } return; @@ -205,20 +211,28 @@ <h1>PC solver</h1> initialErrors.innerText = "inital field has no solutions or is unreachable"; solutions.innerHTML = ""; solutions.classList.remove("loading"); + hideProgress(); return; } else if (message.data.kind == "possible" && message.data.query.garbage == getGarbage()[0]) { initialErrors.innerText = ""; return; } + if (message.data.kind == "progress") { + progress.value = message.data.amount; + return; + } + if (message.data.query.queue != work.queue || message.data.query.garbage != work.garbage || message.data.query.hold != work.hold || message.data.query.count != work.count) { worker.postMessage(work); + showProgress(); return; } else { work = null; + hideProgress(); solutions.innerHTML = ""; solutions.classList.remove("loading"); } @@ -234,6 +248,18 @@ <h1>PC solver</h1> } } + function hideProgress() { + window.clearTimeout(progressTimeout); + progress.style.display = "none"; + } + function showProgress() { + progress.removeAttribute("value"); + window.clearTimeout(progressTimeout); + progressTimeout = window.setTimeout(() => { + progress.style.display = ""; + }, 1000); + } + function showSolutions(solns, count) { if (solns.length == 0) { solutions.innerText = "no solutions"; @@ -310,6 +336,7 @@ <h1>PC solver</h1> solutions.classList.add("loading"); work = { queue: queue.value, garbage, hold: hold.checked }; worker.postMessage(work); + showProgress(); } else { work = { queue: queue.value, garbage, hold: hold.checked }; } diff --git a/gomen/worker.js b/gomen/worker.js index ad5f583..ec5f360 100644 --- a/gomen/worker.js +++ b/gomen/worker.js @@ -1,5 +1,12 @@ importScripts("./pkg/solver.js"); +function progress(piece_count, stage, board_idx, board_total) { + let stage_progress = board_idx / board_total; + let total_progress = (stage + stage_progress) / (2 + 2 * piece_count); + + postMessage({ kind: "progress", amount: total_progress }); +} + async function main() { await wasm_bindgen("./pkg/solver_bg.wasm"); let solver = new wasm_bindgen.Solver(); @@ -34,6 +41,10 @@ async function main() { let solutions = solver.solve(queue, query.garbage, query.hold).split(","); + if (solutions[0] == "") { + solutions = []; + } + postMessage({ kind: "ok", query, solutions }); } } diff --git a/solver/src/lib.rs b/solver/src/lib.rs index 6161113..ea8ea9a 100644 --- a/solver/src/lib.rs +++ b/solver/src/lib.rs @@ -88,3 +88,8 @@ fn parse_shape(shape: char) -> Option<Shape> { _ => None, } } + +#[wasm_bindgen] +extern "C" { + pub fn progress(piece_count: usize, stage: usize, board_idx: usize, board_total: usize); +} diff --git a/solver/src/solver.rs b/solver/src/solver.rs index 3cb0002..4169dfe 100644 --- a/solver/src/solver.rs +++ b/solver/src/solver.rs @@ -18,6 +18,7 @@ fn scan( legal_boards: &HashSet<Board>, start: Board, bags: &[Bag], + piece_count: usize, can_hold: bool, place_last: bool, ) -> Vec<ScanStage> { @@ -26,14 +27,19 @@ fn scan( let mut prev: ScanStage = HashMap::new(); prev.insert(start, (bags.first().unwrap().init_hold(), SmallVec::new())); - for (bag, i) in bags + for (stage, (bag, i)) in bags .iter() .flat_map(|b| (0..b.count).into_iter().map(move |i| (b, i))) .skip(1) + .enumerate() { let mut next: ScanStage = HashMap::new(); - for (&old_board, (old_queues, _preds)) in prev.iter() { + for (board_idx, (&old_board, (old_queues, _preds))) in prev.iter().enumerate() { + if board_idx % 4096 == 0 { + crate::progress(piece_count, stage, board_idx, prev.len()); + } + for shape in Shape::ALL { let is_first = i == 0; let new_queues = bag.take(old_queues, shape, is_first, can_hold); @@ -67,7 +73,11 @@ fn scan( if place_last { let mut next: ScanStage = HashMap::new(); - for (&old_board, (old_queues, _preds)) in prev.iter() { + for (board_idx, (&old_board, (old_queues, _preds))) in prev.iter().enumerate() { + if board_idx % 4096 == 0 { + crate::progress(piece_count, piece_count, board_idx, prev.len()); + } + for shape in Shape::ALL { if old_queues.iter().any(|queue| queue.hold() == Some(shape)) { for (_, new_board) in PiecePlacer::new(old_board, shape) { @@ -88,6 +98,8 @@ fn scan( prev = next; } + crate::progress(piece_count, piece_count, 1, 1); + stages.push(prev); stages } @@ -118,20 +130,26 @@ fn place( culled: &HashSet<Board>, start: BrokenBoard, bags: &[Bag], + piece_count: usize, can_hold: bool, place_last: bool, ) -> HashMap<BrokenBoard, SmallVec<[QueueState; 7]>> { let mut prev = HashMap::new(); prev.insert(start, bags.first().unwrap().init_hold()); - for (bag, i) in bags + for (stage, (bag, i)) in bags .iter() .flat_map(|b| (0..b.count).into_iter().map(move |i| (b, i))) .skip(1) + .enumerate() { let mut next: HashMap<BrokenBoard, SmallVec<[QueueState; 7]>> = HashMap::new(); - for (old_board, old_queues) in prev.iter() { + for (board_idx, (old_board, old_queues)) in prev.iter().enumerate() { + if board_idx % 4096 == 0 { + crate::progress(piece_count, piece_count + 1 + stage, board_idx, prev.len()); + } + for shape in Shape::ALL { let is_first = i == 0; let new_queues = bag.take(old_queues, shape, is_first, can_hold); @@ -159,7 +177,11 @@ fn place( if place_last { let mut next: HashMap<BrokenBoard, SmallVec<[QueueState; 7]>> = HashMap::new(); - for (old_board, old_queues) in prev.iter() { + for (board_idx, (old_board, old_queues)) in prev.iter().enumerate() { + if board_idx % 4096 == 0 { + crate::progress(piece_count, 2 * piece_count + 1, board_idx, prev.len()); + } + for shape in Shape::ALL { if old_queues.iter().any(|queue| queue.hold() == Some(shape)) { for (piece, new_board) in PiecePlacer::new(old_board.board, shape) { @@ -174,6 +196,8 @@ fn place( prev = next; } + crate::progress(piece_count, 2 * piece_count + 1, 1, 1); + prev } @@ -187,12 +211,27 @@ pub fn compute( return vec![start.clone()]; } - let new_mino_count: u32 = bags.iter().map(|b| b.count as u32 * 4).sum(); + let piece_count = bags.iter().map(|b| b.count as usize).sum(); + let new_mino_count = piece_count as u32 * 4; let place_last = start.board.0.count_ones() + new_mino_count <= 40; - let scanned = scan(legal_boards, start.board, bags, can_hold, place_last); + let scanned = scan( + legal_boards, + start.board, + bags, + piece_count, + can_hold, + place_last, + ); let culled = cull(&scanned); - let mut placed = place(&culled, start.clone(), bags, can_hold, place_last); + let mut placed = place( + &culled, + start.clone(), + bags, + piece_count, + can_hold, + place_last, + ); let mut solutions: Vec<BrokenBoard> = placed.drain().map(|(board, _queue_states)| board).collect();