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 .
×
May your rolls be merry + bright! 🎄
Create a free account

How to efficiently detect if anyof several fields changed?

1676412049

Edited 1676412123
Daniel C.
Sheet Author
Here's a bit I'm maintaining in the D&D_OED character sheet script, to add up encumbrance values. There are 40 text fields titled attr_enc_1 , etc.     // Gather encumbrance fields     const items = [];     for ( i = 1; i <= 40; i++ ) {         items.push( 'enc_' + i );     }     // Compute total encumbrance     items.forEach( item => {         on( `change:${item} sheet:opened`, () => {             var total = 0;             getAttrs( items, values => {                 items.forEach( enc => {                     total += (parseFloat( values[enc] ) || 0);                 });                 setAttrs({total_enc: total});             });         });     }); The thing I notice here is a particular inefficiency: on sheet:opened, it seems that this script will re-compute the total encumbrance 40 times over (once for each item in the array, due to the outer forEach loop; i.e., 1600 total additions). Right? I can detect a bit of a hiccup-delay when the sheet loads as a result. I've tried a few things (e.g., an initial scan and setting a "needToRecompute" variable then an if statement), but nothing I'm trying seems to work. What the best way to make this efficient, and just compute the total once any time either sheet:opened or any change:enc_X occurs?
1676412782
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
For something like this, you don't want to replicate the sheet:opened  listener in each piece of the forEach; that should be it's own thing outside of the forEach. If you really need to do this on sheet open, the easy way to do it is to functionalize your on listener like so: // Gather encumbrance fields const items = []; for ( i = 1; i <= 40; i++ ) { items.push( 'enc_' + i ); } const computeEncumbrance = () => { getAttrs( items, values => { var total = 0; items.forEach( enc => { total += (parseFloat( values[enc] ) || 0); }); setAttrs({total_enc: total}); }); }; // Compute total encumbrance on('sheet:opened',computeEncumbrance); items.forEach( item => { on( `change:${item}`, computeEncumbrance); }); However, I'd wonder why you need to do this on sheet open. The encumbrance can't change without triggering the listener, so there's no situation where this should not already be computed at startup. If you're doing this to cover for crashes in the sheet, the better option is to find what's causing those crashes and fix that.
Scott C. said: For something like this, you don't want to replicate the sheet:opened  listener... This sounds solid to me. I've removed the sheet:opened listener from that point and others in the script (and added some reasonable starting defaults in the HTML), and it seems to be working fine, and much snappier to open. Thanks for the insight.
1676436005
GiGs
Pro
Sheet Author
API Scripter
sheet:opened is very useful when you are creating and testing workers, but generally speaking, you shouldn't use it in live code unless you have a specific reason to do so. I'm sure Scott was chafing at the bit fpr not pointing this out, but you also have getAttrs and setAttrs inside a loop, which is generally a bad idea. It will work, but it can be very slow and cause lag. Each run of computeEncumbrancedoes this, so you will be doing it for every item in items. It would be better to rewrite this so you only have one getAttrs (which includes all the items), a worker body that calculates everything in items, and a single setAttrs which saves all the calculated attributes.
1676436346

Edited 1676436381
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Eh, in this case, it's not a bad thing. The function is run as the listener, so it's only ever run once for a given event. I would not however recommend calling this function from other functions. That way lies much lag.
Thanks for considering that aspect. I'll confess I had to think about the same thing for a minute, but with the sheet:opened removed from the listener, it seems to pass the smell test (loop body only happens once per event) on my end. Ironically, one place I added a sheet:opened listener today was to create a hidden attribute on the sheet, esp. the first time the sheet is actually created. I don't know if there's otherwise a best practice in how to do that?
1676527488

Edited 1676527521
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
You can simply use a type hidden input: <input type="hidden" name="attr_hidden_attribute" value=""> It'll still show up on the attributes and abilities tab, but the only way to avoid that is to make a repeating attribute.
Great, much better, thanks for that!