test_repo/wfc_setup_worker.js
2025-06-10 08:51:34 +02:00

405 lines
13 KiB
JavaScript

// wfc_setup_worker.js
let sampleWidth,
sampleHeight,
srcDataView,
currentPatternSizeW,
enableRotationsW;
let patterns, patternByID, patternByIndex, patternIdToIndex;
let adjacency;
// --- Helper functions (subset of original, or new for worker) ---
function Uint8ArrayToHexString(u8a) {
let hex = '';
for (let i = 0; i < u8a.length; i++) {
let h = u8a[i].toString(16);
if (h.length < 2) h = '0' + h;
hex += h;
}
return hex;
}
function rotatePatternCells(flatCells, pSize, angle) {
const newFlatCells = new Uint8Array(pSize * pSize * 4);
if (angle === 0) {
newFlatCells.set(flatCells);
return newFlatCells;
}
for (let r_orig = 0; r_orig < pSize; r_orig++) {
for (let c_orig = 0; c_orig < pSize; c_orig++) {
const oldIdxBase = (r_orig * pSize + c_orig) * 4;
let nr, nc;
if (angle === 90) {
nr = c_orig;
nc = pSize - 1 - r_orig;
} else if (angle === 180) {
nr = pSize - 1 - r_orig;
nc = pSize - 1 - c_orig;
} else if (angle === 270) {
nr = pSize - 1 - c_orig;
nc = r_orig;
} else {
newFlatCells.set(flatCells);
return newFlatCells;
} // Should not happen with defined angles
const newIdxBase = (nr * pSize + nc) * 4;
newFlatCells[newIdxBase] = flatCells[oldIdxBase];
newFlatCells[newIdxBase + 1] = flatCells[oldIdxBase + 1];
newFlatCells[newIdxBase + 2] = flatCells[oldIdxBase + 2];
newFlatCells[newIdxBase + 3] = flatCells[oldIdxBase + 3];
}
}
return newFlatCells;
}
// --- Stats helpers (subset for this worker) ---
function estimateStringBytes(str) {
return str ? str.length * 2 : 0;
}
function estimateObjectOverhead(obj) {
if (!obj) return 0;
return (
Object.keys(obj).length *
(estimateStringBytes('key_placeholder_avg') + 8 + 4) +
16
);
}
let workerStats = {
patternExtraction: {},
adjacency: {},
memory: {},
};
function extractPatterns() {
console.log(
'[Setup Worker] extractPatterns started. sampleWidth:',
sampleWidth,
'sampleHeight:',
sampleHeight,
'pSize:',
currentPatternSizeW
);
// Log a small portion of srcDataView to check its general content
if (srcDataView && srcDataView.length > 0) {
console.log(
'[Setup Worker] srcDataView first 100 bytes:',
srcDataView.slice(0, 100)
);
} else {
console.error(
'[Setup Worker] srcDataView is null, empty, or undefined at start of extractPatterns!'
);
// If srcDataView is bad, we can't proceed meaningfully.
throw new Error('srcDataView is invalid in extractPatterns');
}
const t0 = performance.now();
const countMap = new Map();
const keyToFlatCells = new Map();
const pSize = currentPatternSizeW;
let rawPatternCounter = 0;
for (let y = 0; y <= sampleHeight - pSize; y++) {
for (let x = 0; x <= sampleWidth - pSize; x++) {
rawPatternCounter++;
const originalFlatBlock = new Uint8Array(pSize * pSize * 4);
let k = 0;
for (let dy = 0; dy < pSize; dy++) {
for (let dx = 0; dx < pSize; dx++) {
const imgPixelY = y + dy;
const imgPixelX = x + dx;
const srcIdx = (imgPixelY * sampleWidth + imgPixelX) * 4;
if (srcIdx + 3 >= srcDataView.length) {
// Boundary check
console.error(
`[Setup Worker] Out of bounds access attempt: srcIdx=${srcIdx}, y=${y},x=${x}, dy=${dy},dx=${dx}, imgPixelY=${imgPixelY},imgPixelX=${imgPixelX}, sampleWidth=${sampleWidth}, sampleHeight=${sampleHeight}, srcDataView.length=${srcDataView.length}`
);
// Fill with a debug color or handle error
originalFlatBlock[k++] = 255;
originalFlatBlock[k++] = 0;
originalFlatBlock[k++] = 0;
originalFlatBlock[k++] = 255; // Red
continue; // Skip this pixel
}
originalFlatBlock[k++] = srcDataView[srcIdx];
originalFlatBlock[k++] = srcDataView[srcIdx + 1];
originalFlatBlock[k++] = srcDataView[srcIdx + 2];
originalFlatBlock[k++] = srcDataView[srcIdx + 3];
}
}
if (x === 0 && y === 0) {
// Log only the very first extracted block
console.log(
'[Setup Worker] First originalFlatBlock (x=0, y=0, pSize=' +
pSize +
'):'
);
let pixelsToLog = [];
for (let i = 0; i < originalFlatBlock.length; i += 4) {
pixelsToLog.push(
`(${originalFlatBlock[i]},${originalFlatBlock[i + 1]},${
originalFlatBlock[i + 2]
},${originalFlatBlock[i + 3]})`
);
}
console.log(pixelsToLog.join(' '));
}
const rotationsToConsider = enableRotationsW ? [0, 90, 180, 270] : [0];
for (const angle of rotationsToConsider) {
const currentFlatBlockCells = rotatePatternCells(
originalFlatBlock,
pSize,
angle
);
const key = Uint8ArrayToHexString(currentFlatBlockCells);
countMap.set(key, (countMap.get(key) || 0) + 1);
if (!keyToFlatCells.has(key)) {
keyToFlatCells.set(key, currentFlatBlockCells);
}
}
}
}
workerStats.patternExtraction.rawPatternCount =
rawPatternCounter * (enableRotationsW ? 4 : 1);
patterns = [];
patternByID = {};
patternByIndex = []; // This will be an array of pattern objects
patternIdToIndex = {};
let idx = 0;
for (const [key, freq] of countMap.entries()) {
const patID = 'p' + idx;
const flatCellsData = keyToFlatCells.get(key); // This is a Uint8Array
const patObj = {
id: patID,
cells: flatCellsData,
frequency: freq,
index: idx,
};
patterns.push(patObj);
patternByID[patID] = patObj;
patternByIndex[idx] = patObj; // Store by index
patternIdToIndex[patID] = idx;
idx++;
}
workerStats.patternExtraction.timeMs = performance.now() - t0;
workerStats.patternExtraction.uniquePatternCount = patterns.length;
workerStats.patternExtraction.rotationsEnabled = enableRotationsW;
workerStats.memory.patternCellsBytes = 0;
workerStats.memory.patternsArrayBytes = 0;
patterns.forEach((p) => {
workerStats.memory.patternCellsBytes += p.cells.byteLength;
workerStats.memory.patternsArrayBytes +=
estimateStringBytes(p.id) + 4 + 4 + 8 + 16;
});
workerStats.memory.patternsArrayBytes += 16;
workerStats.memory.patternByIdBytes = estimateObjectOverhead(patternByID);
Object.keys(patternByID).forEach((key) => {
workerStats.memory.patternByIdBytes += estimateStringBytes(key) + 8;
});
self.postMessage({
type: 'SETUP_PROGRESS',
payload: {
statusMessage: `Found ${patterns.length} unique patterns. Building adjacency...`,
statsUpdate: {
patternExtraction: workerStats.patternExtraction,
memory: workerStats.memory,
},
},
});
}
// --- CORRECTED `buildAdjacency` for the OVERLAPPING MODEL ---
// --- Paste this into wfc_setup_worker.js ---
function buildAdjacency() {
const t1 = performance.now();
adjacency = {};
if (!patterns) return;
const pSize = currentPatternSizeW;
let ruleCount = 0;
for (const pat of patterns) {
adjacency[pat.id] = { up: [], down: [], left: [], right: [] };
}
for (const p of patterns) {
for (const q of patterns) {
if (p.id === q.id && patterns.length === 1) {
adjacency[p.id].right.push(q.index);
ruleCount++;
adjacency[q.id].left.push(p.index);
ruleCount++;
adjacency[p.id].down.push(q.index);
ruleCount++;
adjacency[q.id].up.push(p.index);
ruleCount++;
continue;
}
// Check if q can be to the RIGHT of p (OVERLAPPING MODEL)
// The right N-1 columns of p must match the left N-1 columns of q.
let canSitRight = true;
if (pSize > 1) {
for (let r = 0; r < pSize; r++) { // For each row
for (let c = 0; c < pSize - 1; c++) { // For each overlapping column
for (let ch = 0; ch < 4; ch++) {
// Compare p's pixel at [r][c+1] with q's pixel at [r][c]
const pPixel = p.cells[(r * pSize + (c + 1)) * 4 + ch];
const qPixel = q.cells[(r * pSize + c) * 4 + ch];
if (pPixel !== qPixel) {
canSitRight = false;
break;
}
}
if (!canSitRight) break;
}
if (!canSitRight) break;
}
} else {
canSitRight = true; // 1x1 patterns are always adjacent
}
if (canSitRight) {
adjacency[p.id].right.push(q.index);
ruleCount++;
adjacency[q.id].left.push(p.index);
ruleCount++;
}
// Check if q can be DOWN from p (OVERLAPPING MODEL)
// The bottom N-1 rows of p must match the top N-1 rows of q.
let canSitDown = true;
if (pSize > 1) {
for (let r = 0; r < pSize - 1; r++) { // For each overlapping row
for (let c = 0; c < pSize; c++) { // For each column in that row
for (let ch = 0; ch < 4; ch++) {
// Compare p's pixel at [r+1][c] with q's pixel at [r][c]
const pPixel = p.cells[((r + 1) * pSize + c) * 4 + ch];
const qPixel = q.cells[(r * pSize + c) * 4 + ch];
if (pPixel !== qPixel) {
canSitDown = false;
break;
}
}
if (!canSitDown) break;
}
if (!canSitDown) break;
}
} else {
canSitDown = true; // 1x1 patterns are always adjacent
}
if (canSitDown) {
adjacency[p.id].down.push(q.index);
ruleCount++;
adjacency[q.id].up.push(p.index);
ruleCount++;
}
}
}
workerStats.adjacency.totalRules = ruleCount;
workerStats.adjacency.timeMs = performance.now() - t1;
let adjMem = estimateObjectOverhead(adjacency);
if (adjacency) {
for (const patId in adjacency) {
adjMem += estimateStringBytes(patId);
adjMem += estimateObjectOverhead(adjacency[patId]);
['up', 'down', 'left', 'right'].forEach((dir) => {
adjMem += estimateStringBytes(dir);
adjMem +=
adjacency[patId][dir].length * (patterns.length > 65535 ? 4 : 2) + 16;
});
}
}
workerStats.memory.adjacencyBytes = adjMem;
}
self.onmessage = function (e) {
const { type, imageDataBuffer, width, height, patternSize, enableRotations } =
e.data;
if (type === 'START_SETUP') {
console.log(
'[Setup Worker] Received START_SETUP. Image data buffer length:',
imageDataBuffer ? imageDataBuffer.byteLength : 'N/A'
);
sampleWidth = width;
sampleHeight = height;
srcDataView = new Uint8ClampedArray(imageDataBuffer);
currentPatternSizeW = patternSize;
enableRotationsW = enableRotations;
try {
extractPatterns();
if (!patterns || patterns.length === 0) {
self.postMessage({
type: 'SETUP_COMPLETE',
error: 'No patterns extracted.',
}); // Send error within SETUP_COMPLETE for main thread to handle
return;
}
buildAdjacency();
// patterns[i].cells are Uint8Arrays. Their .buffer will be transferred.
const transferablePatternCellBuffers = patterns.map(
(p) => p.cells.buffer
);
self.postMessage(
{
type: 'SETUP_COMPLETE',
payload: {
patternsData: {
patterns: patterns, // Array of pattern objects, .cells are Uint8Array
patternByID: patternByID,
patternByIndex: patternByIndex, // Send this explicitly if main thread uses it by this name
patternIdToIndex: patternIdToIndex,
adjacency: adjacency,
},
stats: workerStats,
},
},
transferablePatternCellBuffers
);
} catch (err) {
console.error('[Setup Worker] Error during setup process:', err);
self.postMessage({
type: 'SETUP_COMPLETE',
error: err.message + (err.stack ? '\n' + err.stack : ''),
});
}
}
};
// Catch unhandled errors in the worker
self.onerror = function (message, source, lineno, colno, error) {
console.error(
'[Setup Worker] Unhandled error:',
message,
source,
lineno,
colno,
error
);
// Try to inform the main thread, though if postMessage itself fails, this might not work.
try {
self.postMessage({
type: 'SETUP_COMPLETE',
error: `Unhandled error in setup worker: ${message}`,
});
} catch (e) {
console.error(
'[Setup Worker] Failed to postMessage about unhandled error.',
e
);
}
return true; // Prevents default error handling if possible
};