- Anatomy of a Sheet Worker
- Events, and watching Attributes
- Variables – How to Name Things
- Arithmetic in Sheet Workers
- What If? in Sheet Workers
- JavaScript Objects
- Getting Loopy With JavaScript
- Logging in the Browser Console
- Strings, Arrays, and Loops
- Asynchronicity and Things to Avoid With Loops
- Changes and the eventInfo Object
- Action Buttons
- setAttrs and Saving Attributes
- Castle Falkenstein Design – Sheet Workers
- The Perils of Sheet Worker Functions
- The Script Block and Identifying Characters
- Arrays and Dropdowns
- Undefined and Other Error Values
- The Ternary Operator – The One-Line If
- Template Literals
- Functions and the Fat Arrow
- Strings in Sheet Workers
- A Sheet Worker Reprise
You will sometimes here that some function is asynchronous. What does that mean?
An Explanation of Asynchronous
Imagine you have this code:
on('change:attribute', function() {
console.log('worker started.');
getAttrs(['attribute'], function(values) {
console.log('getAttrs running.');
});
console.log('worker ended.');
});
Code language: JavaScript (javascript)
What you’ll see printed in the console is most likely:
- worker started.
- worker ended.
- getAttrs running.
If you don’t know anything about synchronous functions, that is probably not the order you expected.
Most code is synchronous. That means each command is completed in the order it is listed, one command after another. But asynchronous commands are different – when they start, they take time to complete, and the rest of the code keeps going while they do their own thing.
In the above example, the first console statement is run. Then the getAttrs function begins. But it takes time to complete and the next console.log completes because it is synchronous and fast. Finally, the getAttrs function completes and its console statement is displayed.
Why Does This Happen
A Roll20 character sheet seems to have a bunch of stats on it, but in reality, they are just displayed on the character sheet. They are actually stored on Roll20’s servers.
This is a good thing – it means if your game crashes, those stats are safe (theoretically), and if one of your players crashes, their character’s stats are not taken down with their PC.
But there’s a downside here too – when you want to access a character’s stat, the Roll20 servers are contacted to get the current value, and that can create lag. At any given moment there might be many thousands of games being played, and each one has a bunch of players. All of them might need to update or read current stats and so reach out and contact Roll20’s servers.
With so many players, this can create a delay and apparent lag. Each is each put in a queue, until they get their turn to read the Roll20 servers. This is largely invisible to you, and while it happens very quickly, it’s not instant.
The Scope of getAttrs
Because asynchronous objects only have their values within their scope, any code that depends on them must be in that scope. This means that this will return an undefined error:
getAttrs(['a_stat'],function(values) {
/* some code */
});
console.log(values);
Code language: JavaScript (javascript)
When the console.log command runs, the getAttrs command has finished, and everything created inside of it is gone – values and anything made from it no longer exist. This works though:
getAttrs(['a_stat'],function(values) {
/* some code */
console.log(values);
});
Code language: JavaScript (javascript)
Remember that any command lines that depend on an asynchronous function must be inside the asynchronous function! (You can put multiple asynchronous functions inside each other.)
Avoiding Asynchronous Loops
The asynchronous functions in Roll20 are getAtts, setAttrs, and getSectionIDs. You need to minimise their use as much as possible. Remember each one is a separate call on Roll20’s servers, and that creates lag. Where possible, use just one of each in a sheet worker, and avoid putting them inside of a loop.
Imagine this arbitrary worker. Given a list of attributes, it adds 1 to each stat’s value.
const an_array = [/* list of a lot of attributes */];
getAttrs(an_array, function(values) {
an_array.forEach(function(stat) {
const current_stat = parseInt(values[stat]) || 0;
setAttrs({
[stat]: current_stat +1
});
});
});
Code language: JavaScript (javascript)
This is really slow. Recall that setAttrs expects an object variable, and you can supply that object.
const an_array = [/* list of a lot of attributes */];
getAttrs(an_array, function(values) {
const new_stats = {};
an_array.forEach(function(stat) {
const current_stat = parseInt(values[stat]) || 0;
new_stats[stat] = current_stat +1;
});
setAttrs(new_stats);
});
Code language: JavaScript (javascript)
Here we create an object, new_stats, to hold all the changed attributes. We loop through all the stats, add 1 to their value, and store them in new_stats. Then after the loop is finished, we call setAttrs just once.
A More Common Example
It’s easy to make this mistake without using a loop. For example:
setAttrs({ first_stat: first_stat_value});
setAttrs({ second_stat: second_stat_value});
setAttrs({ third_stat: third_stat_value});
Code language: JavaScript (javascript)
Here three stats have been modified. So three setAttrs functions are called. But it’s easy to combine these into a single setAttrs.
setAttrs({
first_stat: first_stat_value,
second_stat: second_stat_value,
third_stat: third_stat_value
});
Code language: JavaScript (javascript)
You can chain multiple changes inside the same setAttrs, and it’s probably easier to read.
In Summary
Some functions in Roll20 are asynchronous. When using them, add any code depending on them inside the asynchronous function. And where possible, combine multiple asynchronous functions into a single function.