I am trying to port one of the programs I’ve written in my own programming language to the web using WebAssembly. However, I’ve run into a problem. Namely, one of my programs is supposed to print all the permutations of the digits of a number entered by the user. You can see the live version.
The problem is that, when you enter some number with a relatively large number of digits, such as “1234567”, even though the program starts finding permutations almost immediately, and prints them immediately into innerHTML, the browser doesn’t display any permutations at all until all of them are found. That’s not the desired behavior, the desired behavior is to print a permutation as soon as it’s found. How can I do that? The code I use for printing strings is here:
function printString(ptr) { let buffer=new Uint8Array(memory.buffer); let str=""; while (buffer[ptr]) { str+=String.fromCharCode(buffer[ptr]); ptr++; } document.getElementById("format_as_code").innerHTML+=str; }
Here is the code I am using to find the permutations:
/* * This will be a test to see whether calling JavaScript functions from AEC * works as expected. It will also attempt to expose as many potential compiler * bugs as possible by implementing the permutations algorithm. */ //So, those are JavaScript functions which I am planning to call from AEC: Function printInt(Integer32 int) Which Returns Nothing Is External; Function printString(CharacterPointer ptr) Which Returns Nothing Is External; Function clearScreen() Which Returns Nothing Is External; //JavaScript equivalent of C "strlen" is the "length" property of a string // and there is, as far as I know, no way to call it from outside of JS. //Nevertheless, I think I can easily write it myself. Function strlen(CharacterPointer ptr) Which Returns Integer32 Does Return ValueAt(ptr) = 0 ? 0 : 1 + strlen(ptr + 1); EndFunction Integer32 originalNumberOfDigits[10]; Integer32 NDEBUG := 1; Integer32 numberOfPermutations; Function recursiveProcedure(CharacterPointer currentAttempt) Which Returns Nothing Does Integer32 lengthOfTheCurrentAttempt := strlen(currentAttempt); If not(NDEBUG) Then printString( "DEBUG: "recursiveProcedure" has been invoked with the argument: "" ); printString(currentAttempt); printString("". "strlen" says it has length of "); printInt(lengthOfTheCurrentAttempt); printString(".n"); EndIf Integer32 currentNumberOfDigits[10] := {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; Integer32 i := 0; While i<lengthOfTheCurrentAttempt Loop currentNumberOfDigits[ValueAt(currentAttempt + i) - '0'] := currentNumberOfDigits[ValueAt(currentAttempt + i) - '0'] + 1; i := i + 1; EndWhile If not(NDEBUG) Then i := 0; While i < 10 Loop printString("DEBUG: The current number has "); printInt(currentNumberOfDigits[i]); printString(" digits '"); printInt(i); printString("'.n"); i := i + 1; EndWhile EndIf i := 0; While i < 10 Loop If currentNumberOfDigits[i] > originalNumberOfDigits[i] Then If not(NDEBUG) Then printString("DEBUG: There are too many digits '"); printInt(i); printString("', ending "recursiveProcedure".n"); EndIf Return; EndIf i := i + 1; EndWhile Integer32 haveWeFoundAPermutation := 1; i := 0; While i < 10 Loop If currentNumberOfDigits[i] < originalNumberOfDigits[i] Then haveWeFoundAPermutation := 0; EndIf i := i + 1; EndWhile If haveWeFoundAPermutation Then printString("Permutation #"); numberOfPermutations:=numberOfPermutations+1; printInt(numberOfPermutations); printString(": "); printString(currentAttempt); printString("n"); Return; EndIf Character digitWeAreAdding := '0'; While digitWeAreAdding < '9' + 1 Loop //The less-than-or-equal operator //">=" hasn't yet been implemented. Character newAttempt[12]; i := 0; While i < 12 Loop If i < lengthOfTheCurrentAttempt Then newAttempt[i] := ValueAt(currentAttempt + i); Else newAttempt[i] := 0; EndIf i := i + 1; EndWhile newAttempt[lengthOfTheCurrentAttempt] := digitWeAreAdding; If currentNumberOfDigits[digitWeAreAdding - '0'] < originalNumberOfDigits[digitWeAreAdding - '0'] Then //To speed //things up //a bit. recursiveProcedure(AddressOf(newAttempt[0])); EndIf digitWeAreAdding := digitWeAreAdding + 1; EndWhile EndFunction Function printPermutationsOfDigits(Integer32 original) Which Returns Nothing Does clearScreen(); If original < 0 Then original := -original; EndIf printString("Printing all the permutations of digits of the number: "); printInt(original); //Unfortunately, the JavaScript standard library //doesn't have "printf". printString("n"); Integer32 i := 0; While i < 10 Loop originalNumberOfDigits[i] := 0; i := i + 1; EndWhile If original = 0 Then originalNumberOfDigits[0] := 1; EndIf While original > 0 Loop originalNumberOfDigits[mod(original, 10)] := originalNumberOfDigits[mod(original, 10)] + 1; original := original / 10; EndWhile If not(NDEBUG) Then i := 0; While i < 10 Loop printString("DEBUG: The original number has "); printInt(originalNumberOfDigits[i]); printString(" digits '"); printInt(i); printString("'.n"); i := i + 1; EndWhile EndIf numberOfPermutations := 0; recursiveProcedure(""); printString("The end!"); EndFunction
Advertisement
Answer
The issue you are having is your code never gives the browser any time to update the UI. As others have pointed out, if you used a setTimeout in your code, you could give the browser some breathing room. But this would only work if the timeout occurred inside of recursiveProcedure
and was used to call the next iteration of recursiveProcedure
. This doesn’t seem very easy or viable. But there’s another solution:
Run your web assembly in a web worker.
Your HTML file would create a worker. Inside the worker you would require your was file and have your printString
. printString
would call back to the page to update the output. Something like this:
index.html
<script> var myWorker = new Worker('worker.js'); // When we get a message from the worker, update the format_as_code element with the data sent myWorker.onmessage = function(e) { document.getElementById("format_as_code").innerHTML += e.data; } </script>
worker.js
function printString(ptr) { let buffer=new Uint8Array(memory.buffer); let str=""; while (buffer[ptr]) { str+=String.fromCharCode(buffer[ptr]); ptr++; } // We post back to the page the contents of the string postMessage(str); } // This is the file that contains your wasm compilation result importScripts('wasm_compiled.js');