All files / src/components/rendering tilemapLayersWorkerPool.ts

0% Statements 0/40
0% Branches 0/14
0% Functions 0/7
0% Lines 0/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101                                                                                                                                                                                                         
import type {
  TilemapLayersCanvasData,
  TilemapLayersCanvasResult,
} from "./TilemapLayersCanvas.worker";
 
type Listener = (event: MessageEvent<TilemapLayersCanvasResult>) => void;
 
interface PendingRequest {
  data: TilemapLayersCanvasData;
  transfer: Transferable[];
}
 
interface WorkerSlot {
  worker: Worker;
  busy: boolean;
  listeners: Map<string, Listener>;
  pending: Map<string, PendingRequest>;
}
 
const workerCount = Math.min(
  2,
  Math.max(1, navigator.hardwareConcurrency - 2 || 1),
);
 
const slots: WorkerSlot[] = [];
let nextSlot = 0;
 
const dispatchNext = (slot: WorkerSlot) => {
  if (slot.busy) {
    return;
  }
  const next = slot.pending.entries().next().value as
    [string, PendingRequest] | undefined;
  if (!next) {
    return;
  }
 
  const [canvasId, request] = next;
  slot.pending.delete(canvasId);
  slot.busy = true;
  slot.worker.postMessage(request.data, request.transfer);
};
 
for (let index = 0; index < workerCount; index++) {
  const worker = new Worker(
    new URL("./TilemapLayersCanvas.worker.ts", import.meta.url),
  );
  const slot: WorkerSlot = {
    worker,
    busy: false,
    listeners: new Map(),
    pending: new Map(),
  };
  worker.addEventListener(
    "message",
    (event: MessageEvent<TilemapLayersCanvasResult>) => {
      slot.busy = false;
      const listener = slot.listeners.get(event.data.canvasId);
      if (listener) {
        listener(event);
      } else {
        event.data.canvasImage.close?.();
      }
      dispatchNext(slot);
    },
  );
  worker.addEventListener("error", () => {
    slot.busy = false;
    dispatchNext(slot);
  });
  slots.push(slot);
}
 
export interface TilemapLayersWorkerHandle {
  subscribe: (listener: Listener) => () => void;
  request: (data: TilemapLayersCanvasData, transfer: Transferable[]) => void;
}
 
export const createTilemapLayersWorkerHandle = (
  canvasId: string,
): TilemapLayersWorkerHandle => {
  const slot = slots[nextSlot++ % slots.length];
 
  return {
    subscribe: (listener) => {
      slot.listeners.set(canvasId, listener);
      return () => {
        if (slot.listeners.get(canvasId) === listener) {
          slot.listeners.delete(canvasId);
          slot.pending.delete(canvasId);
        }
      };
    },
    request: (data, transfer) => {
      slot.pending.delete(canvasId);
      slot.pending.set(canvasId, { data, transfer });
      dispatchNext(slot);
    },
  };
};