5E Character Create Playlist
  1. #1
    Varsuuk's Avatar
    Join Date
    Dec 2015
    Location
    New York
    Posts
    2,075

    Stumped: What are the triggers for "Recursive node event or call terminated"

    EDIT:
    I'M SORRY...
    So, I deleted the character and did it again to document entire "path" and found that.. it worked. Completely.
    Huh?
    NO IDEA how I got it fibered - but of course, how I work is create a char then add code - do stuff, reload, test, review, add logging, test, remove/add code, test again, etc.

    Maybe somehow?? I got in some state. But fresh - no happening. Will need to keep eye open to see if see how this can happen. Anyhow, not deleting the text in case I find it happen again - but no need to waste time reading this unless curious.

    ------------------------------------------


    I finished encoding the AddClass and addRace along with the rules regarding max levels and max levels for class/race and whether changing at start (mistake/mind change) and logging warnings if changed later - all sorts of "fun" things . [What can I say, I like thinking about corner conditions]

    I get what Recursion is NORMALLY. Something calls the same method in a chain. I understand the recursion might be tricker than something as obvious as you calling doFOO() which calls onFOOChanged() which called doY() which calls onFooChange() etc. But I am not sure what the recursion is here. I know it is likely this (long as it may be) is not sufficient to point out the issue, but I am hoping in Lua or FG there is something more experienced folk know to watch out for on these event handlers that can cause issue. For now, will look at doing it a different way - the way I am doing it now is because I copied code and modified it keeping the general structure - I may need to change the paths or handlers to get around this until I understand what I did wrong.


    Tracing the code:

    I got to the part where now on classLevelIUpdate, I set thac0 control. I do so via:
    Code:
    ...
    	nThac0 = ClassCommon.class_level_thac0[sClassID][nLevel] 
    Debug.console("nTHAC0=", nThac0)
    	DB.setValue(nodeChar, "thac0", "number", nThac0)
    (Commenting out setValue() call results in no recursion warning logging.)


    We got to the above method addClassLevel() via:
    1. Player drags from Classes dialog "Fighter" to the Character sheet that already had levels of Fighter and race = human.
    2. onDrop() from record_char_more.xml is triggered. Inside it:
    Code:
    ...
    					local nodeChar = getDatabaseNode()
    					if StringManager.contains({"pcclass"}, sClass) then
    						CharacterManagerSWWB.addClassDB(nodeChar, sClass, sRecord)
    						return true
    3. In manage_char_sw.lua the method addClassDB() is called:
    Code:
    function addClassLevel(nodeChar, sClass, sRecord)
    Debug.console(nodeChar, sClass, sRecord)
    	local sClassID = NodeUtils.getLeafNameFromRecord(sRecord)
    
    	local nLevel = DB.getValue(nodeChar, "pcclasslevel")
    	
    	-- Existing race, if any.
     	local _, sCurrentPCRaceRecord = DB.getValue(nodeChar, "pcracelink")
     	local sRaceID = NodeUtils.getLeafNameFromRecord(sCurrentPCRaceRecord)
    
     	local aAdvancementTable = ClassCommon.class_level_advancement[sClassID]
    
    	local nCurrentLevelXPNeeded = aAdvancementTable[nLevel]
    	local nNextLevelXPNeeded = -1
    	
    	local nMaxLevel = getMaxLevelAllowed(sClassID, sRaceID)
    	
    	nLevel = nLevel + 1
    
    	if nLevel <= nMaxLevel then
    		nNextLevelXPNeeded = aAdvancementTable[nLevel]
    	end
    
    	nThac0 = ClassCommon.class_level_thac0[sClassID][nLevel] 
    Debug.console("nTHAC0=", nThac0)
    	DB.setValue(nodeChar, "thac0", "number", nThac0)
    end
    ...
    -- Full method above, the "-1" value was set by me in prep to treat being at "max level" differently but I didn't get to try it or decide if will do it that way - the recursion needed to be addressed first.
    4. PRESUMABLY, I expected the setValue above to trigger onValueChanged below in record_char_more.xml file. BUT I do not see that log message "thac0 - onValueChanged" in log...?
    Code:
    ...
    			<!-- The thac0 field is tied to descending AC option. -->
    			<number_ctlink name="thac0">
    				<anchored to="combatframe" position="insidetopright" offset="15,91" />
    				<tooltip textres="tooltip_thac0" />
    				<script>
    					function onInit()
    						setValue(Character.defaultTHAC0())
    					end	
    
    					function onValueChanged()
    				Debug.console("thac0 - onValueChanged")
    						
    
    					end
    				</script>
    			</number_ctlink>
    ...

    The log of the issue:
    Code:
    Runtime Notice: Reloading ruleset
    Runtime Notice: s'aMajor: ' | { s'CoreRPG' = #4, s'MoreCore' = #1 } | s' aMinor: ' | { s'CoreRPG' = #0, s'MoreCore' = #58 }
    Runtime Notice: s'onInit: registerResultHandler'
    Runtime Notice: s'onInit: '
    Runtime Notice: s'drag1: ' | dragdata = { type = s'shortcut', desc = s'Classe: Fighter', #slots = #1, slot = #1, string = s'', num = #0, dice = {  }, shortcut = { pcclass, referencepcclass.fighter@MCSWWB }, token = { prototype = , instance =  }, custom = nil }
    Runtime Notice: s'draginfo: ' | dragdata = { type = s'shortcut', desc = s'Classe: Fighter', #slots = #1, slot = #1, string = s'', num = #0, dice = {  }, shortcut = { pcclass, referencepcclass.fighter@MCSWWB }, token = { prototype = , instance =  }, custom = nil }
    Runtime Notice: s'onDrop: ' | fn
    Runtime Notice: s'sClass: ' | s'pcclass'
    Runtime Notice: s'sRecord: ' | s'referencepcclass.fighter@MCSWWB'
    Runtime Notice: s'getDatabaseNode(): ' | databasenode = { charsheet.id-00030 }
    Runtime Notice: s'DB.getValue: ' | s'Fighter'
    Runtime Notice: s'getDescription: ' | s'Classe: Fighter'
    Runtime Notice: databasenode = { charsheet.id-00030 } | s'pcclass' | s'referencepcclass.fighter@MCSWWB'
    Runtime Notice: s'11111'
    Runtime Notice: databasenode = { charsheet.id-00030 } | s'Fighter' | s'referencepcclass.fighter@MCSWWB'
    Runtime Notice: s'nTHAC0=' | #14
    Script Warning: setValue: Recursive node event or call terminated (charsheet.id-00030.thac0)
    Runtime Notice: s'333333'


    * As an aside, I've been wondering where the Debug.console log above of "Runtime Notice: s'getDescription: ' | s'Classe: Fighter'"
    - It has to be in CoreRPG, MoreCore or my SWWB extension but I cannot find it. I am not sure if "Classe" is a typo of extra "e" or another language version of "class"
    Last edited by Varsuuk; January 10th, 2021 at 21:10.

  2. #2
    I've had to work around recursive issues (that did not go away - were somewhat timing based but duplicatable) by putting in global flags that tracked when I was in one thing so that I could force the conflicting call to skip processing if it was. It can be very dangerous to do this, but sometimes if you have code that multi tasks you can't avoid it as some OOB, DB handler, or xml window trigger will inevitably trigger it where the main thread is actually inside the thing your trying to update. Just have to be careful and keep an open mind on how to problem solve it when it happens - because almost every case of it is unique. Unless you just have a bug (bad coding logic).
    Free(Forums/Forge) Extension(FGU 5E):
    Paid (Forge) Extension(FGU 5E):

  3. #3
    damned's Avatar
    Join Date
    Mar 2011
    Location
    Australia
    Posts
    26,649
    Blog Entries
    1
    Its most likely because your charsheet code is in a constant state of evolution and sometimes a PC that was created earlier will have data stored that no longer matches the code for that field.
    I regularly delete my toons when developing as I have been burnt by that many times in the past.

  4. #4
    An example of a "Recursive node event or call terminated" is when:
    windowcontrol.setValue (or databasenode.setValue) function called in the same event loop more than once.

    In order to prevent common endless loop conditions (which would lock up the client), these situations are blocked.

    Regards,
    JPG

  5. #5
    Varsuuk's Avatar
    Join Date
    Dec 2015
    Location
    New York
    Posts
    2,075
    Thanks as always JPG.

    I am having trouble still imposing the limits. I expect it is that I am trying to do it in a way not intended and somehow just not seeing the RIGHT way to do it

    The following are some of the related code snippets put together into one code block. The first is from the XML the second from it's LUA flle.

    Part of my problem also, is that the way the onValueChanged works is if you type "11" in the box, it first processes as if typed only "1" (doesn't wait for enter/lost focus etc - it's an onCHANGED callback) then after done, reacts to 11, which causes the setValue(10) - which works here.

    So onValueChanged() get's called with values. "1", "11", and finally "10" on the one "action" of typing "11" in that box.
    Is the right way to find whatever the "onHitEnter()" and "onLostFocus()" calls and wait for one of them before processing the input? If so - any good examples?

    Code:
    			<totnumber name="level" source="pcclasslevel" >
    				<anchored to="pcclass" position="right" offset="7,-4" width="18" />
    				<tooltip text="Character Level" />
    				<script>
    					function onValueChanged()
    					Debug.console("LVL - onValueChanged", getValue())
    						local nodeChar = getDatabaseNode().getParent()
    						CharacterManagerSWWB.onClassLevelChanged(nodeChar)
    					end
    				</script>
    			</totnumber>
    
    
    function onClassLevelChanged(nodeChar)
    Debug.console("[[onClassLevelChanged]]")
    	local _, sPCClassRecord = DB.getValue(nodeChar, "pcclasslink")
    	local sClassID = NodeUtils.getLeafNameFromRecord(sPCClassRecord)
    
    	local _, sPCRaceRecord = DB.getValue(nodeChar, "pcracelink")
    	local sRaceID = NodeUtils.getLeafNameFromRecord(sPCRaceRecord)
    	local nLevel = DB.getValue(nodeChar, "pcclasslevel", 0)
    
    	if nLevel == 0 then
    		-- Zero is not a valid level.
    		nLevel = 1
    	end
    
    	local nAdjustedLevel = adjustForMaxLevel(nodeChar, nLevel, sClassID, sRaceID)
    
    	if nAdjustedLevel ~= nLevel then
    Debug.console("[[onClassLevelChanged -- DB.setValue(nodeChar, pcclasslevel]]")
    		DB.setValue(nodeChar, "pcclasslevel", "number", nAdjustedLevel)
    	end
    
    	updateForClassAndLevel(nodeChar, nAdjustedLevel, sClassID, sRaceID)
    end
    How do I enforce not editing this field to be more than 10 (when it is dependent on their class - so I could when class changes call a setMaxSomething on the control - is there any other way?

    What I did works, it just travels through 1(no help I guess) and 11 on the way to replacing itself with 10.
    Last edited by Varsuuk; January 21st, 2021 at 08:10.

  6. #6
    Varsuuk's Avatar
    Join Date
    Dec 2015
    Location
    New York
    Posts
    2,075
    I noticed onEnter() has a return of true or false, what happens different depending on which I returned? Is the true/false only for allowing soft enters or whatever line breaks are called?

    Code:
    onEnter
    
    function onEnter()
    If present, this function is executed whenever the user presses enter when editing the text. This method can be used to support behaviors allowing users to enter multiple records of data using the keyboard.
    
    Return values
    
    (boolean)
    If the event returns true, no further processing is done by the framework. If the return value is false, the framework processes the message after the script function (finishing the edit operation or creating a new line based on the multi line spacing property).

  7. #7
    If you need to wait until a string or number control fully completes before applying validation, make sure to us the "delaykeyupdate" tag on the control/template definition.

    For onEnter,
    If you return true, then all built-in processing is skipped.
    If you return false/nil, then the built-in processing for the control is followed. (For number and single-line strings, submits data and skips to next tab field; For multi-line strings, it inserts a line break.)

    Regards,
    JPG

  8. #8
    Varsuuk's Avatar
    Join Date
    Dec 2015
    Location
    New York
    Posts
    2,075
    Thanks JPG!

    So, to understand this better - not clear if the first sentence is connected directly to the "For onEnter" part.

    But to FIRST part:
    If I add "delaykeyupdate" page (which on the doc page says: "If present and editing the value of this control using the keyboard, changes won't be written to the database and update event functions will not be called until the focus is moved away from the control" ) then what do I do, and in which handler?

    If I had to guess, this means that onValueUpdated won't be called until after onLostFocus is completely executed? If that is true, then when hit enter - it is still not updated?
    Currently, with just tracing console messages,:

    1) Calling setValue() calls onValueChanged().
    2) TYPING in the number field will result in onValueChanged() called n times where n=number of keystrokes.
    3) Hitting enter key after typing changes results in the calls to onValueChanged as before PLUS "onEnter()" called.
    4) Hitting tab key after typing changesHitting enter after changing value results in the various onValueChanged calls and then call to "onLostFocus()"

    Here is a trace of entering data (not delay tag added yet:
    Code:
    {Here I typed 4 in the level control}
    Runtime Notice: s'LVL - onValueChanged' | #4
    Runtime Notice: s'[[onClassLevelChanged]]'
    Runtime Notice: s'rollForHP' | #4 | s'fighter'
    Runtime Notice: s'>>>>>>>>> rerollHPSelectBest'
    Runtime Notice: s'New >= currentHP'
    {Here I hit ENTER after typing "4" above}
    Runtime Notice: s'---------> function onEnter()'
    {Here I hit TAB key after hitting ENTER above}
    {Here I type "1" as part of my typing of "10"}
    Runtime Notice: s'---------> function onLoseFocus()'
    Runtime Notice: s'LVL - onValueChanged' | #1
    Runtime Notice: s'[[onClassLevelChanged]]'
    Runtime Notice: s'thac0 - onValueChanged'
    Runtime Notice: s'rollForHP' | #1 | s'fighter'
    {here I continue typing "10" with the last "0"}
    Runtime Notice: s'LVL - onValueChanged' | #10
    Runtime Notice: s'[[onClassLevelChanged]]'
    Runtime Notice: s'thac0 - onValueChanged'
    Runtime Notice: s'rollForHP' | #10 | s'fighter'
    Runtime Notice: s'>>>>>>>>> rerollHPSelectBest'
    Runtime Notice: s'New >= currentHP'
    
    
    I had updated the xml to add the tag - This FIXES my issue with calling onValueChanged() AS I am changing it (character by char):
    Runtime Notice: s'LVL - onValueChanged' | #4
    Runtime Notice: s'[[onClassLevelChanged]]'
    Runtime Notice: s'thac0 - onValueChanged'
    Runtime Notice: s'rollForHP' | #4 | s'fighter'
    Runtime Notice: s'>>>>>>>>> rerollHPSelectBest'
    Runtime Notice: s'New >= currentHP'
    Runtime Notice: s'---------> function onEnter()'
    Runtime Notice: s'---------> function onLoseFocus()'
    {HERE I type "10" THEN hit ENTER - but it calls onValueChanged with FULL "10" (GOOD!) and then it does the work and then it calls onEnter()}
    Runtime Notice: s'LVL - onValueChanged' | #10
    Runtime Notice: s'[[onClassLevelChanged]]'
    Runtime Notice: s'thac0 - onValueChanged'
    Runtime Notice: s'rollForHP' | #10 | s'fighter'
    Runtime Notice: s'>>>>>>>>> rerollHPSelectBest'
    Runtime Notice: s'New >= currentHP'
    Runtime Notice: s'---------> function onEnter()'
    So, if I turn on this delay, where do I check it? In the onValueChanged()? Can I say "nope" then do setValue(10) if someone types "11"? Or so I do this another way like say, in onEnter() return false if bad value and it never updates? But it seems to not call onEnter() when hit tab to "finish" the input. So if you tab out - how to do validate?


    Perhaps some pseudocode or example if this is used in some ruleset?


    Second part: "For onEnter()" - did I correctly take away from your rephrasing of the definition I posted in prior message that: if return false from onEnter() the "data" (newly typed number?) is NOT accepted and the field's content does not change?
    Last edited by Varsuuk; January 22nd, 2021 at 06:50.

  9. #9
    If you use delaykeyupdate tag, then you would perform the check in the onLoseFocus event or the onValueChanged event. If you use the onValueChanged event, you'll need a blocking variable to prevent setValue from creating an endless loop with onValueChanged.

    Also, are you aware of setMinValue and setMaxValue for number controls?

    Regards
    JPG

  10. #10
    Varsuuk's Avatar
    Join Date
    Dec 2015
    Location
    New York
    Posts
    2,075
    Thanks Moon - delaykeyupdate was the piece I needed to make it work as I wanted (only the one update.)

    I now have it working as I want with drag drop leveling one at time. Editing the lvl numeric control allows moving forward or back while validating or resetting values based on whether restrictions are enabled (RAW) or not.

    There were many corner cases (especially since I store the history of rolls and allow rollback/forward - which is technically useful for losing/restoring levels but more importantly for the mechanic of "oops" I didn't mean to do that and allows a rollback.)

    I'm back to THAC0 rolls now and class/race mods there.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
5E Product Walkthrough Playlist

Log in

Log in