Compare commits
4 Commits
3c6d69c343
...
5ccbb2ef1c
Author | SHA1 | Date | |
---|---|---|---|
5ccbb2ef1c | |||
da1405f262 | |||
2f8801bae6 | |||
67ec0d5430 |
11
Dockerfile
11
Dockerfile
@ -1,7 +1,6 @@
|
|||||||
# dev stage
|
# dev stage
|
||||||
FROM node:14-alpine as dev
|
FROM node:14-alpine as dev
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apk update && apk add --no-cache python3 make gcc g++
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
@ -14,21 +13,13 @@ RUN npx tsc && npm prune --production
|
|||||||
FROM node:14-alpine
|
FROM node:14-alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN printf "%b" '#!'"/bin/sh\n\
|
RUN apk add --update --no-cache util-linux
|
||||||
set -e\n\
|
|
||||||
if [ ! -z \"\$RUN_MIGRATIONS\" ]; then\n\
|
|
||||||
echo \"Running migrations.\"\n\
|
|
||||||
npm run knex:migrate:latest\n\
|
|
||||||
fi\n\
|
|
||||||
exec \"\$@\"\n" > docker-entrypoint.sh && chmod +x docker-entrypoint.sh
|
|
||||||
|
|
||||||
# Copy over production modules and dist folder
|
# Copy over production modules and dist folder
|
||||||
COPY --from=build /app/package*.json ./
|
COPY --from=build /app/package*.json ./
|
||||||
COPY --from=build /app/node_modules ./node_modules
|
COPY --from=build /app/node_modules ./node_modules
|
||||||
COPY --from=build /app/dist ./dist
|
COPY --from=build /app/dist ./dist
|
||||||
COPY --from=build /app/db ./db
|
|
||||||
|
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
|
|
||||||
ENTRYPOINT [ "./docker-entrypoint.sh" ]
|
|
||||||
CMD [ "node", "dist/main.js" ]
|
CMD [ "node", "dist/main.js" ]
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
RouterContext,
|
RouterContext,
|
||||||
graphql,
|
graphql,
|
||||||
makeExecutableSchema,
|
makeExecutableSchema,
|
||||||
} from "./mods";
|
} from "./mods.js";
|
||||||
|
|
||||||
export interface ResolversProps {
|
export interface ResolversProps {
|
||||||
Query?: any;
|
Query?: any;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { mergeTypeDefs, mergeResolvers } from "../mods";
|
import { mergeTypeDefs, mergeResolvers } from "../mods.js";
|
||||||
|
import * as sudoku from "./sudoku.js";
|
||||||
|
|
||||||
const modules = [require("./sudoku")];
|
const modules = [sudoku];
|
||||||
|
|
||||||
export const typeDefs = mergeTypeDefs(modules.map((mod) => mod.typeDefs));
|
export const typeDefs = mergeTypeDefs(modules.map((mod) => mod.typeDefs));
|
||||||
export const resolvers = mergeResolvers(modules.map((mod) => mod.resolvers));
|
export const resolvers = mergeResolvers(modules.map((mod) => mod.resolvers));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { gql } from "../mods";
|
import { gql } from "../mods.js";
|
||||||
import { generate, GenerateArguments } from "../sudoku/index";
|
import { generate, GenerateArguments } from "../sudoku/index.js";
|
||||||
|
|
||||||
export const typeDefs = gql`
|
export const typeDefs = gql`
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Application, bodyParser } from "./mods";
|
import { Application, bodyParser } from "./mods.js";
|
||||||
import { applyGraphQL } from "./graphql";
|
import { applyGraphQL } from "./graphql.js";
|
||||||
import { typeDefs, resolvers } from "./graphql/index";
|
import { typeDefs, resolvers } from "./graphql/index.js";
|
||||||
import stoppable from "stoppable";
|
import stoppable from "stoppable";
|
||||||
|
|
||||||
import cors from "@koa/cors";
|
import cors from "@koa/cors";
|
||||||
|
@ -33,18 +33,13 @@ type SolutionCallback = (output: DNode[]) => boolean;
|
|||||||
type ColumnSelector = (header: CNode) => CNode;
|
type ColumnSelector = (header: CNode) => CNode;
|
||||||
|
|
||||||
function selectColumnSizeHeuristic(header: CNode): CNode {
|
function selectColumnSizeHeuristic(header: CNode): CNode {
|
||||||
let minSize = Infinity;
|
let minColumn = header.right;
|
||||||
let minColumn: CNode | undefined;
|
let curColumn = minColumn;
|
||||||
let curColumn = header;
|
|
||||||
while ((curColumn = curColumn.right) !== header) {
|
while ((curColumn = curColumn.right) !== header) {
|
||||||
if (curColumn.column.size < minSize) {
|
if (curColumn.size < minColumn.size) {
|
||||||
minSize = curColumn.column.size;
|
|
||||||
minColumn = curColumn;
|
minColumn = curColumn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!minColumn) {
|
|
||||||
throw new Error("minColumn is undefined, this shouldn't be possible.");
|
|
||||||
}
|
|
||||||
return minColumn;
|
return minColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { StaticPool, isTimeoutError } from "node-worker-threads-pool";
|
import { spawn, Thread, Worker } from "threads";
|
||||||
|
|
||||||
import WORKERS from "physical-cpu-count";
|
import WORKERS from "physical-cpu-count";
|
||||||
import { prettyPrint } from "./util";
|
import { prettyPrint } from "./util.js";
|
||||||
|
|
||||||
const TIMEOUT = 20000;
|
const TIMEOUT = 20000;
|
||||||
|
|
||||||
export type Cell = number;
|
export type Cell = number;
|
||||||
@ -19,34 +20,53 @@ export type Sudoku = {
|
|||||||
cells: Cell[];
|
cells: Cell[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const pool = new StaticPool<GenerateArguments, Cell[]>({
|
function getWorker() {
|
||||||
size: WORKERS,
|
return spawn(new Worker("./worker"));
|
||||||
task: "./src/sudoku/worker.js",
|
}
|
||||||
});
|
|
||||||
|
const available: any = [];
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
console.log(`Starting ${WORKERS} worker threads`);
|
||||||
|
for (let n = 0; n < WORKERS; n++) {
|
||||||
|
getWorker().then((worker) => available.push(worker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
function withTimeout<T>(promise: Promise<T>, ms: number, cb: () => Error) {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
reject(cb());
|
||||||
|
}, ms);
|
||||||
|
promise.then(resolve).catch(reject);
|
||||||
|
}).finally(() => clearTimeout(timeout!));
|
||||||
|
}
|
||||||
|
|
||||||
let activeWorkers = 0;
|
|
||||||
export async function generate(
|
export async function generate(
|
||||||
regionWidth: number,
|
regionWidth: number,
|
||||||
regionHeight: number,
|
regionHeight: number,
|
||||||
clues: number
|
clues: number
|
||||||
): Promise<Sudoku> {
|
): Promise<Sudoku> {
|
||||||
if (activeWorkers >= WORKERS) {
|
const proxy = available.shift();
|
||||||
throw new Error("No workers available. Please try again in a moment.");
|
if (!proxy) {
|
||||||
|
throw new Error("No workers available right now. Please try again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
activeWorkers++;
|
const puzzle: number[] = await withTimeout(
|
||||||
const puzzle = await pool.exec(
|
proxy.generate(regionWidth, regionHeight, clues),
|
||||||
{
|
TIMEOUT,
|
||||||
regionWidth,
|
() => {
|
||||||
regionHeight,
|
Thread.terminate(proxy);
|
||||||
clues,
|
getWorker().then((worker) => available.push(worker));
|
||||||
},
|
return new Error("Timed out. Try reducing the number of clues.");
|
||||||
TIMEOUT
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
available.unshift(proxy);
|
||||||
prettyPrint(regionWidth, regionHeight, puzzle);
|
prettyPrint(regionWidth, regionHeight, puzzle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
regionWidth,
|
regionWidth,
|
||||||
regionHeight,
|
regionHeight,
|
||||||
@ -54,11 +74,6 @@ export async function generate(
|
|||||||
cells: puzzle,
|
cells: puzzle,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isTimeoutError(err)) {
|
|
||||||
throw new Error("Timed out. Try increasing the number of clues.");
|
|
||||||
}
|
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
|
||||||
activeWorkers--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import {
|
|||||||
addNodeToColumn,
|
addNodeToColumn,
|
||||||
maskRow,
|
maskRow,
|
||||||
unmaskRow,
|
unmaskRow,
|
||||||
} from "./dlx";
|
} from "./dlx.js";
|
||||||
import { shuffle, range } from "./util";
|
import { shuffle, range } from "./util.js";
|
||||||
import { Cell } from "./index";
|
import { Cell } from "./index.js";
|
||||||
|
|
||||||
type NodeMeta = {
|
type NodeMeta = {
|
||||||
index: number;
|
index: number;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Cell } from "./index";
|
import { Cell } from "./index.js";
|
||||||
|
|
||||||
export function randInt(lower: number, upper: number) {
|
export function randInt(lower: number, upper: number) {
|
||||||
return Math.floor(Math.random() * (upper - lower)) + lower;
|
return Math.floor(Math.random() * (upper - lower)) + lower;
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
const { SudokuMath } = require("./math");
|
import { SudokuMath } from "./math.js";
|
||||||
const { parentPort } = require("worker_threads");
|
import { expose } from "threads/worker";
|
||||||
|
|
||||||
const maths = {};
|
const maths = {};
|
||||||
|
|
||||||
parentPort.on("message", ({ regionWidth, regionHeight, clues }) => {
|
expose({
|
||||||
|
generate(regionWidth, regionHeight, clues) {
|
||||||
const math =
|
const math =
|
||||||
maths[`${regionWidth}:${regionHeight}`] ||
|
maths[`${regionWidth}:${regionHeight}`] ||
|
||||||
(maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
|
(maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
|
||||||
regionWidth,
|
regionWidth,
|
||||||
regionHeight
|
regionHeight
|
||||||
));
|
));
|
||||||
const puzzle = math.generate(clues);
|
return math.generate(clues);
|
||||||
parentPort.postMessage(puzzle);
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user