Skip to content

Commit

Permalink
Move the computation loop into a worker
Browse files Browse the repository at this point in the history
  • Loading branch information
Jumbub committed Mar 27, 2022
1 parent fa2364a commit e67e46a
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 87 deletions.
4 changes: 2 additions & 2 deletions src/benchmark/bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const bench = (
const valid = rps > rendersPerSecond * 0.999 ? '' : '*TOO FEW RENDERS TO BE CONSIDERED VALID*';

const report = `seconds: ${seconds.toFixed(2)}s
generations: ${meta.generations}
generations/second: ${(meta.generations / seconds).toFixed(2)}
generations: ${meta.generationsAndMax[0]}
generations/second: ${(meta.generationsAndMax[0] / seconds).toFixed(2)}
renders/second: ${rps.toFixed(2)}
${valid}`;

Expand Down
12 changes: 7 additions & 5 deletions src/common/load.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { Board, DONT_SKIP } from '../logic/board.js';
import { Board, DONT_SKIP, getBoardIo } from '../logic/board.js';

export const load = (board: Board, data: string) => {
if (data.length !== board.width * board.height) throw new Error('Miss-match width, height for data loader');
const { input, output, inSkips, outSkips } = getBoardIo(board);

for (let i = 0; i < data.length; i++) {
board.input[i] = board.output[i] = parseInt(data[i]);
input[i] = output[i] = parseInt(data[i]);
}

board.inSkip.fill(DONT_SKIP);
board.outSkip.fill(DONT_SKIP);
inSkips.fill(DONT_SKIP);
outSkips.fill(DONT_SKIP);
};

export const match = (board: Board, data: string) => {
if (data.length !== board.width * board.height) throw new Error('Miss-match width, height for data matcher');
const { output } = getBoardIo(board);

return board.output.every((value, i) => value == parseInt(data[i]));
return output.every((value, i) => value == parseInt(data[i]));
};

export const random = (size: number) => {
Expand Down
33 changes: 16 additions & 17 deletions src/graphics/loop.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Board, newBoard } from '../logic/board.js';
import { next, startNextBoardLoop } from '../logic/next.js';
import { next } from '../logic/next.js';
import { GoMessage } from '../logic/primaryWorker.js';
import { newContext } from './context.js';
import { handleMouse } from './mouse.js';
import { render } from './render.js';

export type Meta = {
board: Board;
context: CanvasRenderingContext2D;
workers: Worker[];
maxGenerations: number;
generations: number;
primaryWorker: Worker;
generationsAndMax: Uint32Array; // [computations, maxGenerations]
renders: number;
rendersMinimumMilliseconds: number;
onDone: (meta: Meta) => void;
Expand All @@ -24,24 +24,20 @@ export const setup = (
): Meta => {
const board = newBoard(viewWidth, viewHeight);
const context = newContext(viewWidth, viewHeight);
const workers = Array(1)
.fill(0)
.map((_, i) => new Worker('/logic/worker.js', { name: 'worker', type: 'module' }));
const primaryWorker = new Worker('/logic/primaryWorker.js', { name: 'worker-primary', type: 'module' });
const generationsAndMax = new Uint32Array(new SharedArrayBuffer(8));
generationsAndMax[0] = 0;
generationsAndMax[1] = maxGenerations === Infinity ? Math.pow(2, 32) : maxGenerations;
const meta: Meta = {
board,
context,
maxGenerations,
generationsAndMax,
rendersMinimumMilliseconds,
onDone,
generations: 0,
renders: 0,
workers,
primaryWorker,
};

window.addEventListener('blur', () => {
meta.maxGenerations = 0;
}); // prevent unecessary cpu spam

handleMouse(board);

return meta;
Expand All @@ -51,16 +47,19 @@ export const run = (meta: Meta) => {
const renderLambda = () => render(meta.board, meta.context);

const interval = setInterval(() => {
document.title = String(meta.generations);
const [generations, maxGenerations] = meta.generationsAndMax;

document.title = String(generations);
requestAnimationFrame(renderLambda);
meta.renders++;

if (meta.generations >= meta.maxGenerations) {
if (generations >= maxGenerations) {
clearInterval(interval);
meta.onDone(meta);
return;
}
}, meta.rendersMinimumMilliseconds);

startNextBoardLoop(meta);
const go: GoMessage = { ...meta.board, generationsAndMax: meta.generationsAndMax };
meta.primaryWorker.postMessage(go);
};
7 changes: 4 additions & 3 deletions src/graphics/mouse.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Board, DONT_SKIP } from '../logic/board.js';
import { Board, DONT_SKIP, getBoardIo } from '../logic/board.js';

export const handleMouse = (board: Board) => {
const { output, outSkips } = getBoardIo(board);
let mouseDown = false;
const draw = ({ x, y }: { x: number; y: number }) => {
const RADIUS = 15;
for (let yo = y - RADIUS; yo < y + RADIUS; yo++) {
for (let xo = x - RADIUS; xo < x + RADIUS; xo++) {
const i = yo * board.width + xo;
board.output[i] = Math.round(Math.random());
board.outSkip[i] = DONT_SKIP;
output[i] = Math.round(Math.random());
outSkips[i] = DONT_SKIP;
}
}
};
Expand Down
4 changes: 3 additions & 1 deletion src/graphics/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ export const DEAD_COLOR_32 = 16777215;
const COLOR_DIFF = ALIVE_COLOR_32 - DEAD_COLOR_32;

export const render = (board: Board, context: CanvasRenderingContext2D) => {
const { width, height, output } = board;
const { width, height } = board;

const image = new ImageData(width, height);
const image32 = new Uint32Array(image.data.buffer);
const size = width * height;

const output = board.cells[1 - board.cellsInput[0]];

for (let i = 0; i < size; i++) {
image32[i] = DEAD_COLOR_32 + COLOR_DIFF * output[i];
}
Expand Down
36 changes: 24 additions & 12 deletions src/logic/board.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { littleEndian } from './endianness.js';

export type Board = {
input: Uint8Array;
output: Uint8Array;
cells: [Cells, Cells];
skips: [Skips, Skips];

inSkip: Skips;
outSkip: Skips;
cellsInput: Uint32Array; // [index of cells]
skipsInput: Uint32Array; // [index of skips]

width: number;
height: number;
Expand Down Expand Up @@ -38,19 +38,31 @@ export const newBoard = (viewWidth: number, viewHeight: number) => {
const width = viewWidth + 2;
const height = viewHeight + 2;

const makeZeros = () => {
const buffer = new SharedArrayBuffer(width * height);
return new Uint8Array(buffer);
};
const cells = () => new Uint8Array(new SharedArrayBuffer(width * height));
const sharedNumber = () => new Uint32Array(new SharedArrayBuffer(4));

const board: Board = {
width,
height,
input: makeZeros(),
output: makeZeros(),
inSkip: makeZeros(),
outSkip: makeZeros(),
cellsInput: sharedNumber(),
cells: [cells(), cells()],
skipsInput: sharedNumber(),
skips: [cells(), cells()],
};

return board;
};

export const flipBoardIo = (board: Board) => {
board.cellsInput[0] = 1 - board.cellsInput[0];
board.skipsInput[0] = 1 - board.skipsInput[0];
};

export const getBoardIo = (board: Board) => {
return {
input: board.cells[board.cellsInput[0]],
output: board.cells[1 - board.cellsInput[0]],
inSkips: board.skips[board.skipsInput[0]],
outSkips: board.skips[1 - board.skipsInput[0]],
};
};
43 changes: 8 additions & 35 deletions src/logic/next.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Meta } from '../graphics/loop.js';
import { Board, Cells, DONT_SKIP, Skip, SKIP, Skips, SKIP_MULTIPLYER } from './board.js';
import { Board, Cells, DONT_SKIP, flipBoardIo, getBoardIo, Skip, SKIP, Skips, SKIP_MULTIPLYER } from './board.js';
import { assignBoardPadding } from './padding.js';

export const LOOKUP = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0] as const;
Expand Down Expand Up @@ -31,51 +31,24 @@ const revokeSkipForNeighbours = (i: number, outSkip: Skips, width: number) => {
};

export const next = (board: Board) => {
[board.input, board.output, board.inSkip, board.outSkip] = [board.output, board.input, board.outSkip, board.inSkip];
flipBoardIo(board);
const { input, output, inSkips, outSkips } = getBoardIo(board);

board.outSkip.fill(SKIP);
outSkips.fill(SKIP);

let i = board.width + 1;
const endI = board.width * (board.height - 1) - 1;
while (i < endI) {
while (board.inSkip[Math.floor(i / SKIP_MULTIPLYER)]) i += SKIP_MULTIPLYER;
while (inSkips[Math.floor(i / SKIP_MULTIPLYER)]) i += SKIP_MULTIPLYER;

board.output[i] = isAlive(i, board.input, board.width);
output[i] = isAlive(i, input, board.width);

if (board.input[i] !== board.output[i]) {
revokeSkipForNeighbours(i, board.outSkip, board.width);
if (input[i] !== output[i]) {
revokeSkipForNeighbours(i, outSkips, board.width);
}

i++;
}

assignBoardPadding(board);
};

export const startNextBoardLoop = (meta: Meta & { loop?: () => void }) => {
// let endI = meta.board.width;
// const segmentSize = (meta.board.height / JOBS + meta.board.height % JOBS) * meta.board.width;
// const segments = Array(JOBS).fill(1).map(() => {
// const beginI = endI;
// endI = Math.min(meta.board.width*(meta.board.height-1), endI + segmentSize)
// return {
// beginI,
// endI,
// };
// })
const loop = () => {
meta.workers.forEach(worker => {
const handleWorkerMessage = (event: MessageEvent<Board>) => {
worker.removeEventListener('message', handleWorkerMessage);
meta.board = event.data;
meta.generations++;
if (meta.generations < meta.maxGenerations) {
setTimeout(loop, 0);
}
};
worker.addEventListener('message', handleWorkerMessage);
worker.postMessage(meta.board);
});
};
setTimeout(loop, 0);
};
28 changes: 17 additions & 11 deletions src/logic/padding.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Board, DONT_SKIP, SKIP_MULTIPLYER } from './board.js';
import { Board, DONT_SKIP, getBoardIo, SKIP_MULTIPLYER } from './board.js';

const assignPadding = (board: Board) => {
const { width, height } = board;
const { output } = getBoardIo(board);

const assignPadding = ({ output, width, height }: Board) => {
const innerWidth = width - 2;
const innerHeight = height - 2;

Expand All @@ -22,26 +25,29 @@ const assignPadding = ({ output, width, height }: Board) => {
output[width * height - 1] = output[width + 1];
};

const assignSkips = ({ outSkip, width, height }: Board) => {
const assignSkips = (board: Board) => {
const { width, height } = board;
const { outSkips } = getBoardIo(board);

const innerWidth = width - 2;
const innerHeight = height - 2;

for (let i = 1; i <= width - 1; i++) {
outSkip[Math.floor((i + width * innerHeight) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((i + width * innerHeight) / SKIP_MULTIPLYER)] = DONT_SKIP;
}
for (let i = width * (height - 1) + 1; i <= width * height - 2; i++) {
outSkip[Math.floor((i - width * innerHeight) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((i - width * innerHeight) / SKIP_MULTIPLYER)] = DONT_SKIP;
}
for (let i = width; i <= width * (height - 1); i += width) {
outSkip[Math.floor((i + innerWidth) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((i + innerWidth) / SKIP_MULTIPLYER)] = DONT_SKIP;
}
for (let i = width * 2 - 1; i <= width * (height - 1) - 1; i += width) {
outSkip[Math.floor((i - innerWidth) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((i - innerWidth) / SKIP_MULTIPLYER)] = DONT_SKIP;
}
outSkip[Math.floor(0 / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkip[Math.floor((width - 1) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkip[Math.floor((width * (height - 1)) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkip[Math.floor((width * height - 1) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor(0 / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((width - 1) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((width * (height - 1)) / SKIP_MULTIPLYER)] = DONT_SKIP;
outSkips[Math.floor((width * height - 1) / SKIP_MULTIPLYER)] = DONT_SKIP;
};

export const assignBoardPadding = (board: Board) => {
Expand Down
35 changes: 35 additions & 0 deletions src/logic/primaryWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Board } from './board.js';

export type GoMessage = Board & {
generationsAndMax: Uint32Array; // [computations, maxGenerations]
};

{
const workers = new Array(1)
.fill(1)
.map((_, i) => new Worker('/logic/worker.js', { name: `worker-${i}`, type: 'module' }));

console.log(workers);

const doGeneration = (data: GoMessage) => {
workers.forEach(worker => {
const handleWorkerFinished = () => {
worker.removeEventListener('message', handleWorkerFinished);

// Queue next job
// TODO: investigate removing the timeout because we're in another thread now!!!
data.generationsAndMax[0]++;
if (data.generationsAndMax[0] < data.generationsAndMax[1]) {
doGeneration(data);
}
};

worker.addEventListener('message', handleWorkerFinished);
worker.postMessage(data);
});
};

addEventListener('message', (event: MessageEvent<GoMessage>) => {
doGeneration(event.data);
});
}
2 changes: 1 addition & 1 deletion src/logic/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { next } from './next.js';
addEventListener('message', (event: MessageEvent<Board>) => {
const board = event.data;
next(board);
postMessage(board);
postMessage(1);
});

0 comments on commit e67e46a

Please sign in to comment.