Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[HELP] Part of my API runs fine the first time it's run, and fine after restart, but not on consecutive runs.

1621860719

Edited 1621873083
Layton
API Scripter
My API is quite large now so I hope I can encapsulate all relevant information here. /**  * Sets the `current` and `max` values of the sheet's HP as per the configuration, either with averages or randomly.  * @param {Character} sheet  */ function setHp(sheet) {   const hp = getOrCreateAttrObject(sheet.id, "hp");   const form = getAttrByName(sheet.id, "npc_hpformula");   if (AWS_rollHp) {     if (form && rollHp()) return;     if (setAvgHp())       return toChat(         `Could not roll for HP! Did the beast have a valid entry in the npc_hpformula attribute?<br>**Average beast HP has been used instead.**`,         { code: 60 }       );   } else {     if (setAvgHp()) return;     if (form && calcAvgHp()) return;   }   setHpToOne();   return toChat(     `Could not find any HP attributes! **HP is set to 1 instead.**<br>To fix this, set max hp or npc_hpformula on the beast sheet.`,     { code: 61 }   );   function setAvgHp() {     const hpMax = hp.get("max") || "";     if (!hpMax) return false;     hp.setWithWorker("current", hp.get("max"));     return true;   }   function calcAvgHp() {     const count = /[0-9]+(?=d)/.exec(form)[0];     const die = /(?<=d)[0-9]+/.exec(form)[0];     const avg = +die / 2 + 0.5;     const tot = Math.ceil(+count * avg);     const resolve = form.replace(`${count}d${die}`, tot);     return setHpToResolve(resolve);   }   function rollHp() {     return setHpToResolve(form);   }   function setHpToOne() {     hp.setWithWorker({ current: "1", max: "1" });     return true;   }   function setHpToResolve(formula) {     sendChat(AWS_name, `/r ${formula}`, (r) => {       const resolved = r[0] && r[0].content && JSON.parse(r[0].content).total;       hp.setWithWorker({ current: resolved, max: resolved });     });     return true;   } } In the above code, when a creature has an empty hp.get("max")  value and the user has set AWS_rollHp to false , the setHpToOne()  function is called. I am sure it is running and that the hp attribute is correct. The issue is that after the first time setHpToOne()  is called, the .setWithWorker function calls are doing nothing. I am sure they are being called. Is there a solution to this? It seems ridiculous. I'll be editing this post with more information if it is requested. Referenced functions are here, but I'm almost certain they're not relevant: /**  * Attempts to find the attribute by name, else returns a new attribute.  * @param {string} _characterid  * @param {string} name Name of the attribute.  * @returns {Attribute}  */ function getOrCreateAttrObject(_characterid, name) {   try {     return getAttrObject(_characterid, name);   } catch (err1) {     if (err1.name === "RangeError") throw err1;     try {       return createObj("attribute", {         _characterid,         name,       });     } catch (err2) {       throw new Error(         `Failed to create Attribute Object with errors "${err2.message}" and "${err1.message}".`       );     }   } } /**  * Returns an attribute object found by character id and attribute name.  * @param {string} _characterid  * @param {string} name  * @returns {Attribute}  */ function getAttrObject(_characterid, name) {   const attrs = findObjs({     _type: "attribute",     _characterid,     name,   });   if (attrs.length < 1)     throw new Error(`Attribute by name "${name}" could not be found.`);   if (attrs.length > 1)     throw new RangeError(`Multiple attributes found with name "${name}".`);   return attrs[0]; } /**  * Output the supplied message to chat, optionally as a whisper and/or as an error which will also log to the console.  * @param {string} message  * @param {{code:number, player:string, logMsg:string}} options  */ function toChat(   message,   { code = undefined, player = undefined, logMsg = undefined } = {} ) {   const isError = code !== undefined;   const playerName = player && player.concat(" ").split(" ", 1)[0];   if (message)     sendChat(       isError ? AWS_error : AWS_name,       `${playerName ? "/w " + playerName + " " : ""}${"<br>" + message}`     );   if (isError)     log(AWS_log + (logMsg || message) + " Error code " + code + "."); }
1621862063
The Aaron
Roll20 Production Team
API Scripter
It's possible there's a race condition in the set calls?  Maybe try setting them together: function setHpToOne() { hp.setWithWorker({ current: "1", max: "1" }); toChat(`Could not find any HP attributes, so HP is set to 1 instead.`); } Just as an aside, I'd suggest not using try/catch blocks for flow control.  They aren't meant for such mundane tasks and are quite a bit heavier than a few if/else blocks.  They also have a nasty habit of hiding real failures behind the assumption that your exception was intended.
The Aaron said: Maybe try setting them together I didn't know that was an acceptable format for setWithWorker ! However, it did not solve the issue. The Aaron said: Just as an aside, I'd suggest not using try/catch blocks for flow control.  They aren't meant for such mundane tasks and are quite a bit heavier than a few if/else blocks.  They also have a nasty habit of hiding real failures behind the assumption that your exception was intended. I'm trying to get everything working before I clear up the try / catches, but ideally I don't want my API crashing the API system even if there is a real failure. Would you recommend against that? If so, why?
Try/catch statements removed from the function, original post updated to reflect this, problem persists.
1621873425

Edited 1621875967
The Aaron
Roll20 Production Team
API Scripter
I tend to follow the mantra of "Keep Exceptions Exceptional."  I protect code with exceptions when I can't handle the bad case myself, such as calling into code I don't control.  For example, TokenMod wraps its notify calls to registered callbacks with a try/catch and reports when one of the observers throws an exception.  In some cases, I'll wrap all of my code in a single try/catch block and report the context of the issue.  For example, TokenMod wraps its messageHandler function in a try/catch and reports the callstack for a crash, the command that was being parsed, and some other context of execution along with a link to where to report the problem so I can track down the issue and fix it. try/catch blocks by their nature require a fair bit of overhead to set up for the runtime, and if you don't need to waste the processing, why would you?  Certainly, if you can detect the failure case, there are more idiomatic ways of handling it in Javascript.  For example, you can return undefined if an operation can't be performed.  If you need to know how it failed, you can wrap your results in a context object with a success property set to true or false and an error message, error code, etc. which can be used by the calling code to deal with special case issues.  Since you aren't even special case handling most (if not all) of the exceptions you have, there's not really any point to them.  Removing them simplifies the code considerably: const getOrCreateAttrObject => (_characterid, name) => findObjs({ _type: "attribute", _characterid, name, })[0] || createObj("attribute", { _characterid, name, }); const hp = getOrCreateAttrObject(sheet.id, "hp"); if(hp) { /* ... */ Without losing any of the functionality you're taking advantage of.  It will be more performant, and easier to reason about.  And if anything is throwing an exception that you aren't expecting, you'll get the exception and can deal with it where it makes sense instead of hiding it in the control structure of a try/catch that should be an if. So to summarize, my recommendation is to have a try/catch at the top to handle the unforeseen issues (and possibly at the bottom where flow control passes to something you can't trust), and use idiomatic Javascript techniques for flow control in your code.
1621873717

Edited 1621873795
The Aaron
Roll20 Production Team
API Scripter
Just so I'm clear on what the actual problem is, the token's current and max value are properly updated, and the only issue is that the sheet worker that should be firing isn't after the first call?  Is the bar linked to an attribute? Scratch that, you're not dealing with a token, you're hitting the attribute directly.
The Aaron said: const getOrCreateAttrObject => (_characterid, name) => findObjs({ _type: "attribute", _characterid, name, })[0] || createObj("attribute", { _characterid, name, }); const hp = getOrCreateAttrObject(sheet.id, "hp"); if(hp) { /* ... */ This is fantastic, thanks. The Aaron said: So to summarize, my recommendation is to have a try/catch at the top to handle the unforeseen issues (and possibly at the bottom where flow control passes to something you can't trust), and use idiomatic Javascript techniques for flow control in your code. I'll try and stick to this more. It's obviously very good advice, so thanks for that :)
1621873916
The Aaron
Roll20 Production Team
API Scripter
Layton said: The Aaron said: So to summarize, my recommendation is to have a try/catch at the top to handle the unforeseen issues (and possibly at the bottom where flow control passes to something you can't trust), and use idiomatic Javascript techniques for flow control in your code. I'll try and stick to this more. It's obviously very good advice, so thanks for that :) Things do get a bit more complicated with asynchronous calls, which is another reason try/catch in Javascript can be problematic...
1621874017
The Aaron
Roll20 Production Team
API Scripter
So, the first time through, the attribute is set to 1/1, and the sheet worker fires.  The second time through, has the value of the attribute changed away from 1/1 and it's being set back to 1/1, or is it writing 1/1 over the existing 1/1?
1621874483

Edited 1621874527
Layton
API Scripter
The API is for 5th Edition D&D Wildshape. For context, a druid character keeps some of their own stats and some of a "beasts" stats, and the two are combined as they transform into a copy of the beast. My API creates a new sheet for this, which is then deleted when the user reverts from the wildshape back to their regular form. The first time the user wildshapes, the HP on the newly created sheet is correctly set to 1/1. The user then reverts and everything functions normally there. The second time the user wildshapes, the HP is never changed from empty strings, but after logging what was happening the API definitely has the correct attribute.
1621874662
The Aaron
Roll20 Production Team
API Scripter
That sounds intriguing.  If you do the .set() instead of .setWithWorker(), does it update the attribute correctly?
It does not, .set() behaves exactly the same (which is not at all after the first run).
If you want it, the full project is here:&nbsp; <a href="https://github.com/LaytonGB/AutomaticWildShape/blob/re-code/Automatic%20Wild%20Shape.js" rel="nofollow">https://github.com/LaytonGB/AutomaticWildShape/blob/re-code/Automatic%20Wild%20Shape.js</a> Right now there are still many bad try/catch statements but I'll get to that soon.
1621874979
The Aaron
Roll20 Production Team
API Scripter
Well, consistency is good, I guess.&nbsp; I don't know how much more help I can be without running the code and trying to debug it. Do you want to set up a test game with the code installed and PM me an invite so I can dig into it this evening?
I actually don't have a PRO subscription any more, I've been using the game of a friend to make this API. I'll make sure he's ready to promote you to GM and drop you an invite as and when he gets back to me. Unless I can GM as a guest GM? Though I'm under the impression that's not possible.
1621875403
The Aaron
Roll20 Production Team
API Scripter
You probably can't, but if you want, I can make a test game for it and send you an invite.
1621875516
The Aaron
Roll20 Production Team
API Scripter
(Check your PMs)