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

Using an Asychronous Infinite Loop in an API

Hello, I had a request come in concerning creating an API that co-locates all tokens related to the same character sheet across multiple maps.  It's used in conjunction with the VTTES so that some players are placed in the Physical World, others are placed in the Astral Plane (Earthdawn and Shadowrun specific), and we have plans to include the Matrix Landscape (Shadowrun specific).  A fourth map would be used as the GM's map, but whenever a player moved their Character on the Physical map, their token on the Astral, Matrix, and GM maps would also move to that same location. The issue I'm coming across is unifying the Initiative Tracker.  The end goal is to have each object on the Tracker reflect on each Page's Tracker and the GM's Tracker.  I'm mostly just brainstorming at this point, but one possible idea is to have an asynchronous infinite loop that constantly (or periodically) compares the Initiative trackers on each page and, when a change is detected, replicate those changes to all other pages.  Concerns are overloading the GM's tracker with duplicates, so it's not a likely solution, but I'm curious if such a looping process would heavily impact the R20 experience. The Pseudo-Code: On Ready, PageID.Tracker => var Tracker while(True) {   If PageID.Tracker !== Tracker {     Tracker <= PageID.Tracker     For each Page in Campaign {       Page.Tracker <= Tracker     }   } } Obviously these aren't the exact same methods, fields, etc, so if I need to clarify anything, let me know.  I just wanted to provide an idea of what would be happening continuously in the background. I suspect larger tables with many pages would experience a slow down in the performance of the actions within the loop, but I'm not sure if the overall experience would be impacted.
1692818070
timmaugh
Forum Champion
API Scripter
Why would you have to go to an async infinite loop? If you listen for the on('change:campaign') event, you will know both when a token is added to the Turn Order... AND when a player is moved to another page. The GM already sees all tokens across all pages in the game, so all of the characters would be there. If you trapped for when there was a change to the campaign, you could test the obj and prev to know whether a player had changed pages. Then you could parse the Turn Order, and look for tokens where that player was the primary/only controller. When you find one, look for a token of the same name on the new map that the player has arrived at, and edit/rebuild the Turn Order data to replace the token from the original page (where the player used to be) with the token from the new page (where the player is).... but keep the same initiative value. That keeps the initiatives matched across pages, but doesn't pollute the Turn Order with a bunch of extra entries. So not only with the GM only see one entry for each character (even if they are being moved to a new map), the player will see the proper entries in the Turn Order for the page they are on (including their own token on that page). You would just have to cover a few edge cases... like a player moving to a page where they didn't already have a token... so you might want a manual "rebalance turn order" button to trigger the check of what page each player is on, and whether the entry of each token the turn order matches the page id for the player with controlling rights. Here is a getPageForPlayer function The Aaron recently shared with me:     const getPageForPlayer = (playerid) => {         let player = getObj('player', playerid);         if (playerIsGM(playerid)) {             return player.get('lastpage') || Campaign().get('playerpageid');         }         let psp = Campaign().get('playerspecificpages');         if (psp[playerid]) {             return psp[playerid];         }         return Campaign().get('playerpageid');     }; Does that make sense, and did I understand what you were trying to do?
1692903628
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
As Timmaugh says, this is much simpler than doing an asynchronous constantly monitoring function. The other piece though is that Roll20 just isn't set up for this. Regardless of what you do, you are going to wind up with one of two results: The GM sees 4 copies of each token (1 for each map) on the initiative tracker while the players only see one of each The GM sees 1 copy of each token, but the players only see the initiative of the tokens on the page they are on This is just how the initiative tracker works, and the API has no method to change this basic functionality. I'd also question why you need combat initiative tracking across 3 different planes; I can't imagine running a game in which combat was running across three different planes.
I completely forgot I had brought this up. One issue I'm seeing is when an API makes a change, it doesn't trigger the onChange listeners.  This includes the Initiative Tracker, and sadly, the API we're using performs all the changes.  I've been wracking my brain for a method that could be used, and so far, it just seems unlikely without a drain on resources. And as Scott pointed out, the GM would see 4 copies of each token on the Tracker.  That's another issue that would need to be addressed, and so far, it just doesn't seem like there is a solution that results in only one copy of each token that every player (assuming they are on at least two different maps) and the GM could see.
Tracking initiative across 3 different planes is an edge case that does not come up often, but I've run a couple sessions testing this out, and the lack of initiative wasn't a terrible detriment to the experience.  The players loved the concept, so I figured I'd try to make it more easily possible and with all the knowledge (at least concerning initiative) on the board.
1694055088
GiGs
Pro
Sheet Author
API Scripter
There are two functions the api can use to set attribute values. .Set does not trigger change events, and .setWithWorker does. Is this your issue?
1694056965

Edited 1694056994
timmaugh
Forum Champion
API Scripter
One of the ways I talked about addressing this would result in only 1 token/character in the Turn Order, so the GM would NOT see multiple tokens. You would just need to rebuild the Turn Order every time someone changed pages. Imagine PlayerA and PlayerB, each with a single character (CharacterA and CharacterB). Those characters have tokens on 2 different maps (TokenA1, TokenA2, TokenB1, TokenB2). Both players are on Map1, and they roll initiative for their characters, populating the Turn Order with TokenA1 and TokenB1. Then the GM moves PlayerB to Map2, and your script monitors the event. It rebuilds the Turn Order and swaps out TokenB1 with TokenB2, preserving the original initiative. There are still 2 -- and ONLY 2 -- tokens in the Turn Order, so the GM doesn't see bloat. And PlayerA (still on Map1), properly loses sight of the initiative value for TokenB1, since CharacterB is on a different plane. Now, as for your original script not generating events, you just have to create a Observer registration in it and expose that in the script interface. That way, your new script (that would monitor when tokens are added to the Turn Order and when players move between maps) can register both for Roll20 events *AND* as an observer of your original script. When the original script makes a change you want your new script to know about, the original/main script would send an obj and a prev to all of the registered observers (which are the exact same functions as would register with Roll20. TokenMod does this, if you need an example... or post back and we can help if something isn't clear.
GiGs said: There are two functions the api can use to set attribute values. .Set does not trigger change events, and .setWithWorker does. Is this your issue? This is exactly what I was missing.  Thanks!  I believe I can make something happen with this. timmaugh said: One of the ways I talked about addressing this would result in only 1 token/character in the Turn Order, so the GM would NOT see multiple tokens. You would just need to rebuild the Turn Order every time someone changed pages. Imagine PlayerA and PlayerB, each with a single character (CharacterA and CharacterB). Those characters have tokens on 2 different maps (TokenA1, TokenA2, TokenB1, TokenB2). Both players are on Map1, and they roll initiative for their characters, populating the Turn Order with TokenA1 and TokenB1. Then the GM moves PlayerB to Map2, and your script monitors the event. It rebuilds the Turn Order and swaps out TokenB1 with TokenB2, preserving the original initiative. There are still 2 -- and ONLY 2 -- tokens in the Turn Order, so the GM doesn't see bloat. And PlayerA (still on Map1), properly loses sight of the initiative value for TokenB1, since CharacterB is on a different plane. Now, as for your original script not generating events, you just have to create a Observer registration in it and expose that in the script interface. That way, your new script (that would monitor when tokens are added to the Turn Order and when players move between maps) can register both for Roll20 events *AND* as an observer of your original script. When the original script makes a change you want your new script to know about, the original/main script would send an obj and a prev to all of the registered observers (which are the exact same functions as would register with Roll20. TokenMod does this, if you need an example... or post back and we can help if something isn't clear. The only issue I have with this is that PlayerA wouldn't have visibility on CharacterB, but should I be unable to implement GiG's solution, I'll be coming back to this suggestion.  I did have an idea where the missing piece was that listeners weren't triggered by .set, so if .setWithWorker does trigger listeners and the solution I have in mind works as intended, I'll have the solution, and I might even get it out onto the library.
1694732695

Edited 1694732721
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Note that setwithworker triggers sheetworker listeners, not API listeners. The API is deliberately set up to not trigger itself with changes that it makes. That said, if you already have a single API that is making all these changes, then you just need to redo your architecture so that it handles all the possibilities rather than doing it via event detection.
Scott C. said: Note that setwithworker triggers sheetworker listeners, not API listeners. The API is deliberately set up to not trigger itself with changes that it makes. That said, if you already have a single API that is making all these changes, then you just need to redo your architecture so that it handles all the possibilities rather than doing it via event detection. Appreciate the heads up.  Seems I'll have to find an alternative solution.  Ultimately, it doesn't seem like there's a way to make this happen without either leaving out the players on the second map or duplicating multiple turn order entries to the GM.  I'll continue giving it though as I work on other things.    
1694881992
timmaugh
Forum Champion
API Scripter
You could do the version that only has one entry for each character, so that you don't duplicate it for the GM, but then also manage a particular handout where you let the players see all of the initiative that they should be able to see. Hand out content can be updated pretty dynamically, so you could be sending new content to it all the time. That way, if the players really needed to see the initiative for another character, they could refer to the handout.