Implemented queueing system for rollWorker to limit number of concurrent workers

Moved some consts to the config.ts file
Added flag to disable logging while rolling for a massive speed increase and to increase stability of the bot
This commit is contained in:
Ean Milligan (Bastion) 2022-06-19 01:51:16 -04:00
parent b58cf31edb
commit bb10d52506
9 changed files with 198 additions and 136 deletions

View File

@ -5,6 +5,11 @@ export const config = {
"localtoken": "local_testing_token", // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token"
"prefix": "[[", // Prefix for all commands
"postfix": "]]", // Postfix for rolling command
"limits": { // Limits for the bot functions
"maxLoops": 5000000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk
"maxWorkers": 16, // Maximum number of worker threads to spawn at once (Set this to less than the number of threads your CPU has, Artificer will eat it all if too many rolls happen at once)
"workerTimeout": 300000 // Maximum time before the bot kills a worker thread in ms
},
"api": { // Setting for the built-in API
"enable": false, // Leave this off if you have no intention of using this/supporting it
"port": 8080, // Port for the API to listen on

View File

@ -268,7 +268,7 @@ ${details}`,
details = 'Details have been ommitted from this message for being over 2000 characters.';
if (b.size > 8388290) {
details +=
'Full details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.';
'\n\nFull details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.';
return {
embed: {
color: infoColor2,
@ -283,7 +283,7 @@ ${details}`,
},
};
} else {
details += 'Full details have been attached to this messaged as a \`.txt\` file for verification purposes.';
details += '\n\nFull details have been attached to this messaged as a \`.txt\` file for verification purposes.';
return {
embed: {
color: infoColor2,

View File

@ -11,60 +11,33 @@ import {
sendDirectMessage,
} from '../../deps.ts';
import { SolvedRoll } from '../solver/solver.d.ts';
import { RollModifiers } from '../mod.d.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollEmbed, infoColor1, warnColor } from '../commandUtils.ts';
import { QueuedRoll, RollModifiers } from '../mod.d.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollEmbed, infoColor1, infoColor2, warnColor } from '../commandUtils.ts';
import rollFuncs from './roll/_index.ts';
export const roll = async (message: DiscordenoMessage, args: string[], command: string) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(`CALL INC_CNT("roll");`).catch((e) => {
log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`);
});
let currentWorkers = 0;
const rollQueue: Array<QueuedRoll> = [];
// If DEVMODE is on, only allow this command to be used in the devServer
if (DEVMODE && message.guildId !== config.devServer) {
message.send({
embeds: [{
color: warnColor,
title: 'Command is in development, please try again later.',
}],
}).catch((e) => {
log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`);
});
return;
}
// Rest of this command is in a try-catch to protect all sends/edits from erroring out
try {
const originalCommand = `${config.prefix}${command} ${args.join(' ')}`;
const m = await message.reply({
const rollingEmbed = {
embeds: [{
color: infoColor1,
title: 'Rolling . . .',
}],
});
};
// Get modifiers from command
const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand);
// Handle setting up and calling the rollWorker
const handleRollWorker = async (m: DiscordenoMessage, message: DiscordenoMessage, originalCommand: String, rollCmd: String, modifiers: RollModifiers) => {
currentWorkers++;
// gmModifiers used to create gmEmbed (basically just turn off the gmRoll)
const gmModifiers = JSON.parse(JSON.stringify(modifiers));
gmModifiers.gmRoll = false;
// Return early if the modifiers were invalid
if (!modifiers.valid) {
return;
}
// Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in
const rollCmd = `${command} ${args.join(' ')}`;
// const returnmsg = solver.parseRoll(rollCmd, modifiers) || <SolvedRoll>{ error: true, errorCode: 'EmptyMessage', errorMsg: 'Error: Empty message' };
const rollWorker = new Worker(new URL('../solver/rollWorker.ts', import.meta.url).href, { type: 'module' });
const workerTimeout = setTimeout(async () => {
rollWorker.terminate();
currentWorkers--;
m.edit({
embeds: [
(await generateRollEmbed(
@ -72,13 +45,13 @@ export const roll = async (message: DiscordenoMessage, args: string[], command:
<SolvedRoll> {
error: true,
errorCode: 'TooComplex',
errorMsg: 'Error: Roll Too Complex, try breaking roll down into simpler parts',
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
},
<RollModifiers> {},
)).embed,
],
});
}, 60000);
}, config.limits.workerTimeout);
rollWorker.postMessage({
rollCmd,
@ -87,6 +60,7 @@ export const roll = async (message: DiscordenoMessage, args: string[], command:
rollWorker.addEventListener('message', async (workerMessage) => {
try {
currentWorkers--;
clearTimeout(workerTimeout);
const returnmsg = workerMessage.data;
const pubEmbedDetails = await generateRollEmbed(message.authorId, returnmsg, modifiers);
@ -145,6 +119,79 @@ export const roll = async (message: DiscordenoMessage, args: string[], command:
log(LT.ERROR, `Unddandled Error: ${JSON.stringify(e)}`);
}
});
};
// Runs the roll or queues it depending on how many workers are currently running
const queueRoll = async (m: DiscordenoMessage, message: DiscordenoMessage, originalCommand: String, rollCmd: String, modifiers: RollModifiers) => {
if (!rollQueue.length && currentWorkers < config.limits.maxWorkers) {
handleRollWorker(m, message, originalCommand, rollCmd, modifiers);
} else {
m.edit({
embeds: [{
color: infoColor2,
title: `${config.name} currently has its hands full and has queued your roll.`,
description: `There are currently ${currentWorkers + rollQueue.length} rolls ahead of this roll.
The results for this roll will replace this message when it is done.`,
}],
}).catch((e) => {
log(LT.ERROR, `Failed to send message: ${JSON.stringify(m)} | ${JSON.stringify(e)}`);
});
rollQueue.push({ m, message, originalCommand, rollCmd, modifiers });
}
};
// Checks the queue constantly to make sure the queue stays empty
setInterval(async () => {
log(LT.LOG, `Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`);
if (rollQueue.length && currentWorkers < config.limits.maxWorkers) {
const temp = rollQueue.shift();
if (temp) {
temp.m.edit(rollingEmbed).catch((e) => {
log(LT.ERROR, `Failed to send message: ${JSON.stringify(temp.m)} | ${JSON.stringify(e)}`);
});
handleRollWorker(temp.m, temp.message, temp.originalCommand, temp.rollCmd, temp.modifiers);
}
}
}, 1000);
export const roll = async (message: DiscordenoMessage, args: string[], command: string) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(`CALL INC_CNT("roll");`).catch((e) => {
log(LT.ERROR, `Failed to call stored procedure INC_CNT: ${JSON.stringify(e)}`);
});
// If DEVMODE is on, only allow this command to be used in the devServer
if (DEVMODE && message.guildId !== config.devServer) {
message.send({
embeds: [{
color: warnColor,
title: 'Command is in development, please try again later.',
}],
}).catch((e) => {
log(LT.ERROR, `Failed to send message: ${JSON.stringify(message)} | ${JSON.stringify(e)}`);
});
return;
}
// Rest of this command is in a try-catch to protect all sends/edits from erroring out
try {
const originalCommand = `${config.prefix}${command} ${args.join(' ')}`;
const m = await message.reply(rollingEmbed);
// Get modifiers from command
const modifiers = rollFuncs.getModifiers(m, args, command, originalCommand);
// Return early if the modifiers were invalid
if (!modifiers.valid) {
return;
}
// Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in
const rollCmd = `${command} ${args.join(' ')}`;
queueRoll(m, message, originalCommand, rollCmd, modifiers);
} catch (e) {
log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`);
}

10
src/mod.d.ts vendored
View File

@ -1,4 +1,5 @@
// mod.d.ts custom types
import { DiscordenoMessage } from '../deps.ts';
// EmojiConf is used as a structure for the emojis stored in config.ts
export type EmojiConf = {
@ -22,3 +23,12 @@ export type RollModifiers = {
valid: boolean;
count: boolean;
};
// QueuedRoll is the structure to track rolls we could not immediately handle
export type QueuedRoll = {
m: DiscordenoMessage;
message: DiscordenoMessage;
originalCommand: String;
rollCmd: String;
modifiers: RollModifiers;
};

View File

@ -8,7 +8,7 @@ import config from '../../config.ts';
import { RollModifiers } from '../mod.d.ts';
import { CountDetails, ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts';
import { compareTotalRolls, escapeCharacters } from './rollUtils.ts';
import { compareTotalRolls, escapeCharacters, loggingEnabled } from './rollUtils.ts';
import { formatRoll } from './rollFormatter.ts';
import { fullSolver } from './solver.ts';
@ -49,7 +49,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
// Loop thru all roll/math ops
for (const sepRoll of sepRolls) {
log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRoll}`);
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRoll}`);
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the opertaion
const [tempConf, tempFormat] = sepRoll.split(config.postfix);
@ -59,7 +59,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
// Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
let parenCnt = 0;
mathConf.forEach((e) => {
log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
if (e === '(') {
parenCnt++;
} else if (e === ')') {
@ -74,7 +74,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
// Evaluate all rolls into stepSolve format and all numbers into floats
for (let i = 0; i < mathConf.length; i++) {
log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into mathable items ${JSON.stringify(mathConf[i])}`);
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into mathable items ${JSON.stringify(mathConf[i])}`);
if (mathConf[i].toString().length === 0) {
// If its an empty string, get it out of here
mathConf.splice(i, 1);
@ -198,7 +198,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
// Fill out all of the details and results now
tempReturnData.forEach((e) => {
log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
let preFormat = '';
let postFormat = '';

View File

@ -7,6 +7,7 @@ import {
import { roll } from './roller.ts';
import { rollCounter } from './counter.ts';
import { RollFormat } from './solver.d.ts';
import { loggingEnabled } from './rollUtils.ts';
// formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep
// formatRoll handles creating and formatting the completed rolls into the SolvedStep format
@ -21,7 +22,7 @@ export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll:
// Loop thru all parts of the roll to document everything that was done to create the total roll
tempRollSet.forEach((e) => {
log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
let preFormat = '';
let postFormat = '';

View File

@ -6,10 +6,7 @@ import {
import { ReturnData, RollSet } from './solver.d.ts';
// MAXLOOPS determines how long the bot will attempt a roll
// Default is 5000000 (5 million), which results in at most a 10 second delay before the bot calls the roll infinite or too complex
// Increase at your own risk
export const MAXLOOPS = 5000000;
export const loggingEnabled = false;
// genRoll(size) returns number
// genRoll rolls a die of size size and returns the result
@ -63,7 +60,7 @@ export const compareOrigidx = (a: RollSet, b: RollSet): number => {
export const escapeCharacters = (str: string, esc: string): string => {
// Loop thru each esc char one at a time
for (const e of esc) {
log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
// Create a new regex to look for that char that needs replaced and escape it
const temprgx = new RegExp(`[${e}]`, 'g');
str = str.replace(temprgx, `\\${e}`);

View File

@ -1,3 +1,4 @@
import config from '../../config.ts';
import {
log,
// Log4Deno deps
@ -5,7 +6,7 @@ import {
} from '../../deps.ts';
import { RollSet } from './solver.d.ts';
import { compareOrigidx, compareRolls, genRoll, MAXLOOPS } from './rollUtils.ts';
import { compareOrigidx, compareRolls, genRoll, loggingEnabled } from './rollUtils.ts';
// roll(rollStr, maximiseRoll, nominalRoll) returns RollSet
// roll parses and executes the rollStr, if needed it will also make the roll the maximum or average
@ -87,8 +88,8 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
throw new Error('YouNeedAD');
}
log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`);
log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`);
// Finish parsing the roll
if (remains.length > 0) {
@ -99,7 +100,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Loop until all remaining args are parsed
while (remains.length > 0) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing remains ${remains}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing remains ${remains}`);
// Find the next number in the remains to be able to cut out the rule name
let afterSepIdx = remains.search(/\d/);
if (afterSepIdx < 0) {
@ -157,7 +158,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing r> ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing r> ${i}`);
rollConf.reroll.nums.push(i);
}
break;
@ -168,7 +169,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true;
for (let i = 1; i <= tNum; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing r< ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing r< ${i}`);
rollConf.reroll.nums.push(i);
}
break;
@ -182,7 +183,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing cs> ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cs> ${i}`);
rollConf.critScore.range.push(i);
}
break;
@ -190,7 +191,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
for (let i = 0; i <= tNum; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing cs< ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cs< ${i}`);
rollConf.critScore.range.push(i);
}
break;
@ -204,7 +205,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing cf> ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cf> ${i}`);
rollConf.critFail.range.push(i);
}
break;
@ -212,7 +213,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
for (let i = 0; i <= tNum; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing cf< ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cf< ${i}`);
rollConf.critFail.range.push(i);
}
break;
@ -245,7 +246,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing !> ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing !> ${i}`);
rollConf.exploding.nums.push(i);
}
break;
@ -256,7 +257,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true;
for (let i = 1; i <= tNum; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Parsing !< ${i}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing !< ${i}`);
rollConf.exploding.nums.push(i);
}
break;
@ -279,7 +280,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Since only one drop or keep option can be active, count how many are active to throw the right error
let dkdkCnt = 0;
[rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach((e) => {
log(LT.LOG, `Handling roll ${rollStr} | Checking if drop/keep is on ${e}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Checking if drop/keep is on ${e}`);
if (e) {
dkdkCnt++;
}
@ -344,9 +345,9 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Initial rolling, not handling reroll or exploding here
for (let i = 0; i < rollConf.dieCount; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`);
// If loopCount gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) {
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
@ -378,9 +379,9 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// If needed, handle rerolling and exploding dice now
if (rollConf.reroll.on || rollConf.exploding.on) {
for (let i = 0; i < rollSet.length; i++) {
log(LT.LOG, `Handling roll ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`);
// If loopCount gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) {
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
@ -451,11 +452,11 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
if (rollConf.reroll.on) {
for (let j = 0; j < rollSet.length; j++) {
// If loopCount gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) {
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
log(LT.LOG, `Handling roll ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`);
rollSet[j].origidx = j;
if (rollSet[j].rerolled) {
@ -505,11 +506,11 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
let i = 0;
while (dropCount > 0 && i < rollSet.length) {
// If loopCount gets too high, stop trying to calculate infinity
if (loopCount > MAXLOOPS) {
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
log(LT.LOG, `Handling roll ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
// Skip all rolls that were rerolled
if (!rollSet[i].rerolled) {
rollSet[i].dropped = true;

View File

@ -11,6 +11,7 @@ import {
} from '../../deps.ts';
import { SolvedStep } from './solver.d.ts';
import { loggingEnabled } from './rollUtils.ts';
// fullSolver(conf, wrapDetails) returns one condensed SolvedStep
// fullSolver is a function that recursively solves the full roll and math
@ -32,7 +33,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
// Evaluate all parenthesis
while (conf.indexOf('(') > -1) {
log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
// Get first open parenthesis
const openParen = conf.indexOf('(');
let closeParen = -1;
@ -40,7 +41,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
// Using nextParen to count the opening/closing parens, find the matching paren to openParen above
for (let i = openParen; i < conf.length; i++) {
log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`);
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`);
// If we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one
if (conf[i] === '(') {
nextParen++;
@ -83,10 +84,10 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
// Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest)
const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']];
allCurOps.forEach((curOps) => {
log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
// Iterate thru all operators/operands in the conf
for (let i = 0; i < conf.length; i++) {
log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`);
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`);
// Check if the current index is in the active teir of operators
if (curOps.indexOf(conf[i].toString()) > -1) {
// Grab the operands from before and after the operator
@ -172,7 +173,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
// If we somehow have more than one item left in conf at this point, something broke, throw an error
if (conf.length > 1) {
log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`);
loggingEnabled && log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`);
throw new Error('ConfWhat');
} else if (singleNum && (typeof (conf[0]) === 'number')) {
// If we are only left with a number, populate the stepSolve with it