Finish multi-threaded support for generating puzzles
This commit is contained in:
parent
044099379d
commit
7d4e474731
2 changed files with 88 additions and 61 deletions
|
@ -4,9 +4,10 @@ use std::error::Error;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use sudoku_solver::solver::{SolveController, SolveStatistics};
|
use sudoku_solver::solver::{SolveController, SolveStatistics};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{mpsc};
|
use std::sync::{mpsc, Arc};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability
|
We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability
|
||||||
|
@ -125,64 +126,28 @@ fn main() {
|
||||||
|
|
||||||
let solve_controller = difficulty.map_to_solve_controller();
|
let solve_controller = difficulty.map_to_solve_controller();
|
||||||
|
|
||||||
let (grid, solve_statistics, num_hints) =
|
let (result, num_attempts) =
|
||||||
if threads < 1 {
|
if threads < 1 {
|
||||||
eprintln!("--threads must be at least 1");
|
eprintln!("--threads must be at least 1");
|
||||||
exit(1);
|
exit(1);
|
||||||
} else if threads == 1 {
|
} else if threads == 1 {
|
||||||
let mut rng = SmallRng::from_entropy();
|
let mut rng = SmallRng::from_entropy();
|
||||||
let result = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints);
|
get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints, &AtomicBool::new(false))
|
||||||
match result {
|
|
||||||
Some(x) => x,
|
|
||||||
None => {
|
|
||||||
eprintln!("Unable to find an appropriate puzzle in the required amount of attempts");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let mut thread_rng = thread_rng();
|
run_multi_threaded(max_attempts, max_hints, threads, debug, solve_controller, difficulty)
|
||||||
let (transmitter, receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
for _i in 0..threads {
|
|
||||||
let cloned_transmitter = mpsc::Sender::clone(&transmitter);
|
|
||||||
let mut rng = SmallRng::from_rng(&mut thread_rng).unwrap();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
if debug {
|
|
||||||
println!("Thread spawned");
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints);
|
|
||||||
match result {
|
|
||||||
Some((grid, solve_statistics, num_hints)) => {
|
|
||||||
cloned_transmitter.send((SafeGridWrapper(grid), solve_statistics, num_hints)).unwrap();
|
|
||||||
},
|
|
||||||
None => {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if debug {
|
let (grid, solve_statistics, num_hints) = match result {
|
||||||
println!("Thread terminated");
|
Some(x) => x,
|
||||||
}
|
None => {
|
||||||
});
|
println!("Unable to find a desired puzzle in {} tries.", num_attempts);
|
||||||
}
|
return;
|
||||||
|
|
||||||
// TODO - fix bug where recv doesn't return if no Grid is found by any threads!
|
|
||||||
match receiver.recv() {
|
|
||||||
Ok((grid, solve_statistics, num_hints)) => (grid.0, solve_statistics, num_hints),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Unable to find an appropriate puzzle in the required amount of attempts");
|
|
||||||
if debug {
|
|
||||||
eprintln!("Error returned: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
println!("{}", grid);
|
println!("{}", grid);
|
||||||
println!("Puzzle has {} hints", num_hints);
|
println!("Puzzle has {} hints and was found in {} attempts.", num_hints, num_attempts);
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
println!("Solving this puzzle involves roughly:");
|
println!("Solving this puzzle involves roughly:");
|
||||||
|
@ -209,21 +174,83 @@ fn main() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, solve_controller: &SolveController, max_attempts: i32, max_hints: i32) -> Option<(Grid, SolveStatistics, i32)>{
|
fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bool, solve_controller: SolveController, difficulty: Difficulty) -> (Option<(Grid, SolveStatistics, i32)>, i32){
|
||||||
|
let mut thread_rng = thread_rng();
|
||||||
|
let (transmitter, receiver) = mpsc::channel();
|
||||||
|
let mut remaining_attempts = max_attempts;
|
||||||
|
|
||||||
|
let should_stop = AtomicBool::new(false);
|
||||||
|
let should_stop = Arc::new(should_stop);
|
||||||
|
|
||||||
|
for i in 0..threads {
|
||||||
|
let cloned_transmitter = mpsc::Sender::clone(&transmitter);
|
||||||
|
let mut rng = SmallRng::from_rng(&mut thread_rng).unwrap();
|
||||||
|
let thread_attempts = remaining_attempts / (threads - i);
|
||||||
|
remaining_attempts -= thread_attempts;
|
||||||
|
let should_stop = Arc::clone(&should_stop);
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
if debug {
|
||||||
|
println!("Thread {} spawned with {} max attempts", i, thread_attempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
let should_stop = &*should_stop;
|
||||||
|
let (result, num_attempts) = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, thread_attempts, max_hints, should_stop);
|
||||||
|
|
||||||
|
let mut result_was_some = false;
|
||||||
|
let result = match result {
|
||||||
|
None => {None}
|
||||||
|
Some((grid, solve_statistics, num_hints)) => {
|
||||||
|
result_was_some = true;
|
||||||
|
Some((SafeGridWrapper(grid), solve_statistics, num_hints))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cloned_transmitter.send((result, num_attempts)).unwrap();
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
println!("Thread {}, terminated having run {} attempts; did send result: {}", i, num_attempts, result_was_some);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut threads_running = threads;
|
||||||
|
let mut attempt_count = 0;
|
||||||
|
let mut result_to_return = None;
|
||||||
|
|
||||||
|
while threads_running > 0 {
|
||||||
|
let signal = receiver.recv().unwrap(); // Not sure what errors can result here but they are unexpected and deserve a panic
|
||||||
|
threads_running-=1;
|
||||||
|
|
||||||
|
let (result, attempts) = signal;
|
||||||
|
attempt_count += attempts;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some((safe_grid, solve_statistics, num_hints)) => {
|
||||||
|
result_to_return = Some((safe_grid.0, solve_statistics, num_hints));
|
||||||
|
should_stop.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result_to_return, attempt_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, solve_controller: &SolveController, max_attempts: i32, max_hints: i32, should_stop: &AtomicBool) -> (Option<(Grid, SolveStatistics, i32)>, i32){
|
||||||
let mut num_attempts = 0;
|
let mut num_attempts = 0;
|
||||||
|
|
||||||
loop {
|
while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed){
|
||||||
if num_attempts >= max_attempts {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller);
|
let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller);
|
||||||
num_attempts += 1;
|
num_attempts += 1;
|
||||||
|
|
||||||
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
|
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
|
||||||
return Some((grid, solve_statistics, num_hints));
|
return (Some((grid, solve_statistics, num_hints)), num_attempts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (None, num_attempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
||||||
|
|
|
@ -251,8 +251,8 @@ mod tests {
|
||||||
use crate::grid::*;
|
use crate::grid::*;
|
||||||
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
|
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
|
||||||
use crate::generator::generate_grid;
|
use crate::generator::generate_grid;
|
||||||
use rand_chacha::SmallRng;
|
use rand::prelude::SmallRng;
|
||||||
use rand_chacha::rand_core::SeedableRng;
|
use rand::SeedableRng;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unique_detection() {
|
fn test_unique_detection() {
|
||||||
|
|
Loading…
Reference in a new issue