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

sheet worker execution order

1623666942

Edited 1623667107
Matthew C
Pro
Sheet Author
Well here I am again, this time with an execution order issue: const modifiers = ['staminaMax', 'passiveStamina', 'aggravatedValue', 'legsLevel', 'armsLevel', 'bonesLevel', 'nervesLevel', 'brainLevel', 'sensesLevel']; on("change:stamina change:shielding", function(eventInfo) { var statMax = 0; if (eventInfo.info === 'sheetworker') return; var stat = eventInfo.sourceAttribute; modifiers.forEach(function (stat) { getAttrs([stat], function(values) { var multiplier = stat === 'legsLevel' ? 3 : stat === 'armsLevel' ? 3 : stat === 'bonesLevel' ? 5 : stat === 'nervesLevel' ? 5 : stat === 'brainLevel' ? 5 : stat === 'sensesLevel' ? 5 : 1; var change = values[stat] * multiplier; console.log(stat + ' ' + multiplier); statMax = statMax + change; console.log(statMax); }); }); var v = +eventInfo.newValue || 0; var value = v < 0 ? 0 : v > statMax ? statMax : v; console.log(value); setAttrs({ [stat]: value }); }); Looking at this I would expect it to run the foreach, setting the statMax to what I need it to be and then for value to use the NEW statMax, although if you see the console.logs, the console.log(value) is run BEFORE the other one, and the statMax value being used it the 0 that it starts at...for the life of me I cannot understand why this is run this way, rather than one after the other... edit: on a side note, why is this run twice?
1623671193

Edited 1623671761
Oosh
Sheet Author
API Scripter
getAttrs() is an asynchronous function - the callback won't run until it's fetched the requested Attributes via a Promise. JS will continue to execute code until the Promise is resolved and reaches the front of the queue. Then the callback starts. You have very little control over this in synchronous JS - the only thing you can be sure of is that your callback function won't run until the parent function has resolved. The simplest way to make sure the last bit of code runs after the rest is to put it inside the callback. But you might have a reason for not wanting to do that, not sure. In that case, you could also rewrite it as an async function, giving you more control over sequencing. You can await each time you're calling an asynchronous function (you can await any line you want, it just won't do anything to synchronous code): on("change:stamina change:shielding", async function(eventInfo) { var statMax = 0; if (eventInfo.info === 'sheetworker') return; var stat = eventInfo.sourceAttribute; await Promise.all(modifiers.map(function (stat) { getAttrs([stat], function(values) { var multiplier = stat === 'legsLevel' ? 3 : stat === 'armsLevel' ? 3 : stat === 'bonesLevel' ? 5 : stat === 'nervesLevel' ? 5 : stat === 'brainLevel' ? 5 : stat === 'sensesLevel' ? 5 : 1; var change = values[stat] * multiplier; console.log(stat + ' ' + multiplier); statMax = statMax + change; console.log(statMax); }); })); var v = eventInfo.newValue || 0; var value = v < 0 ? 0 : v > statMax ? statMax : v; console.log(value); await setAttrs({ [stat]: value }); console.log(`Finished`); }); I can't test this properly as I don't have Pro, but I think that should now run in the order you want it to. One issue though - you have your getAttrs() inside the loop. getAttrs is one of the slower functions on a sheet, you generally want to grab all the Attributes you need with a single execution before you start iterating over them. You're also using stat twice - you've got it declared as a hoisted variable, then used it as the iterable name in your forEach loop. That's left me a bit confused about what exactly is supposed to be going on in the function - where is the var stat = eventInfo version supposed to be being used? I'm guessing that version of stat is only supposed to go at the end for the setAttrs. Oh, and the function will probably run twice if both the events you've specified were triggered.... I think! Anyway, my best guess as to what you're trying to do (with the stat confusion), I think this might work better for you: const modifiers = ['staminaMax', 'passiveStamina', 'aggravatedValue', 'legsLevel', 'armsLevel', 'bonesLevel', 'nervesLevel', 'brainLevel', 'sensesLevel']; on("change:stamina change:shielding", async function(eventInfo) { let statMax = 0; if (eventInfo.info === 'sheetworker') return; let triggerStat = eventInfo.sourceAttribute; await getAttrs(modifiers, async function(values) { modifiers.forEach((mod) => { let multiplier = mod === 'legsLevel' ? 3 : mod === 'armsLevel' ? 3 : mod === 'bonesLevel' ? 5 : mod === 'nervesLevel' ? 5 : mod === 'brainLevel' ? 5 : mod === 'sensesLevel' ? 5 : 1; let change = parseInt(values[mod],10) * multiplier; console.log(mod + ' ' + multiplier); statMax = statMax + change; console.log(statMax); }); }); let v = eventInfo.newValue || 0; let value = v < 0 ? 0 : v > statMax ? statMax : v; console.log(value); await setAttrs({ [triggerStat]: value });      console.log(`Finished`); }); I've changed the var s to let s, as it's generally better to avoid var unless you need your variable to be global. Change them back if you do need those variables in other parts of the code. getAttrs now just grabs all the stats once, then does the iteration, and finally triggerStat (the stat that triggered the event listener) is saved with the calculated value. I might have messed up your calculations though! The async code is probably superfluous at this point, as you can probably just put the setAttrs and accompanying calculations inside the getAttrs callback (but after the forEach loop!) and run it all synchronously.
1623674413

Edited 1623674668
Matthew C
Pro
Sheet Author
thank you const modifiers = ['staminaMax', 'passiveStamina', 'aggravatedValue', 'legsLevel', 'armsLevel', 'bonesLevel', 'nervesLevel', 'brainLevel', 'sensesLevel']; on("change:stamina change:shielding", async function(eventInfo) { if (eventInfo.sourceType === 'sheetworker') return; getAttrs(modifiers, async function(values) { let statMax = 0; let source = eventInfo.sourceAttribute; let v = +eventInfo.newValue || 0; modifiers.forEach(function (stat) { let multiplier = stat === 'legsLevel' ? 3 : stat === 'armsLevel' ? 3 : stat === 'bonesLevel' ? 5 : stat === 'nervesLevel' ? 5 : stat === 'brainLevel' ? 5 : stat === 'sensesLevel' ? 5 : 1; let change = parseInt(values[stat]) * multiplier; console.log(stat + ' ' + multiplier); statMax += change; console.log(statMax); }); let value = v < 0 ? 0 : v > statMax ? statMax : v; console.log(eventInfo.newValue); await setAttrs({ [source]: value }); }); }); this is the new version (can probably remove async though) the double run was due to eventInfo.info, which should be eventInfo.sourceType so it wasn't seeing it was coming from a sheetworker
1623677474
GiGs
Pro
Sheet Author
API Scripter
One thing that occurs to me about your latest sheet worker, is the event line (change:etc) only includes stamina and shielding. This is likely to be a problem, because players will at some point change, say, armslevel or nervelevel, or whatever. When they do, the statmax will no longer be correct, because it'll be based on the old stat value. So you need to add them to the change line as well. Which makes using eventInfo a tricky way to build this worker. Also that forEach in the middle looks really convoluted, and I think there's probably a better way to do that using javascript object variables. There is simpler way to do this, but I'd change this part: modifiers.forEach(function (stat) { let multiplier = stat === 'legsLevel' ? 3 : stat === 'armsLevel' ? 3 : stat === 'bonesLevel' ? 5 : stat === 'nervesLevel' ? 5 : stat === 'brainLevel' ? 5 : stat === 'sensesLevel' ? 5 : 1; let change = parseInt(values[stat]) * multiplier; console.log(stat + ' ' + multiplier); statMax += change; console.log(statMax); }); to: const   multiplier s  = {          legsLevel :   3 ,          armsLevel :   3 ,           bonesLevel :   5 ,          nervesLevel :   5 ,          brainLevel :   5 ,          sensesLevel :   5 ,          staminaMax :   1 ,          passiveStamina :   1 ,          aggravatedValue :   1       };        modifiers . forEach ( function  ( stat ) {          let   multiplier  =  multiplier s . hasOwnProperty ( stat ) ?  multiplier s [ stat ] :  1 ;          let   change  =  parseInt ( values [ stat ]) *  multiplier ;          console . log ( stat  +  ' '  +  multiplier );          statMax  +=  change ;          console . log ( statMax );       }); or even const   multipliers  = {          threes  = [ 'legsLevel' ,  'armsLevel' ],          fives :  [ 'bonesLevel' ,  'nervesLevel' ,  'brainLevel' ,  'sensesLevel' ]       };        modifiers . forEach ( function  ( stat ) {          let   multiplier  =  multipliers . threes . includes ( stat ) ?  3  :  multipliers . fives . includes ( stat ) ?  5  :  1 ;       
1623677733
Matthew C
Pro
Sheet Author
Thanks for the reply, will check this out. the function of this is to ensure the current value never goes above the max, so you are correct that right now if the max goes down the current would stay, need to fix that :D I do like the idea of using a constant for the multipliers though, that is a very nice touch
1623680302
GiGs
Pro
Sheet Author
API Scripter
Here's an alternative approach that handles the extra change, and distinguishing between which of stamina or shielding you are doing: const   modifiers  = [ 'staminaMax' ,  'passiveStamina' ,  'aggravatedValue' ,  'legsLevel' ,  'armsLevel' ,  'bonesLevel' ,  'nervesLevel' ,  'brainLevel' ,  'sensesLevel' ]; [ 'stamina' ,  'shielding' ]. forEach ( stat   =>  {    const   changes  =  modifiers . map ( m   =>   `change:  ${ m } ` ). join ( ' ' );    on ( `change: ${ stat }   ${ changes } ` ,  function  ( eventInfo ) {      if  ( eventInfo . sourceType  ===  'sheetworker' )  return ;      getAttrs ([... modifiers ,  stat ],  function  ( values ) {        let   statMax  =  0 ;        let   v  = + values [ stat ] ||  0 ;        const   multipliers  = {          threes  = [ 'legsLevel' ,  'armsLevel' ],          fives :  [ 'bonesLevel' ,  'nervesLevel' ,  'brainLevel' ,  'sensesLevel' ]       };        modifiers . forEach ( function  ( mod ) {          let   multiplier  =  multipliers . threes . includes ( mod ) ?  3  :  multipliers . fives . includes ( mod ) ?  5  :  1 ;          let   thismod  =  parseInt ( values [ mod ]) ||  0 ;          let   change  =  thismod  *  multiplier ;          console . log ( thismod  +  ' '  +  multiplier );          statMax  +=  change ;          console . log ( statMax );       });        let   value  =  v  <  0  ?  0  :  v  >  statMax  ?  statMax  :  v ;        console . log ( eventInfo . newValue );        setAttrs ({          [ stat ]:   value       });     });   }); }); The code is untested - there might be typos and misassigned variables, so check it carefully. But the basic idea: you use forEach at the start to create two separate sheet workers, one for stamina, and one for shielding. It could be more efficient, its occurring to me now, but calculating both stamina and shielding in the same sheet worker Since these are so many attributes they have in common, and a change in them will cause both sheet workers I created above to fire, it's probably worth combining them. Anyway, I did the work so I'm posting it :)
1623681001
GiGs
Pro
Sheet Author
API Scripter
Here's a quick update combining it all into one worker. There's a lot of complex techniques going on here I don't have time to explain, but you are able to figure this out pretty quickly. If you have any questions, ask away and we'll answer. const   modifiers  = [ 'staminaMax' ,  'passiveStamina' ,  'aggravatedValue' ,  'legsLevel' ,  'armsLevel' ,  'bonesLevel' ,  'nervesLevel' ,  'brainLevel' ,  'sensesLevel' ];    const   changes  =  modifiers . map ( m   =>   `change:  ${ m } ` ). join ( ' ' );    on ( `change:stamina change:shielding  ${ changes } ` ,  function  ( eventInfo ) {      if  ( eventInfo . sourceType  ===  'sheetworker' )  return ;      getAttrs ([... modifiers ,  'stamina' ,  'shielding' ],  function  ( values ) {        let   statMax  =  0 ;        let   stamina  = + values . stamina  ||  0 ;        let   shielding  =  + values . shielding   ||  0 ;        const   multipliers  = {          threes  = [ 'legsLevel' ,  'armsLevel' ],          fives :  [ 'bonesLevel' ,  'nervesLevel' ,  'brainLevel' ,  'sensesLevel' ]       };        modifiers . forEach ( function  ( mod ) {          let   multiplier  =  multipliers . threes . includes ( mod ) ?  3  :  multipliers . fives . includes ( mod ) ?  5  :  1 ;          let   thismod  =  parseInt ( values [ mod ]) ||  0 ;          let   change  =  thismod  *  multiplier ;          console . log ( thismod  +  ' '  +  multiplier );          statMax  +=  change ;          console . log ( statMax );       });        const   calcValue  =  v   =>   v  <  0  ?  0  :  v  >  statMax  ?  statMax  :  v ;        let   staminaNew  =   calcValue ( stamina );        let   shieldingnew  =   calcValue ( shielding );        const   output  = {};        if ( staminaNew  !==  stamina )  output . stamina  =  staminaNew ;        if ( shieldingNew  !==  shielding )  output . shielding  =  shieldingNew ;        if ( output ) {          setAttrs ( output );       }     });   }); I've assumed the statmax will be the same for both stamina and shielding - it looks that way from the calculation. With this, you only have to calculate it once. Then you check both shielding and stamina to see if that would cause changes - if so, save them into an output variable. If there is anything in the output variable, one or both stats changed, so call setAttrs to update the sheet with them.
1623685907
Matthew C
Pro
Sheet Author
const modifiers = ['staminaMax', 'passiveStamina', 'aggravatedValue', 'legsLevel', 'armsLevel', 'bonesLevel', 'nervesLevel', 'brainLevel', 'sensesLevel', 'bodyLevel', 'stamina'];                         const multipliers = {legsLevel: -3, armsLevel: -3, bonesLevel: -4, nervesLevel: -4, bodyLevel: -5, brainLevel: -5, sensesLevel: -4, staminaMax: 1, passiveStamina: 1, aggravatedValue: -1};                         const watchstats = modifiers.map(x => `change:${x}`).join(' ');                         on(`${watchstats}`, async function(eventInfo) {                             if (eventInfo.sourceType === 'sheetworker') return;                              getAttrs(modifiers, async function(values) {                                 let statMax = 0;                                 let source = eventInfo.sourceAttribute;                                 let v = values["stamina"] || 0;                                 modifiers.forEach(function (stat) {                                     if (stat === 'stamina') return;                                      let multiplier = multipliers.hasOwnProperty(stat) ? multipliers[stat] : 1;                                     let change = parseInt(values[stat]) * multiplier;                                     statMax += change;                                 });                                 let value = v < 0 ? 0 : v > statMax ? statMax : v;                                 console.log(statMax);                                 await setAttrs({                                     ["stamina"]: value                                 });                             });                                                      }); This is my current version, I removed the shielding (as those will have different variables in the long run) and added everything together and then added a check to ensure checking for stamina value to ignore that during th statMax change (since that value should not be added, as it is the checking value)