Compare commits
	
		
			3 Commits
		
	
	
		
			3c6d69c343
			...
			058dc13c1c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 058dc13c1c | |||
| 18e468b17f | |||
| 24d8ab6763 | 
							
								
								
									
										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,46 +20,64 @@ 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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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(
 | 
					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.pop();
 | 
				
			||||||
    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 {
 | 
					  const puzzle = await withTimeout<number[]>(
 | 
				
			||||||
    activeWorkers++;
 | 
					    proxy.generate(regionWidth, regionHeight, clues),
 | 
				
			||||||
    const puzzle = await pool.exec(
 | 
					    TIMEOUT,
 | 
				
			||||||
      {
 | 
					    () => {
 | 
				
			||||||
        regionWidth,
 | 
					      Thread.terminate(proxy);
 | 
				
			||||||
        regionHeight,
 | 
					      getWorker().then((worker) => available.push(worker));
 | 
				
			||||||
        clues,
 | 
					      return new Error("Timed out. Try reducing the number of 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.");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    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,
 | 
					  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,13 @@
 | 
				
			|||||||
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({
 | 
				
			||||||
  const math =
 | 
					  generate(regionWidth, regionHeight, clues) {
 | 
				
			||||||
    maths[`${regionWidth}:${regionHeight}`] ||
 | 
					    const key = `${regionWidth}:${regionHeight}`;
 | 
				
			||||||
    (maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
 | 
					    const math =
 | 
				
			||||||
      regionWidth,
 | 
					      maths[key] ?? (maths[key] = new SudokuMath(regionWidth, regionHeight));
 | 
				
			||||||
      regionHeight
 | 
					    return math.generate(clues);
 | 
				
			||||||
    ));
 | 
					  },
 | 
				
			||||||
  const puzzle = math.generate(clues);
 | 
					 | 
				
			||||||
  parentPort.postMessage(puzzle);
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user