diff --git a/package-lock.json b/package-lock.json index aa59083..d382af8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -276,7 +276,14 @@ "@types/node": { "version": "14.14.31", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", - "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==", + "dev": true + }, + "@types/physical-cpu-count": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz", + "integrity": "sha512-kSK757BMb0j/7uP8+UR2wQ7GqrF9+Zk99XvAG4K/sL1VCFEqFoKic3xRHUttsGuTR0WMC3BjOTrf6dUIU+2CmQ==", + "dev": true }, "@types/qs": { "version": "6.9.5", @@ -304,6 +311,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.0.tgz", "integrity": "sha512-BRR23Q9CJduH7AM6mk4JRttd8XyFkb4qIPZu4mdLF+VoP+wcjIxIWIKiBbN78NBbEuynrAyMPtzOHnIp2B/JPQ==", + "dev": true, "requires": { "@types/node": "*" } @@ -1336,6 +1344,11 @@ "tslib": "^2.0.3" } }, + "node-worker-threads-pool": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/node-worker-threads-pool/-/node-worker-threads-pool-1.4.3.tgz", + "integrity": "sha512-US55ZGzEDQY2oq8Bc33dFVNKGpx4KaCJqThMDomSsUeX8tMdp2eDjQ6OP0yFd1HTEuHuLqxXSTWC4eidEsbXlg==" + }, "nodemon": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", @@ -1450,6 +1463,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz", "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==" }, + "physical-cpu-count": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz", + "integrity": "sha1-GN4vl+S/epVRrXURlCtUlverpmA=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", diff --git a/package.json b/package.json index 0e145cf..3d83823 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "koa": "^2.13.1", "koa-bodyparser": "^4.3.0", "koa-router": "^9.4.0", + "node-worker-threads-pool": "^1.4.3", + "physical-cpu-count": "^2.0.0", "stoppable": "^1.1.0" }, "devDependencies": { @@ -20,6 +22,7 @@ "@types/koa-bodyparser": "^4.3.0", "@types/koa-router": "^7.4.1", "@types/markdown-it": "^10.0.3", + "@types/physical-cpu-count": "^2.0.0", "@types/stoppable": "^1.1.0", "nodemon": "^2.0.7", "ts-node": "^8.10.2", diff --git a/src/graphql/sudoku.ts b/src/graphql/sudoku.ts index a0258b4..3dbee07 100644 --- a/src/graphql/sudoku.ts +++ b/src/graphql/sudoku.ts @@ -1,5 +1,5 @@ import { gql } from "../mods"; -import { generate } from "../sudoku/index"; +import { generate, GenerateArguments } from "../sudoku/index"; export const typeDefs = gql` type Cell { @@ -37,12 +37,6 @@ export const typeDefs = gql` } `; -type GenerateArguments = { - regionWidth: number; - regionHeight: number; - clues: number; -}; - export const resolvers = { Query: { generate: ( diff --git a/src/sudoku/index.ts b/src/sudoku/index.ts index 793c711..19d3807 100644 --- a/src/sudoku/index.ts +++ b/src/sudoku/index.ts @@ -1,4 +1,8 @@ import { SudokuMath } from "./math"; +import { StaticPool, isTimeoutError } from "node-worker-threads-pool"; + +import WORKERS from "physical-cpu-count"; +const TIMEOUT = 5000; export type Cell = { value: number | null }; export type CellArray = Cell[]; @@ -7,6 +11,12 @@ export type Column = CellArray; export type Region = CellArray; export type Puzzle = CellArray; +export type GenerateArguments = { + regionWidth: number; + regionHeight: number; + clues: number; +}; + export type Sudoku = { regionWidth: number; regionHeight: number; @@ -17,21 +27,51 @@ export type Sudoku = { regions: () => Region[]; }; -export function generate( +const pool = new StaticPool({ + size: WORKERS, + task: "./src/sudoku/worker.js", +}); + +const _math = new SudokuMath(0, 0); +let activeWorkers = 0; + +export async function generate( regionWidth: number, regionHeight: number, clues: number -): Sudoku { - const math = new SudokuMath(regionWidth, regionHeight); - const puzzle = math.generatePuzzle(clues); +): Promise { + if (activeWorkers >= WORKERS) { + throw new Error("No workers available. Please try again in a moment."); + } - return { - regionWidth, - regionHeight, - cells: math.boardCells, - raw: puzzle, - rows: () => math.regionsToRows(puzzle, true), - columns: () => math.regionsToCols(puzzle, true), - regions: () => math.chunkRegions(puzzle), - }; + try { + activeWorkers++; + const [math, puzzle] = await pool.exec( + { + regionWidth, + regionHeight, + clues, + }, + TIMEOUT + ); + + Object.assign(_math, math); + + return { + regionWidth, + regionHeight, + cells: math.boardCells, + raw: puzzle, + rows: () => _math.regionsToRows(puzzle, true), + columns: () => _math.regionsToCols(puzzle, true), + regions: () => _math.chunkRegions(puzzle), + }; + } catch (err) { + if (isTimeoutError(err)) { + throw new Error("Timed out. Try increasing the number of clues."); + } + throw err; + } finally { + activeWorkers--; + } } diff --git a/src/sudoku/math.ts b/src/sudoku/math.ts index 12f06e2..ba78c32 100644 --- a/src/sudoku/math.ts +++ b/src/sudoku/math.ts @@ -227,7 +227,6 @@ export class SudokuMath { } if (fails > removeNext.length) { fails = 0; - console.log("Backstepping.."); Array(removed.length) .fill(null) .forEach(() => replace()); diff --git a/src/sudoku/worker.js b/src/sudoku/worker.js new file mode 100644 index 0000000..3ad5614 --- /dev/null +++ b/src/sudoku/worker.js @@ -0,0 +1,8 @@ +const { SudokuMath } = require("./math"); +const { parentPort } = require("worker_threads"); + +parentPort.on("message", ({ regionWidth, regionHeight, clues }) => { + const math = new SudokuMath(regionWidth, regionHeight); + const puzzle = math.generatePuzzle(clues); + parentPort.postMessage([math, puzzle]); +}); diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index e69de29..0000000