UNPKG

sudoku-wasm-engine

Version:

A WebAssembly-powered Sudoku solver with multithreaded batch processing capabilities

1 lines 7.41 kB
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.sudokuWasmEngine=e():t.sudokuWasmEngine=e()}(this,(()=>(()=>{"use strict";var t={d:(e,s)=>{for(var o in s)t.o(s,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:s[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{SudokuEngine:()=>s});class s{constructor(t){this.wasmLoaded=!1,this.wasmError=null,this.solveSudoku=null,this.solveBatch=null,this.allocateInputBuffer=null,this.allocateOutputBuffer=null,this.allocateSolvedFlags=null,this.freeAllBuffers=null,this.setPuzzle=null,this.getSolution=null,this.wasSolved=null,this.getCompletedThreadCount=null,this.requestStop=null,this.resetStopFlag=null,this.puzzles=[],this.solutions=[],this.batchSize=5e3,this.currentIndex=0,this.solvedCount=0,this.totalSolveTime=0,this.startTime=0,this.stopRequested=!1,this.isProcessing=!1,this.onProgressUpdate=null,this.onStatusChange=null,this.wasmPath=t||"../wasm/sudoku_pt.js"}async initialize(){return new Promise(((t,e)=>{try{const s=document.createElement("script");s.src=this.wasmPath,s.async=!0,window.Module={onRuntimeInitialized:()=>{try{this.initializeWasmFunctions(),this.wasmLoaded=!0,this.notifyStatusChange("WebAssembly module loaded with multithreaded batch processing support"),t()}catch(t){const s=t instanceof Error?t:new Error(String(t));this.wasmError=s,this.notifyStatusChange(`WebAssembly module failed to initialize: ${s.message}`),e(s)}}},s.onerror=t=>{const s=new Error("Failed to load WebAssembly module");this.wasmError=s,this.notifyStatusChange("Failed to load WebAssembly module"),e(s)},document.body.appendChild(s)}catch(t){const s=t instanceof Error?t:new Error(String(t));this.wasmError=s,this.notifyStatusChange(`Error loading WebAssembly module: ${s.message}`),e(s)}}))}initializeWasmFunctions(){const t=window.Module;if(!t||"function"!=typeof t.cwrap)throw new Error("WebAssembly module does not have the expected cwrap function");this.solveSudoku=t.cwrap("solveSudoku","string",["string"]),this.solveBatch=t.cwrap("solveBatch","number",["number"]),this.allocateInputBuffer=t.cwrap("allocateInputBuffer",null,["number"]),this.allocateOutputBuffer=t.cwrap("allocateOutputBuffer",null,["number"]),this.allocateSolvedFlags=t.cwrap("allocateSolvedFlags",null,["number"]),this.freeAllBuffers=t.cwrap("freeAllBuffers",null,[]),this.setPuzzle=t.cwrap("setPuzzle",null,["number","string"]),this.getSolution=t.cwrap("getSolution","string",["number"]),this.wasSolved=t.cwrap("wasSolved","boolean",["number"]),this.getCompletedThreadCount=t.cwrap("getCompletedThreadCount","number",[]),this.requestStop=t.cwrap("requestStop",null,[]),this.resetStopFlag=t.cwrap("resetStopFlag",null,[])}loadPuzzles(t){const e=t.split("\n").map((t=>t.trim())).filter((t=>81===t.length&&/^[0-9]+$/.test(t)));return this.puzzles=e,this.solutions=new Array(e.length).fill(""),this.currentIndex=0,this.solvedCount=0,this.notifyStatusChange(`Loaded ${e.length} puzzles`),e.length}solvePuzzle(t){if(!this.wasmLoaded||!this.solveSudoku)throw new Error("WebAssembly module not loaded");const e=performance.now(),s=this.solveSudoku(t),o=performance.now()-e;return s&&"No solution found"!==s?{solution:s,duration:o,solved:!0}:{solution:null,duration:o,solved:!1}}async startBulkSolve(t){if(!this.wasmLoaded)throw new Error("WebAssembly module not loaded");if(!this.puzzles.length)throw new Error("No puzzles loaded");if(this.isProcessing)throw new Error("Processing already in progress");void 0!==t&&(this.batchSize=t),this.isProcessing=!0,this.stopRequested=!1,this.currentIndex=0,this.solvedCount=0,this.totalSolveTime=0,this.startTime=performance.now(),this.solutions=new Array(this.puzzles.length).fill(""),this.resetStopFlag&&this.resetStopFlag(),this.notifyStatusChange("Starting bulk processing..."),this.updateProgress(),await this.processBatches();const e=((performance.now()-this.startTime)/1e3).toFixed(1);this.notifyStatusChange(this.stopRequested?`Stopped early. Solved ${this.solvedCount}/${this.currentIndex} puzzles in ${e}s`:`Solved ${this.solvedCount}/${this.puzzles.length} puzzles in ${e}s`)}async processBatches(){if(!(this.solveBatch&&this.allocateInputBuffer&&this.allocateOutputBuffer&&this.allocateSolvedFlags&&this.freeAllBuffers&&this.setPuzzle&&this.getSolution&&this.wasSolved))throw new Error("WebAssembly functions not available");let t=0;const e=Math.min(this.batchSize,this.puzzles.length);try{for(;t<this.puzzles.length&&!this.stopRequested;){const s=Math.min(t+e,this.puzzles.length),o=s-t;this.allocateInputBuffer(81*o),this.allocateOutputBuffer(81*o),this.allocateSolvedFlags(o);for(let e=0;e<o;e++)this.setPuzzle(e,this.puzzles[t+e]);const l=performance.now(),n=this.solveBatch(o),i=performance.now()-l;if(n<0)throw new Error("Batch processing failed");for(let e=0;e<o;e++)this.wasSolved(e)&&(this.solutions[t+e]=this.getSolution(e),this.solvedCount++);this.currentIndex=s,this.totalSolveTime+=i,this.freeAllBuffers(),this.updateProgress(),s%(4*e)==0&&await new Promise((t=>setTimeout(t,0))),t=s}}catch(t){this.notifyStatusChange(`Error: ${t instanceof Error?t.message:"Unknown error"}`),console.error("Batch processing error:",t)}finally{this.isProcessing=!1,this.freeAllBuffers&&this.freeAllBuffers(),this.updateProgress()}}stopProcessing(){this.stopRequested=!0,this.requestStop&&this.requestStop(),this.notifyStatusChange("Stopping after current batch...")}getSolutionsText(){return this.solutions.filter((t=>t&&81===t.length)).join("\n")}getPuzzle(t){return t>=0&&t<this.puzzles.length?this.puzzles[t]:null}getSolutionByIndex(t){return t>=0&&t<this.solutions.length&&this.solutions[t]?this.solutions[t]:null}setProgressCallback(t){this.onProgressUpdate=t}setStatusCallback(t){this.onStatusChange=t}updateProgress(){if(!this.onProgressUpdate)return;const t=performance.now()-this.startTime,e=this.puzzles.length>0?this.currentIndex/this.puzzles.length*100:0;let s=null,o=null;this.currentIndex>0&&this.currentIndex<this.puzzles.length&&(s=(this.puzzles.length-this.currentIndex)*(this.totalSolveTime/this.currentIndex/1e3),o=new Date(Date.now()+1e3*s));const l={currentIndex:this.currentIndex,totalPuzzles:this.puzzles.length,solvedCount:this.solvedCount,progressPercent:e,totalSolveTime:this.totalSolveTime,elapsedTime:t,isProcessing:this.isProcessing,averageSolveTime:this.currentIndex>0?this.totalSolveTime/this.currentIndex:0,timeRemaining:s,estimatedCompletion:o};this.onProgressUpdate(l)}notifyStatusChange(t){this.onStatusChange&&this.onStatusChange(t)}dispose(){this.freeAllBuffers&&this.freeAllBuffers(),this.solveSudoku=null,this.solveBatch=null,this.allocateInputBuffer=null,this.allocateOutputBuffer=null,this.allocateSolvedFlags=null,this.freeAllBuffers=null,this.setPuzzle=null,this.getSolution=null,this.wasSolved=null,this.getCompletedThreadCount=null,this.requestStop=null,this.resetStopFlag=null,this.onProgressUpdate=null,this.onStatusChange=null}isLoaded(){return this.wasmLoaded}getError(){return this.wasmError}getPuzzleCount(){return this.puzzles.length}getSolvedCount(){return this.solvedCount}getCurrentIndex(){return this.currentIndex}isRunning(){return this.isProcessing}}return e})()));