Compare commits
	
		
			3 Commits
		
	
	
		
			master
			...
			3c6d69c343
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3c6d69c343 | |||
| 1ff71d099e | |||
| ae9016fbf6 | 
							
								
								
									
										11
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,7 +1,6 @@
 | 
			
		||||
# dev stage
 | 
			
		||||
FROM node:14-alpine as dev
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
RUN apk update && apk add --no-cache python3 make gcc g++
 | 
			
		||||
COPY package*.json ./
 | 
			
		||||
RUN npm ci
 | 
			
		||||
COPY . .
 | 
			
		||||
@ -14,21 +13,13 @@ RUN npx tsc && npm prune --production
 | 
			
		||||
FROM node:14-alpine
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
RUN printf "%b" '#!'"/bin/sh\n\
 | 
			
		||||
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
 | 
			
		||||
RUN apk add --update --no-cache util-linux
 | 
			
		||||
 | 
			
		||||
# Copy over production modules and dist folder
 | 
			
		||||
COPY --from=build /app/package*.json ./
 | 
			
		||||
COPY --from=build /app/node_modules ./node_modules
 | 
			
		||||
COPY --from=build /app/dist ./dist
 | 
			
		||||
COPY --from=build /app/db ./db
 | 
			
		||||
 | 
			
		||||
EXPOSE 4000
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT [ "./docker-entrypoint.sh" ]
 | 
			
		||||
CMD [ "node", "dist/main.js" ]
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import {
 | 
			
		||||
  RouterContext,
 | 
			
		||||
  graphql,
 | 
			
		||||
  makeExecutableSchema,
 | 
			
		||||
} from "./mods";
 | 
			
		||||
} from "./mods.js";
 | 
			
		||||
 | 
			
		||||
export interface ResolversProps {
 | 
			
		||||
  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 resolvers = mergeResolvers(modules.map((mod) => mod.resolvers));
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { gql } from "../mods";
 | 
			
		||||
import { generate, GenerateArguments } from "../sudoku/index";
 | 
			
		||||
import { gql } from "../mods.js";
 | 
			
		||||
import { generate, GenerateArguments } from "../sudoku/index.js";
 | 
			
		||||
 | 
			
		||||
export const typeDefs = gql`
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Application, bodyParser } from "./mods";
 | 
			
		||||
import { applyGraphQL } from "./graphql";
 | 
			
		||||
import { typeDefs, resolvers } from "./graphql/index";
 | 
			
		||||
import { Application, bodyParser } from "./mods.js";
 | 
			
		||||
import { applyGraphQL } from "./graphql.js";
 | 
			
		||||
import { typeDefs, resolvers } from "./graphql/index.js";
 | 
			
		||||
import stoppable from "stoppable";
 | 
			
		||||
 | 
			
		||||
import cors from "@koa/cors";
 | 
			
		||||
 | 
			
		||||
@ -33,18 +33,13 @@ type SolutionCallback = (output: DNode[]) => boolean;
 | 
			
		||||
type ColumnSelector = (header: CNode) => CNode;
 | 
			
		||||
 | 
			
		||||
function selectColumnSizeHeuristic(header: CNode): CNode {
 | 
			
		||||
  let minSize = Infinity;
 | 
			
		||||
  let minColumn: CNode | undefined;
 | 
			
		||||
  let curColumn = header;
 | 
			
		||||
  let minColumn = header.right;
 | 
			
		||||
  let curColumn = minColumn;
 | 
			
		||||
  while ((curColumn = curColumn.right) !== header) {
 | 
			
		||||
    if (curColumn.column.size < minSize) {
 | 
			
		||||
      minSize = curColumn.column.size;
 | 
			
		||||
    if (curColumn.size < minColumn.size) {
 | 
			
		||||
      minColumn = curColumn;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!minColumn) {
 | 
			
		||||
    throw new Error("minColumn is undefined, this shouldn't be possible.");
 | 
			
		||||
  }
 | 
			
		||||
  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 { prettyPrint } from "./util";
 | 
			
		||||
import { prettyPrint } from "./util.js";
 | 
			
		||||
 | 
			
		||||
const TIMEOUT = 20000;
 | 
			
		||||
 | 
			
		||||
export type Cell = number;
 | 
			
		||||
@ -19,46 +20,64 @@ export type Sudoku = {
 | 
			
		||||
  cells: Cell[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pool = new StaticPool<GenerateArguments, Cell[]>({
 | 
			
		||||
  size: WORKERS,
 | 
			
		||||
  task: "./src/sudoku/worker.js",
 | 
			
		||||
});
 | 
			
		||||
function getWorker() {
 | 
			
		||||
  return spawn(new Worker("./worker"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Awaits a promise with a timeout.
 | 
			
		||||
 *
 | 
			
		||||
 * @param promise the promise to await
 | 
			
		||||
 * @param ms the timeout in milliseconds
 | 
			
		||||
 * @param cb a callback to call when the timeout is reached. The promise is
 | 
			
		||||
 *           rejected with whatever gets returned here.
 | 
			
		||||
 */
 | 
			
		||||
function withTimeout<T>(promise: Promise<T>, ms: number, cb: () => any) {
 | 
			
		||||
  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(
 | 
			
		||||
  regionWidth: number,
 | 
			
		||||
  regionHeight: number,
 | 
			
		||||
  clues: number
 | 
			
		||||
): Promise<Sudoku> {
 | 
			
		||||
  if (activeWorkers >= WORKERS) {
 | 
			
		||||
    throw new Error("No workers available. Please try again in a moment.");
 | 
			
		||||
  const proxy = available.pop();
 | 
			
		||||
  if (!proxy) {
 | 
			
		||||
    throw new Error("No workers available right now. Please try again.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    activeWorkers++;
 | 
			
		||||
    const puzzle = await pool.exec(
 | 
			
		||||
      {
 | 
			
		||||
        regionWidth,
 | 
			
		||||
        regionHeight,
 | 
			
		||||
        clues,
 | 
			
		||||
      },
 | 
			
		||||
      TIMEOUT
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    prettyPrint(regionWidth, regionHeight, puzzle);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      regionWidth,
 | 
			
		||||
      regionHeight,
 | 
			
		||||
      size: (regionWidth * regionHeight) ** 2,
 | 
			
		||||
      cells: puzzle,
 | 
			
		||||
    };
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    if (isTimeoutError(err)) {
 | 
			
		||||
      throw new Error("Timed out. Try increasing the number of clues.");
 | 
			
		||||
  const puzzle = await withTimeout<number[]>(
 | 
			
		||||
    proxy.generate(regionWidth, regionHeight, clues),
 | 
			
		||||
    TIMEOUT,
 | 
			
		||||
    () => {
 | 
			
		||||
      Thread.terminate(proxy);
 | 
			
		||||
      getWorker().then((worker) => available.push(worker));
 | 
			
		||||
      return new Error("Timed out. Try reducing the number of clues.");
 | 
			
		||||
    }
 | 
			
		||||
    throw err;
 | 
			
		||||
  } finally {
 | 
			
		||||
    activeWorkers--;
 | 
			
		||||
  }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  available.push(proxy);
 | 
			
		||||
  prettyPrint(regionWidth, regionHeight, puzzle);
 | 
			
		||||
  return {
 | 
			
		||||
    regionWidth,
 | 
			
		||||
    regionHeight,
 | 
			
		||||
    size: (regionWidth * regionHeight) ** 2,
 | 
			
		||||
    cells: puzzle,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,9 @@ import {
 | 
			
		||||
  addNodeToColumn,
 | 
			
		||||
  maskRow,
 | 
			
		||||
  unmaskRow,
 | 
			
		||||
} from "./dlx";
 | 
			
		||||
import { shuffle, range } from "./util";
 | 
			
		||||
import { Cell } from "./index";
 | 
			
		||||
} from "./dlx.js";
 | 
			
		||||
import { shuffle, range } from "./util.js";
 | 
			
		||||
import { Cell } from "./index.js";
 | 
			
		||||
 | 
			
		||||
type NodeMeta = {
 | 
			
		||||
  index: number;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Cell } from "./index";
 | 
			
		||||
import { Cell } from "./index.js";
 | 
			
		||||
 | 
			
		||||
export function randInt(lower: number, upper: number) {
 | 
			
		||||
  return Math.floor(Math.random() * (upper - lower)) + lower;
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,16 @@
 | 
			
		||||
const { SudokuMath } = require("./math");
 | 
			
		||||
const { parentPort } = require("worker_threads");
 | 
			
		||||
import { SudokuMath } from "./math.js";
 | 
			
		||||
import { expose } from "threads/worker";
 | 
			
		||||
 | 
			
		||||
const maths = {};
 | 
			
		||||
 | 
			
		||||
parentPort.on("message", ({ regionWidth, regionHeight, clues }) => {
 | 
			
		||||
  const math =
 | 
			
		||||
    maths[`${regionWidth}:${regionHeight}`] ||
 | 
			
		||||
    (maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
 | 
			
		||||
      regionWidth,
 | 
			
		||||
      regionHeight
 | 
			
		||||
    ));
 | 
			
		||||
  const puzzle = math.generate(clues);
 | 
			
		||||
  parentPort.postMessage(puzzle);
 | 
			
		||||
expose({
 | 
			
		||||
  generate(regionWidth, regionHeight, clues) {
 | 
			
		||||
    const math =
 | 
			
		||||
      maths[`${regionWidth}:${regionHeight}`] ||
 | 
			
		||||
      (maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
 | 
			
		||||
        regionWidth,
 | 
			
		||||
        regionHeight
 | 
			
		||||
      ));
 | 
			
		||||
    return math.generate(clues);
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user