Source Maps are great, they let you map your compiled (even minified) code to the original lines in the source files.
Part of the new typescript starter is a system for parsing and utilising these source maps in Screeps.
Creating the Map
Screeps can’t read .map files as such you need to modify the file and its file type. rollup-plugin-screeps does this for you (here). Basically, you need to take the source map and wrap turn it into an exported JSON string.
moudle.exports = “SOURCE_MAP”;
Save that as map.js in your screeps code folder so that it gets uploaded to Screeps.
Using the Source Map
apemanzilla wrote a class for the Typescript Starter that can utilise the source map and produce stack traces.
I run a modified version that slots into my OS as a process.
First off, I load the source map consumer into the global scope when the file is required. Depending on the size of your codebase this can cause quite a large spike in CPU usage. You need the source-map package installed which you can get from npm. You will need to use a bundler like webpack or rollup to bundle source-map in your package.
When an error is thrown it is passed to this process and it translates it back to the original positions.
You need to wrap your code in a try/catch block so that you can handle the error yourself. I’ve added a plain javascript version of the function below where error is the caught error.
// Where `error` is the error caught by yout try/catch block | |
const stack = error instanceof Error ? error.stack : error; | |
const re = /^\s+at\s+(.+?\s+)?\(?([0-z._\-\\\/]+):(\d+):(\d+)\)?$/gm | |
let match: RegExpExecArray | null | |
let outStack = error.toString() | |
while(match = re.exec(stack)){ | |
if(match[2] === 'main'){ | |
const pos = consumer.originalPositionFor({ | |
line: parseInt(match[3], 10), | |
column: parseInt(match[4], 10) | |
}) | |
if(pos.line != null){ | |
if (pos.name) { | |
outStack += `\n at ${pos.name} (${pos.source}:${pos.line}:${pos.column})` | |
} else { | |
if (match[1]) { | |
// no original source file name known – use file name from given trace | |
outStack += `\n at ${match[1]} (${pos.source}:${pos.line}:${pos.column})` | |
} else { | |
// no original source file name known or in given trace – omit name | |
outStack += `\n at ${pos.source}:${pos.line}:${pos.column}` | |
} | |
} | |
} else { | |
// no known position | |
break; | |
} | |
} else { | |
// no more parseable lines | |
break; | |
} | |
} | |
console.log(outStack) |
This has made my debugging a lot easier.
Catching the errors
Catching errors in your screeps AI is something that everyone should be doing. The Typescript starter recommends wrapping your whole loop function in a try/catch block which works but is a little broad. You will catch the error but your AI will halt at the error and nothing else will get processed.
I wrap my call to each process in my OS in a try/catch block allowing 1 process to fail without killing the whole AI. If you looped through each creep for actions you could try/catch each creeps code so that 1 creep can fail without killing the others.