638 lines
20 KiB
JavaScript
638 lines
20 KiB
JavaScript
// wfc_solver_worker.js
|
|
let grid,
|
|
pCols,
|
|
pRows,
|
|
patternsW,
|
|
patternIdToIndexW,
|
|
adjacencyW,
|
|
currentPatternSizeW_solver;
|
|
let queue, queuedCoordinatesSet, backtrackStack;
|
|
let currentAttemptFailed;
|
|
let maxTriesInWorker; // Renamed from maxTries to avoid confusion
|
|
let useRandomSelectionW;
|
|
let MAX_BACKTRACK_DEPTH_W;
|
|
|
|
// --- Worker Stats (subset for WFC run) ---
|
|
let solverRunStats = {};
|
|
function resetSolverRunStats() {
|
|
solverRunStats = {
|
|
maxBacktrackStackDepthReached: 0,
|
|
totalBacktracks: 0,
|
|
totalContradictions: 0,
|
|
forcedBacktracksByDepth: 0,
|
|
cellsCollapsed: 0,
|
|
propagationSteps: 0,
|
|
// startTime/endTime/durationMs will be managed by main thread based on messages
|
|
};
|
|
}
|
|
let solverMemoryStats = {
|
|
estimatedGridSnapshotBytes: 0, // Will be calculated once grid is initialized
|
|
backtrackStackPeakBytes: 0,
|
|
};
|
|
|
|
// --- Helper functions for solver (deepCopy, getPatternNeighbors, etc.) ---
|
|
function deepCopyGrid(gridToCopy) {
|
|
if (!gridToCopy) return null;
|
|
return gridToCopy.map((row) => row.map((cellOptions) => [...cellOptions]));
|
|
}
|
|
function deepCopyQueue(queueToCopy) {
|
|
if (!queueToCopy) return null;
|
|
return queueToCopy.map((item) => (Array.isArray(item) ? [...item] : item));
|
|
}
|
|
function getPatternNeighbors(c, r) {
|
|
return [
|
|
[c - 1, r, 'left'],
|
|
[c + 1, r, 'right'],
|
|
[c, r - 1, 'up'],
|
|
[c, r + 1, 'down'],
|
|
].filter(([nc, nr]) => nc >= 0 && nr >= 0 && nc < pCols && nr < pRows);
|
|
}
|
|
|
|
function initializeGridInWorker() {
|
|
const allPatternIndices = Array.from(
|
|
{ length: patternsW.length },
|
|
(_, i) => i
|
|
);
|
|
grid = Array.from({ length: pRows }, () =>
|
|
Array.from({ length: pCols }, () => [...allPatternIndices])
|
|
);
|
|
queue = [];
|
|
queuedCoordinatesSet = new Set();
|
|
maxTriesInWorker = pCols * pRows * 30 * currentPatternSizeW_solver; // Original logic for max tries
|
|
currentAttemptFailed = false;
|
|
backtrackStack = [];
|
|
resetSolverRunStats(); // Reset stats for this run
|
|
|
|
// Estimate memory for one grid snapshot (for backtrack stack calculations)
|
|
let oneGridBytes = 16; // Outer array
|
|
if (grid && grid.length > 0 && grid[0] && grid[0].length > 0) {
|
|
oneGridBytes += grid.length * 16; // Each row array
|
|
const cell = grid[0][0];
|
|
const bytesPerIndex =
|
|
patternsW.length > 65535 ? 4 : patternsW.length > 255 ? 2 : 1;
|
|
const cellBytes = 16 + cell.length * bytesPerIndex;
|
|
oneGridBytes += grid.length * grid[0].length * cellBytes;
|
|
}
|
|
solverMemoryStats.estimatedGridSnapshotBytes = oneGridBytes;
|
|
solverMemoryStats.backtrackStackPeakBytes = 0; // Reset for this run
|
|
|
|
// Send initial grid state (all tiles are "dirty" with all options)
|
|
const initialUpdates = [];
|
|
for (let r = 0; r < pRows; r++) {
|
|
for (let c = 0; c < pCols; c++) {
|
|
initialUpdates.push({ c, r, cellOptions: [...grid[r][c]] });
|
|
}
|
|
}
|
|
if (initialUpdates.length > 0) {
|
|
self.postMessage({
|
|
type: 'TILE_UPDATE',
|
|
payload: { updates: initialUpdates },
|
|
});
|
|
}
|
|
self.postMessage({
|
|
type: 'STATS_UPDATE',
|
|
payload: { memory: solverMemoryStats },
|
|
});
|
|
}
|
|
|
|
function pickCellWithLowestEntropy() {
|
|
/* ... Same as original ... */
|
|
if (!grid) return null;
|
|
let bestEntropy = Infinity;
|
|
let bestCells = [];
|
|
for (let r = 0; r < pRows; r++) {
|
|
for (let c = 0; c < pCols; c++) {
|
|
if (!grid[r] || !grid[r][c]) continue;
|
|
const numOptions = grid[r][c].length;
|
|
if (numOptions > 1) {
|
|
const currentEntropy = numOptions + Math.random() * 0.1; // Add jitter
|
|
if (currentEntropy < bestEntropy) {
|
|
bestEntropy = currentEntropy;
|
|
bestCells = [[c, r]];
|
|
} else if (numOptions === Math.floor(bestEntropy)) {
|
|
// Check only integer part for equality
|
|
bestCells.push([c, r]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bestCells.length > 0
|
|
? bestCells[Math.floor(Math.random() * bestCells.length)]
|
|
: null;
|
|
}
|
|
|
|
function collapseCell(c, r) {
|
|
/* ... Same as original, but use solverRunStats and post TILE_UPDATE ... */
|
|
const initialOptionsForCellIndices = grid[r][c];
|
|
if (initialOptionsForCellIndices.length === 0) {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return;
|
|
}
|
|
|
|
if (backtrackStack.length >= MAX_BACKTRACK_DEPTH_W) {
|
|
// Post status about max depth reached
|
|
self.postMessage({
|
|
type: 'STATUS_UPDATE',
|
|
payload: {
|
|
message: `Max backtrack depth (${MAX_BACKTRACK_DEPTH_W}) reached. Forcing backtrack.`,
|
|
},
|
|
});
|
|
currentAttemptFailed = true;
|
|
solverRunStats.forcedBacktracksByDepth++;
|
|
solverRunStats.totalContradictions++;
|
|
return;
|
|
}
|
|
|
|
let optionsToSystematicallyTry = [...initialOptionsForCellIndices];
|
|
// Randomize if checkbox was checked
|
|
if (useRandomSelectionW && optionsToSystematicallyTry.length > 1) {
|
|
for (let i = optionsToSystematicallyTry.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[optionsToSystematicallyTry[i], optionsToSystematicallyTry[j]] = [
|
|
optionsToSystematicallyTry[j],
|
|
optionsToSystematicallyTry[i],
|
|
];
|
|
}
|
|
} else if (!useRandomSelectionW && optionsToSystematicallyTry.length > 1) {
|
|
// Weighted selection
|
|
let totalWeight = 0;
|
|
const weights = optionsToSystematicallyTry.map((idx) => {
|
|
const pat = patternsW[idx]; // patternsW is array of pattern objects
|
|
const w = pat && pat.frequency > 0 ? pat.frequency : 1;
|
|
totalWeight += w;
|
|
return w;
|
|
});
|
|
if (totalWeight === 0) {
|
|
// Should not happen if frequencies are positive
|
|
// Fallback to random if all weights are zero
|
|
} else {
|
|
let rnd = Math.random() * totalWeight;
|
|
let chosenIndexInSystematicList;
|
|
for (let i = 0; i < optionsToSystematicallyTry.length; i++) {
|
|
rnd -= weights[i];
|
|
if (rnd <= 0) {
|
|
chosenIndexInSystematicList = i;
|
|
break;
|
|
}
|
|
}
|
|
if (typeof chosenIndexInSystematicList === 'undefined')
|
|
chosenIndexInSystematicList = optionsToSystematicallyTry.length - 1; // Should not be needed
|
|
|
|
// Move chosen to front
|
|
const actualChosenItem = optionsToSystematicallyTry.splice(
|
|
chosenIndexInSystematicList,
|
|
1
|
|
)[0];
|
|
optionsToSystematicallyTry.unshift(actualChosenItem);
|
|
}
|
|
}
|
|
|
|
let chosenPatternIndex = optionsToSystematicallyTry[0]; // Try the first one (either random or weighted first)
|
|
|
|
if (typeof chosenPatternIndex === 'undefined') {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return;
|
|
}
|
|
|
|
const gridSnapshot = deepCopyGrid(grid);
|
|
const queueSnapshot = deepCopyQueue(queue);
|
|
const queuedCoordinatesSetSnapshot = new Set(queuedCoordinatesSet);
|
|
|
|
backtrackStack.push({
|
|
gridBeforeChoice: gridSnapshot,
|
|
queueBeforeChoice: queueSnapshot,
|
|
queuedCoordinatesSetBeforeChoice: queuedCoordinatesSetSnapshot,
|
|
cellCoords: [c, r],
|
|
optionsAtCell: optionsToSystematicallyTry,
|
|
lastTriedOptionIndexInList: 0,
|
|
});
|
|
solverRunStats.maxBacktrackStackDepthReached = Math.max(
|
|
solverRunStats.maxBacktrackStackDepthReached,
|
|
backtrackStack.length
|
|
);
|
|
solverMemoryStats.backtrackStackPeakBytes = Math.max(
|
|
solverMemoryStats.backtrackStackPeakBytes,
|
|
backtrackStack.length * solverMemoryStats.estimatedGridSnapshotBytes
|
|
);
|
|
self.postMessage({
|
|
type: 'STATS_UPDATE',
|
|
payload: {
|
|
wfcRun: {
|
|
maxBacktrackStackDepthReached:
|
|
solverRunStats.maxBacktrackStackDepthReached,
|
|
},
|
|
memory: {
|
|
backtrackStackPeakBytes: solverMemoryStats.backtrackStackPeakBytes,
|
|
},
|
|
},
|
|
});
|
|
|
|
grid[r][c] = [chosenPatternIndex];
|
|
solverRunStats.cellsCollapsed++;
|
|
self.postMessage({
|
|
type: 'TILE_UPDATE',
|
|
payload: { updates: [{ c, r, cellOptions: grid[r][c] }] },
|
|
});
|
|
|
|
const coordKey = `${c},${r}`;
|
|
if (!queuedCoordinatesSet.has(coordKey)) {
|
|
queue.push([c, r]);
|
|
queuedCoordinatesSet.add(coordKey);
|
|
}
|
|
}
|
|
|
|
function propagateStep() {
|
|
/* ... Same as original, but use solverRunStats and post TILE_UPDATE ... */
|
|
solverRunStats.propagationSteps++;
|
|
if (queue.length === 0) return { success: true };
|
|
|
|
const [c, r] = queue.shift();
|
|
queuedCoordinatesSet.delete(`${c},${r}`);
|
|
|
|
if (!grid || !grid[r] || !grid[r][c]) {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return { success: false };
|
|
}
|
|
const currentPatternIndicesInCell = grid[r][c];
|
|
if (currentPatternIndicesInCell.length === 0) {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return { success: false };
|
|
}
|
|
|
|
const tileUpdatesForPropagation = [];
|
|
|
|
for (const [nc, nr, dirToNeighbor] of getPatternNeighbors(c, r)) {
|
|
if (!grid[nr] || !grid[nr][nc]) {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return { success: false };
|
|
}
|
|
const originalNeighborOptionIndices = grid[nr][nc];
|
|
if (originalNeighborOptionIndices.length === 0) {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return { success: false }; // Already a contradiction
|
|
}
|
|
|
|
let cumulativeAllowedNeighborPatternIndices = new Set();
|
|
currentPatternIndicesInCell.forEach((p_idx) => {
|
|
const p_id_str = patternsW[p_idx].id; // patternsW is array of pattern objects
|
|
if (!adjacencyW[p_id_str]) return; // Should not happen with valid adjacency
|
|
|
|
let compatibleIndices;
|
|
if (dirToNeighbor === 'right')
|
|
compatibleIndices = adjacencyW[p_id_str].right;
|
|
else if (dirToNeighbor === 'left')
|
|
compatibleIndices = adjacencyW[p_id_str].left;
|
|
else if (dirToNeighbor === 'up')
|
|
compatibleIndices = adjacencyW[p_id_str].up;
|
|
else if (dirToNeighbor === 'down')
|
|
compatibleIndices = adjacencyW[p_id_str].down;
|
|
else return;
|
|
|
|
compatibleIndices.forEach((q_idx) =>
|
|
cumulativeAllowedNeighborPatternIndices.add(q_idx)
|
|
);
|
|
});
|
|
|
|
const newNeighborOptionIndices = originalNeighborOptionIndices.filter(
|
|
(idx) => cumulativeAllowedNeighborPatternIndices.has(idx)
|
|
);
|
|
|
|
if (newNeighborOptionIndices.length === 0) {
|
|
currentAttemptFailed = true;
|
|
solverRunStats.totalContradictions++;
|
|
return { success: false };
|
|
}
|
|
|
|
if (
|
|
newNeighborOptionIndices.length < originalNeighborOptionIndices.length
|
|
) {
|
|
grid[nr][nc] = newNeighborOptionIndices;
|
|
tileUpdatesForPropagation.push({
|
|
c: nc,
|
|
r: nr,
|
|
cellOptions: grid[nr][nc],
|
|
});
|
|
|
|
const neighborCoordKey = `${nc},${nr}`;
|
|
if (!queuedCoordinatesSet.has(neighborCoordKey)) {
|
|
queue.push([nc, nr]);
|
|
queuedCoordinatesSet.add(neighborCoordKey);
|
|
}
|
|
}
|
|
}
|
|
if (tileUpdatesForPropagation.length > 0) {
|
|
self.postMessage({
|
|
type: 'TILE_UPDATE',
|
|
payload: { updates: tileUpdatesForPropagation },
|
|
});
|
|
}
|
|
return { success: true };
|
|
}
|
|
|
|
function allCollapsed() {
|
|
/* ... Same as original ... */
|
|
if (!grid) return false;
|
|
for (let r_idx = 0; r_idx < pRows; r_idx++) {
|
|
for (let c_idx = 0; c_idx < pCols; c_idx++) {
|
|
if (
|
|
!grid[r_idx] ||
|
|
!grid[r_idx][c_idx] ||
|
|
grid[r_idx][c_idx].length > 1
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
let solverPhase = 'collapse'; // 'collapse' or 'propagate'
|
|
let runLoopTimeoutId = null;
|
|
|
|
function runStepInWorker() {
|
|
if (runLoopTimeoutId) clearTimeout(runLoopTimeoutId); // Clear previous timeout if any
|
|
|
|
if (solverPhase === 'stop') {
|
|
// A way to stop the loop if main thread terminates worker
|
|
return;
|
|
}
|
|
|
|
if (currentAttemptFailed) {
|
|
currentAttemptFailed = false;
|
|
let backtrackedSuccessfully = false;
|
|
solverRunStats.totalBacktracks++;
|
|
self.postMessage({
|
|
type: 'STATS_UPDATE',
|
|
payload: { wfcRun: { totalBacktracks: solverRunStats.totalBacktracks } },
|
|
});
|
|
|
|
// THIS IS THE CORRECTED BACKTRACKING LOOP
|
|
while (backtrackStack.length > 0) {
|
|
const lastDecision = backtrackStack.pop();
|
|
const {
|
|
gridBeforeChoice,
|
|
queueBeforeChoice,
|
|
queuedCoordinatesSetBeforeChoice,
|
|
cellCoords,
|
|
optionsAtCell,
|
|
lastTriedOptionIndexInList,
|
|
} = lastDecision;
|
|
|
|
const [cc, cr] = cellCoords;
|
|
let nextOptionIndexToTryInList = lastTriedOptionIndexInList + 1;
|
|
|
|
if (nextOptionIndexToTryInList < optionsAtCell.length) {
|
|
grid = deepCopyGrid(gridBeforeChoice); // Restore state
|
|
queue = deepCopyQueue(queueBeforeChoice);
|
|
queuedCoordinatesSet = new Set(queuedCoordinatesSetBeforeChoice);
|
|
|
|
const newChosenPatternIndex = optionsAtCell[nextOptionIndexToTryInList];
|
|
grid[cr][cc] = [newChosenPatternIndex];
|
|
// Post update for the cell being retried
|
|
self.postMessage({
|
|
type: 'TILE_UPDATE',
|
|
payload: { updates: [{ c: cc, r: cr, cellOptions: grid[cr][cc] }] },
|
|
});
|
|
|
|
const coordKeyBacktrack = `${cc},${cr}`;
|
|
if (!queuedCoordinatesSet.has(coordKeyBacktrack)) {
|
|
queue.push([cc, cr]);
|
|
queuedCoordinatesSet.add(coordKeyBacktrack);
|
|
}
|
|
|
|
// THE FIX: Push the updated decision back onto the stack
|
|
if (backtrackStack.length < MAX_BACKTRACK_DEPTH_W) {
|
|
backtrackStack.push({
|
|
...lastDecision,
|
|
lastTriedOptionIndexInList: nextOptionIndexToTryInList,
|
|
});
|
|
solverRunStats.maxBacktrackStackDepthReached = Math.max(
|
|
solverRunStats.maxBacktrackStackDepthReached,
|
|
backtrackStack.length
|
|
);
|
|
solverMemoryStats.backtrackStackPeakBytes = Math.max(
|
|
solverMemoryStats.backtrackStackPeakBytes,
|
|
backtrackStack.length * solverMemoryStats.estimatedGridSnapshotBytes
|
|
);
|
|
self.postMessage({
|
|
type: 'STATS_UPDATE',
|
|
payload: {
|
|
wfcRun: {
|
|
maxBacktrackStackDepthReached:
|
|
solverRunStats.maxBacktrackStackDepthReached,
|
|
},
|
|
memory: {
|
|
backtrackStackPeakBytes:
|
|
solverMemoryStats.backtrackStackPeakBytes,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
self.postMessage({
|
|
type: 'STATUS_UPDATE',
|
|
payload: {
|
|
message: `Backtracked. Retrying option ${
|
|
nextOptionIndexToTryInList + 1
|
|
}/${optionsAtCell.length} at (${cc},${cr}). Stack: ${
|
|
backtrackStack.length
|
|
}`,
|
|
},
|
|
});
|
|
solverPhase = 'propagate';
|
|
backtrackedSuccessfully = true;
|
|
|
|
const allTilesUpdate = [];
|
|
for (let r_idx = 0; r_idx < pRows; r_idx++) {
|
|
for (let c_idx = 0; c_idx < pCols; c_idx++) {
|
|
if (grid[r_idx] && grid[r_idx][c_idx]) {
|
|
allTilesUpdate.push({
|
|
c: c_idx,
|
|
r: r_idx,
|
|
cellOptions: [...grid[r_idx][c_idx]],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (allTilesUpdate.length > 0) {
|
|
self.postMessage({
|
|
type: 'TILE_UPDATE',
|
|
payload: { updates: allTilesUpdate },
|
|
});
|
|
}
|
|
break; // Exit backtrack loop
|
|
}
|
|
}
|
|
|
|
|
|
if (!backtrackedSuccessfully) {
|
|
self.postMessage({
|
|
type: 'REQUEST_RESTART_FROM_SOLVER',
|
|
payload: {
|
|
message:
|
|
'Backtrack stack exhausted -> WFC failed in worker. Requesting restart.',
|
|
wfcStatus: 'Failed (Backtrack Exhausted in Worker)',
|
|
currentRunStats: { wfcRun: solverRunStats }, // Send current stats before restart
|
|
},
|
|
});
|
|
solverPhase = 'stop'; // Stop this worker's loop
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (allCollapsed()) {
|
|
solverPhase = 'stop';
|
|
// Add the final grid to the payload. This is the definitive final state.
|
|
self.postMessage({
|
|
type: 'WFC_COMPLETE',
|
|
payload: {
|
|
finalStats: { wfcRun: solverRunStats },
|
|
finalGrid: grid
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (maxTriesInWorker-- <= 0 && solverPhase !== 'propagate') {
|
|
self.postMessage({
|
|
type: 'REQUEST_RESTART_FROM_SOLVER',
|
|
payload: {
|
|
message: 'Max tries reached in worker -> Requesting restart.',
|
|
wfcStatus: 'Failed (Timeout in Worker)',
|
|
currentRunStats: { wfcRun: solverRunStats },
|
|
},
|
|
});
|
|
solverPhase = 'stop';
|
|
return;
|
|
}
|
|
|
|
if (solverPhase === 'collapse') {
|
|
const cellToCollapse = pickCellWithLowestEntropy();
|
|
if (!cellToCollapse) {
|
|
if (!allCollapsed()) {
|
|
// Should be a contradiction if no cell to collapse but not all done
|
|
currentAttemptFailed = true; // Trigger backtrack
|
|
solverRunStats.totalContradictions++;
|
|
self.postMessage({
|
|
type: 'STATUS_UPDATE',
|
|
payload: {
|
|
message:
|
|
'Error: No cell to collapse, but not all collapsed. Contradiction.',
|
|
},
|
|
});
|
|
} else {
|
|
// Should have been caught by allCollapsed() earlier
|
|
solverPhase = 'stop';
|
|
self.postMessage({
|
|
type: 'WFC_COMPLETE',
|
|
payload: { finalStats: { wfcRun: solverRunStats } },
|
|
});
|
|
return;
|
|
}
|
|
} else {
|
|
collapseCell(cellToCollapse[0], cellToCollapse[1]);
|
|
if (!currentAttemptFailed) {
|
|
solverPhase = 'propagate';
|
|
}
|
|
}
|
|
} else if (solverPhase === 'propagate') {
|
|
const MAX_PROP_PER_BATCH = Math.max(30, Math.floor((pCols * pRows) / 20)); // Batch propagations
|
|
let processedInBatch = 0;
|
|
let propResult = { success: true };
|
|
|
|
while (
|
|
processedInBatch++ < MAX_PROP_PER_BATCH &&
|
|
queue.length > 0 &&
|
|
!currentAttemptFailed
|
|
) {
|
|
propResult = propagateStep();
|
|
if (!propResult.success) break; // currentAttemptFailed would be true
|
|
}
|
|
|
|
if (queue.length === 0 || currentAttemptFailed || !propResult.success) {
|
|
solverPhase = 'collapse'; // Go back to collapse if queue empty or contradiction
|
|
}
|
|
}
|
|
|
|
// Post periodic stat updates
|
|
self.postMessage({
|
|
type: 'STATS_UPDATE',
|
|
payload: { wfcRun: solverRunStats },
|
|
});
|
|
|
|
if (solverPhase !== 'stop') {
|
|
runLoopTimeoutId = setTimeout(runStepInWorker, 0); // Continue the loop non-blockingly
|
|
}
|
|
}
|
|
|
|
self.onmessage = function (e) {
|
|
const {
|
|
type,
|
|
pCols: pc,
|
|
pRows: pr,
|
|
patterns: p,
|
|
patternIdToIndex: pIdToIdx,
|
|
adjacency: adj,
|
|
currentPatternSize: cPS,
|
|
maxBacktrackDepth,
|
|
useRandomSelection,
|
|
} = e.data;
|
|
|
|
if (type === 'START_SOLVE') {
|
|
pCols = pc;
|
|
pRows = pr;
|
|
// patternsW are received with ArrayBuffers for cells. These are now owned by this worker.
|
|
// We need to reconstruct them into Uint8Arrays if they are not already.
|
|
// Assuming main thread sent them as objects with .cells being ArrayBuffer.
|
|
patternsW = p.map((pat) => ({ ...pat, cells: new Uint8Array(pat.cells) }));
|
|
patternIdToIndexW = pIdToIdx;
|
|
adjacencyW = adj;
|
|
currentPatternSizeW_solver = cPS;
|
|
MAX_BACKTRACK_DEPTH_W = maxBacktrackDepth;
|
|
useRandomSelectionW = useRandomSelection;
|
|
|
|
solverPhase = 'collapse'; // Initial phase
|
|
|
|
try {
|
|
initializeGridInWorker();
|
|
self.postMessage({
|
|
type: 'STATUS_UPDATE',
|
|
payload: {
|
|
message: `Solver worker started. Grid: ${pCols}x${pRows}. Patterns: ${patternsW.length}`,
|
|
},
|
|
});
|
|
runStepInWorker(); // Start the loop
|
|
} catch (err) {
|
|
console.error('Error in solver worker START_SOLVE:', err);
|
|
self.postMessage({
|
|
type: null,
|
|
error: err.message + (err.stack ? '\n' + err.stack : ''),
|
|
});
|
|
solverPhase = 'stop';
|
|
}
|
|
}
|
|
};
|
|
|
|
// Catch unhandled errors in the worker
|
|
self.onerror = function (message, source, lineno, colno, error) {
|
|
console.error(
|
|
'Unhandled error in Solver Worker:',
|
|
message,
|
|
source,
|
|
lineno,
|
|
colno,
|
|
error
|
|
);
|
|
self.postMessage({
|
|
type: null,
|
|
error: `Unhandled: ${message} ${error ? error.stack : ''}`,
|
|
});
|
|
solverPhase = 'stop'; // Stop processing on unhandled error
|
|
return true; // Prevents default error handling
|
|
}; |