PDA

View Full Version : onValueChanged function and infinite loop



cscase
November 4th, 2013, 05:46
I am trying to set up a function that enforces constraints on a value, but using the following in the onValueChanged() event:

function onValueChanged()
local nodeChar = window.link.getTargetDatabaseNode();
local sClass, sRecord = window.link.getValue();
local hprating = nodeChar.createChild("stats.Health.rating").getValue();


if sClass == 'charsheet' then
if getValue() > hprating then
setValue(hprating);
return;
end
end
end

What I am trying to do is say, "If, after this value is changed, it exceeds the maximum value for HP (referred to here as hprating), then knock this value back down to the maximum value." I've done this exact same thing in other functions and had it work, but here, it's not working and I'm getting the following warning:
Script Warning: setValue: Recursive control event or call terminated (numbercontrol:hp)

Can anyone see what I'm doing wrong here? I wonder if perhaps, by using setValue() within the onValueChanged event, I'm causing the event to be called again, and so on into infinity? If that's the case, I don't understand why this same sort of function is working for me elsewhere. I'd like to understand how I'm goofing this up, and also, if you know of a better way to code this kind of constraint, I'd appreciate any ideas!

Thanks!

Zeus
November 4th, 2013, 09:09
Yes, as your calling setValue() on the control from within onValueChanged() in the same control, it essentially will fire the onValueChanged event and will call itself , creating an infinite loop. Another approach:

i) add onInit and onValueChanged handlers to hprating control, in which they set the <max> field (if present) of your hp (number field?) control.
ii) in your hp control add the <max> field to the control definition (XML) and set it to a nominal value (10?), add an onInit() handler which updates the max field value based upon value of hprating.

Then just let FG's number control manage the hp field value via the max setting. If its ever set higher than the hprating (max field in hp) value, it will be set to hprating.

cscase
November 4th, 2013, 18:01
Oh man, that would make things SO MUCH simpler. Can I dynamically set the min and max properties for a control, though?


I had looked for that previously (looking here, which may or may not be the right place to look: https://www.fantasygrounds.com/refdoc/numbercontrol.xcp ) and don't see any function to do that.

Or, would you do it in a roundabout way, doing like <max>variablename</max>, and then just changing the variable's value to set the max?


If I could do things as you describe, though, that would be awesome. I could go back and get rid of a lot of awkward code that I created in doing these sort of checks elsewhere in the ruleset.

Thanks, Zeus.

Moon Wizard
November 4th, 2013, 22:40
No, there are currently no functions to set the min and max fields for a numbercontrol.

What I've done in the past is to create a local control variable that is checked before running the contents of the onValuehanged function.



local bProcessing = false;

function onValueChanged()
if bProcessing then
return;
end
bProcessing = true;

local sClass,_ = window.link.getValue();
if sClass == 'charsheet' then
local nodeChar = window.link.getTargetDatabaseNode();
local hprating = nodeChar.createChild("stats.Health.rating").getValue();

if getValue() &gt; hprating then
setValue(hprating);
end
end

bProcessing = false;
end


Cheers,
JPG

cscase
November 5th, 2013, 15:45
Ahhh, I gotcha. So this just stops recursion from happening. And the variable can't be a local one because it needs to be visible to different iterations of the event. This makes sense.

One thing I am curious about though is what is going on with the variable declaration. I had copied that from another function that I was looking at as an example.

In your response, you replaced sRecord with an underscore, which from what I have read means "placeholder for variable I don't need":

local sClass,_ = window.link.getValue();

I wondered about this when I was copying it but figured I'd best not jack with it... why the need for two variables in that declaration? If the second one is not needed, why do we need to mention it at all? Does link.getValue somehow return two separate values and I need to capture both?

Thanks for all your help!
Scott

Zeus
November 5th, 2013, 17:29
The getValue() from window.link (control) returns the class and record name of the link. Since the routine is only interested in the class (sClass), moon's code ignore's the record name returned (_).

cscase
November 5th, 2013, 20:37
Ahhh, okay, that makes sense. Thanks to you both for taking the time to teach me. I really appreciate it.

Scott

cscase
November 6th, 2013, 05:01
I can't seem to get this to actually do anything. It's no longer giving me the error message about recursion, but it's not actually preventing the value from exceeding its max, either. Here's the full control as I have it:



<number_ct_points name="hp">
<anchored to="rightanchor" width="30" height="20">
<top />
<right anchor="left" relation="relative" offset="-10" />
</anchored>
<tabtarget prev="init" next="magic" />
<script>
local bProcessing = false;


function onValueChanged()
if bProcessing then
return;
end
bProcessing = true;

local sClass,_ = window.link.getValue();
if sClass == 'charsheet' then
local nodeChar = window.link.getTargetDatabaseNode();
local hprating = nodeChar.createChild("stats.Health.rating").getValue();


if getValue() &gt; hprating then
Debug.chat('Whoah Nelly!');
setValue(hprating);
end
end
bProcessing = false;
end
</script>
</number_ct_points>

As you can see I put in a debug to tell me when the function event fires and finds all the conditions are being met and the value should be getting reset. But it's not getting actually reset. I also tried doing just setValue(5), to rule out a problem with the hprating variable, but it doesn't do that, either. I shook my fist at the computer emphatically and that, too, seems to have been fruitless.

I'm working my way down through the templates that are being used for this control, looking for anything that might be making it not work, but nothing is jumping out at me. I'll keep working on it but I thought I would at least ask, in case someone might recognize what is happening.

Trenloe
November 6th, 2013, 06:12
EDIT: Sorry, misread your code. Forget what I said... :o

Moon Wizard
November 6th, 2013, 06:17
Put different debug statements before the if bProcessing flag check, and after it is set to true to see where it is getting in the onValueChanged event function before exiting.

Cheers,
JPG

cscase
November 9th, 2013, 06:01
Hrmm, I went ahead and did this:


function onValueChanged() if bProcessing then
return;
end
Debug.chat('bProcessing ON');
bProcessing = true;

local sClass,_ = window.link.getValue();
if sClass == 'charsheet' then
local nodeChar = window.link.getTargetDatabaseNode();
local hprating = nodeChar.createChild("stats.Health.rating").getValue();


if getValue() &gt; hprating then
Debug.chat('Whoah Nelly!');
setValue(hprating);
end
end
Debug.chat('bProcessing OFF');
bProcessing = false;
end


And if I bump the number up over the limit (using the mouse wheel) I get:
bProcessing ON
Whoah Nelly!
bProcessing OFF

So it's like everything working. It recognizes that the number is above the limit. It's just not doing the setValue, it seems like.

I tried creating an onDoubleClick function to just setValue to a fixed number - to see if maybe this kind of object is not working with setValue, but that worked fine, which has me puzzled.

I also tried debugging the getValue() immediately after having setValue(hprating), and that shows that it hasn't changed. Then I tried just making a do-while, to have it repeatedly setValue(hprating) until they match, but that creates an endless loop and crashes me. So I can setValue to my heart's content, but it's not actually changing the value for some reason.

Trenloe
November 9th, 2013, 06:16
Try referencing the actual control in the setValue command. Something like window.hp.setValue(hprating);

cscase
November 9th, 2013, 07:48
That was a good thought, Trenloe. I tried explicitly referencing the control that way. The result of that is that I get the warning again about an infinite loop having been terminated.I think I'm just going to comment out this check for now and try to code in the constraint in other ways. This seems like it's turning into more trouble than its worth.Thanks Zeus, JPG, and Trenloe!