
I was trying to make some minimalist test code for the problem in thread. Change to SetWithWorker ?
https://app.roll20.net/forum/post/12277607/change-to-setwithworker#post-12277638
When I found my new code did not display the issue being talked about but did perfectly highlight some other problems I have had. So here is some test code that highlights two bugs. HTML
<!DOCTYPE html> <meta charset="UTF-8"> <div> <input name="attr_i1" type="number" value="10" /> <input name="attr_i2" type="number" value="10" /> <input name="attr_i3" type="number" value="10" /> </div> <script type="text/worker"> // when i1 or d1 change, copy the new value into i2 or d2. on("change:i1 change:i2 change:i3", function test1( eventInfo ) { 'use strict'; log( "Sheetworker " + eventInfo[ "sourceAttribute" ] + " triggered"); log( JSON.stringify(eventInfo)); }); // This is for test1 </script>
The sheetworker simply logs if any of the values are changed.
and .js
// // Define a Name-space var Earthdawn = Earthdawn || {}; // Demo8. Log all changed attributes (prev then new). // There is a sheetworker that changes copies all fields that end in an odd digit to the next field. // This .js copies the values of all even fields to the next field. // The object is to see which fields get copied. Anything put into field i1, should quickly make its way to i6. on("ready", function() { 'use strict'; log( "Demo8 ready"); }); on("change:attribute", function (attr, prev) { 'use strict'; log( prev); // use prev["name"] and prev["current"] // {"name":"Wounds","current":0,"max":8,"_id":"-MlqexKD2f4f744TgzlK","_type":"attribute","_characterid":"-MlqeuXlNO51-RYxmJv8"} log( attr); // use attr.get("name") and attr.get("current"). // {"name":"Wounds","current":"1","max":8,"_id":"-MlqexKD2f4f744TgzlK","_type":"attribute","_characterid":"-MlqeuXlNO51-RYxmJv8"} let sa = attr.get( "name" ), cID = attr.get( "_characterid" ), current = attr.get( "current" ), num = parseInt( sa.slice( -1 )); if( num == 2 ) { log( "API " + sa + " triggered"); let aobj = Earthdawn.findOrMakeObj({ _type: 'attribute', _characterid: cID, name: 'i3' }, 20); // log( "before API setWW current = " + JSON.stringify( aobj )); aobj.setWithWorker( "current", current ); // log( "after API setWW current = " + JSON.stringify( aobj )); // let bobj = Earthdawn.findOrMakeObj({ _type: 'attribute', _characterid: cID, name: 'i1' }, 20); // bobj.set( "current", current ); } }); // end on("change:attribute" // Look for an object. If you can't find one, make one and return that. Earthdawn.findOrMakeObj = function ( attrs, deflt, maxDeflt ) { 'use strict'; try { //log(attrs); let obj, objs = findObjs( attrs ); if( objs ) { if( objs.length > 1 ) { Earthdawn.errorTrace( "Error Earthdawn:findOrMakeObj() found " + objs.length + " objects: " ); log( objs ); let keep = 0, maxscore = 0; // pick one to keep and get rid of the rest. for( let i = 0; i < objs.length; ++i ) { let score = 0; function scoreit( a, dflt ) { 'use strict'; if( a !== undefined && a !== null ) { if( typeof a != "string" ) ++score; // Gain points for not being string, and not being equal to default, and not evaluating to false, on the assumption that something tried to change it to those. Note that these criteria are rather arbitrary, but wanted to make decision based on something other than first or last. if( dflt !== undefined && a != dflt ) ++score; if( a ) ++score; } } scoreit( objs[ i ].get( "current" ), deflt ); scoreit( objs[ i ].get( "max" ), maxDeflt ); if( score > maxscore ) { keep = i; maxscore = score; } } let txt = ""; for( let i = objs.length -1; i > -1; --i ) if( i !== keep ) { txt += " attr[ " + i + " ],"; objs[ i ].remove(); } obj = objs[ keep ]; log( "removing" + txt.slice( 0, -1) + " and keeping attr[ " + keep + "]." ); } // end found more than one. else if( objs.length > 0 ) obj = objs[ 0 ]; } // end found one. if( obj === undefined && "_type" in attrs ) { // we did not find any, create one. let type = attrs[ "_type" ]; delete attrs[ "_type" ]; obj = createObj( type, attrs); if( obj && deflt !== undefined && deflt !== null ) obj.setWithWorker( "current", deflt ); if( obj && maxDeflt !== undefined && maxDeflt !== null ) obj.setWithWorker( "max", maxDeflt ); } return obj; } catch(err) { Earthdawn.errorTrace( "Earthdawn:findOrMakeObj() error caught: " + err ); } }; // end findOrMakeObj()
// Log a programming error and give trace information. Earthdawn.errorTrace = function( msg ) { 'use strict'; try { log( Earthdawn.timeStamp() + msg); if( Earthdawn.traceStack || Earthdawn.traceInfo ) log( "Possible " + (Earthdawn.traceStack ? "TraceStack: " + Earthdawn.traceStack : "") + (Earthdawn.traceInfo ? " TraceInfo: " + Earthdawn.traceInfo: "")); } catch(err) { log( "Earthdawn:ErrorTrace( " + msg + " ) error caught: " + err ); } } // end ErrorTrace() // return string with timestamp in it. Earthdawn.timeStamp = function () { 'use strict'; try { let today = new Date(); return today.getFullYear() + "-" + (today.getMonth() +1) + "-" + today.getDate() + " " + today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds() + " (UTC) "; } catch(err) { log( "Earthdawn:timeStamp() error caught: " + err ); } } // End timeStamp
The js is not strictly minimalist, since I included a few helper routines, but the heart of the code is the on change attribute routine.
So the code is simple. If any attribute changes, log the fact and if it is i2 that changed write a trigger message and make i3 the same value. Ignore i1 as it was there because the original sequence being tested had to have another value change as one of the things being tested.
So here is what I found.
New start, Browser log open, API console log open in other window. It has displayed "Demo8 ready", but nothing else.
Create a brand new character. I1, i2, and I3 all show their default value of 10.
Increases i2 (middle field) to 12.
browser log shows that sheetworker i2 was triggered and we see the new value of 12.
We see NOTHING new in the API log. API (on change i2) apparently was not triggered by this.
Increase i2 to 14.
Browser log shows that sheetworker i2 was triggered and new value of 14.
Now we see that the API is triggered successfully from 12 to 14.
{"name":"i2","current":"12","max":"","_id":"-OM5qekLU3_eotlocrNP","_type":"attribute","_characterid":"-OM5qcGDshiSRdor28sb"}{"name":"i2","current":"14","max":"","_id":"-OM5qekLU3_eotlocrNP","_type":"attribute","_characterid":"-OM5qcGDshiSRdor28sb"}
"API i2 triggered"
"Sheetworker i3 triggered"
"{\"sourceAttribute\":\"i3\",\"sourceType\":\"api\",\"triggerName\":\"i3\",\"newValue\":20}"
"Sheetworker i3 triggered"
"{\"sourceAttribute\":\"i3\",\"sourceType\":\"api\",\"triggerName\":\"i3\",\"previousValue\":20,\"newValue\":\"14\"}"
We also see above that the sheetworker was triggered twice, once when the API creates i3 with its default value of 20, and again when the api a fraction of a second later changes the value of i3 to be 14 to match the value of i2. This is as expected.
the api console log also this time shows only one on change i3 trigger, as it is changed once to 14. Note however that the character sheet still shows i3 with a value of 10 (which is what it displays if it does not see a database entry for i3), not the 14 that the API just set and that the sheetworker on the API thread acknowledged. The sheet is still displaying 10.
Increase i2 to 16, 18, etc.
All further increases of i2 are identical to the last. browser log shows that it is triggered on i2 change and sees the 16.
API log is again triggering successfully with i2 changing from14 to 16.
the API console log shows one triggering of i3 being changed to match i2.
AGAIN the character sheet is still displaying i3 as being 10 even though the API has twice changed it to 16.
If you change the character sheet from "character sheet" to "attributes and abilities" you will see all three values at their correct value (10, 16, and 16) and from then on i3 will display the value that the API is setting. But i3 had been stuck displaying its default value until the user did something to kick it into displaying as it should.
Analysis of this sequence.
I see two things wrong.
(a) The very first time I changed i2 the browser console log showed an update by "player", but the API did not trigger for some unknown reason.
(b) on a character where i3 does not exist in the database, the character sheet does not display changes to i3 made by the API, until something is done to "kick" the character into knowing the attribute now exists in the database such as clicking the "attributes and abilities" tab, or some other things.
There is a third problem that I have identified. Using the same code, if I do the following sequence, raise i2 to 12, raise i2 to 14, raise i3 (manually) to 11, raise i2 to 16, I get the following API logs.
{"name":"i2","current":"12","max":"","_id":"-OM5zcWKst8k7rCPnj8S","_type":"attribute","_characterid":"-OM5zaYLabc-RetTfGmP"}
{"name":"i2","current":"14","max":"","_id":"-OM5zcWKst8k7rCPnj8S","_type":"attribute","_characterid":"-OM5zaYLabc-RetTfGmP"}
"API i2 triggered"
"Sheetworker i3 triggered"
"{\"sourceAttribute\":\"i3\",\"sourceType\":\"api\",\"triggerName\":\"i3\",\"newValue\":20}"
"Sheetworker i3 triggered"
"{\"sourceAttribute\":\"i3\",\"sourceType\":\"api\",\"triggerName\":\"i3\",\"previousValue\":20,\"newValue\":\"14\"}"
{"name":"i2","current":"14","max":"","_id":"-OM5zcWKst8k7rCPnj8S","_type":"attribute","_characterid":"-OM5zaYLabc-RetTfGmP"}
{"name":"i2","current":"16","max":"","_id":"-OM5zcWKst8k7rCPnj8S","_type":"attribute","_characterid":"-OM5zaYLabc-RetTfGmP"}
"API i2 triggered"
"2025-3-24 7:12:14 (UTC) Error Earthdawn:findOrMakeObj() found 2 objects: "
[{"name":"i3","current":"14","max":"","_id":"-OM5zdHJ29-lSDwxCTg4","_type":"attribute","_characterid":"-OM5zaYLabc-RetTfGmP"},{"name":"i3","current":"11","max":"","_id":"-OM5ze-nXdZGPvFnDKYS","_type":"attribute","_characterid":"-OM5zaYLabc-RetTfGmP"}]
"removing attr[ 1 ] and keeping attr[ 0]."
"Sheetworker i3 triggered"
"{\"sourceAttribute\":\"i3\",\"sourceType\":\"api\",\"triggerName\":\"i3\",\"previousValue\":\"14\",\"newValue\":\"16\"}"
What seems to have happened is that when I raised i2 to 12, again nothing happened when it should have),
when I raised i2 to 14, it created i3 with a default value of 20, then changed it to 14, just like in the previous set of tests.
Then when I manually changed i3 to 11, it seems to have created a SECOND database entry for i3 with a different value.
then when I raised i2 to 16 the code I found necessary to put in to detect this exact condition found it, and deleted one. This has been happening for years and I have mentioned it before, but this code is a minimalist set for perfectly recreating it.
So anyway, this code demonstrates in an easily replicable way three bugs with the API.
(1) API On change attribute is not triggering for the first attribute changed.
(2) Attributes created by the API do not show as changed on the sheet when changed by the API.
(3) When the API creates a value, and then the user (or I think a sheetworker) changes a value, the system ends up with TWO values in the database and tends to deliver random ones to the sheet and/or API lookups.
These problems should probably be fixed.
Thank you.