PDA

View Full Version : Approximating a switch when calling methods



Varsuuk
March 16th, 2019, 05:29
I started writing a six-check if/then chain for abilities when I said... hmmm no switch, I know - but you can make one via function table so I tried:



local aAbilityToUpdateSwitch = {
["strength"] = AbilityScoreBonusesManager.updateStrengthRelatedBo nuses,
["dexterity"] = AbilityScoreBonusesManager.updateDexterityRelatedB onuses,
["constitution"] = AbilityScoreBonusesManager.updateConstitutionRelat edBonuses,
["intelligence"] = AbilityScoreBonusesManager.updateIntelligenceRelat edBonuses,
["wisdom"] = AbilityScoreBonusesManager.updateWisdomRelatedBonu ses,
["charisma"] = AbilityScoreBonusesManager.updateCharismaRelatedBo nuses
}

function updateAttributeBonuses(nodeScore)
local node func = aAbilityToUpdateSwitch[nodeScore.getParent().getName()];

if func ~= nil then
func(nodeScore);
end
end


The above references methods in:
<script name="AbilityScoreBonusesManager" file="scripts/manager_ability_score_bonuses.lua" />

But the console logs the error complaining that "AbilityScoreBonusesManager is null in <first line referencing it>"


Any way around this?
I could create 6 one-liner methods that each call the method I want and put those one line names in the table. But anything else?

gamerhawaii
March 16th, 2019, 18:35
I believe you need to move the initialization inside "onInit()" because AbilityScoreBonusesManager has not been created yet. This worked for me:



local aAbilityToUpdateSwitch = nil;

function onInit()
aAbilityToUpdateSwitch = {
["strength"] = AbilityScoreBonusesManager.updateStrengthRelatedBo nuses,
["dexterity"] = AbilityScoreBonusesManager.updateDexterityRelatedB onuses,
["constitution"] = AbilityScoreBonusesManager.updateConstitutionRelat edBonuses,
["intelligence"] = AbilityScoreBonusesManager.updateIntelligenceRelat edBonuses,
["wisdom"] = AbilityScoreBonusesManager.updateWisdomRelatedBonu ses,
["charisma"] = AbilityScoreBonusesManager.updateCharismaRelatedBo nuses
};

Debug.console("strength: " .. updateAttributeBonuses("strength"));
Debug.console("charisma: " .. updateAttributeBonuses("charisma"));
Debug.console("junk: " .. updateAttributeBonuses("junk"));
end


function updateAttributeBonuses(nodeScore)
local func = aAbilityToUpdateSwitch[nodeScore];

if func ~= nil then
return func();
else
return "no matches";
end
end


with the AbilityScoreBonusesManager functions just being these:



function updateStrengthRelatedBonuses()
return "Strength";
end

function updateDexterityRelatedBonuses()
return "Dexterity";
end

function updateConstitutionRelatedBonuses()
return "Constitution";
end

function updateIntelligenceRelatedBonuses()
return "Intelligence";
end

function updateWisdomRelatedBonuses()
return "Wisdom";
end

function updateCharismaRelatedBonuses()
return "Charisma";
end

gamerhawaii
March 16th, 2019, 18:37
As a test extension attached

Varsuuk
March 16th, 2019, 18:43
[PARTIALLY SOLVED - but without understanding why other way fails]

So, I am back home and took a look at this. I ended up getting my code to work as well as identifying a single line of code that will cause this issue (although it is not fully covering all case, just a specific instance of the problem.)

First, let me give my "solution" (again, not sure it's THE way, but it's the way I got it to work.)



function updateAbilityScores(nodeScore)
updateAttributeBonuses(nodeScore);
-- just started this, so it's a one liner but more will be added as I continue...
end


function nodeCharFromAbilityScore(node)
return node.getChild("....");
end

...

local aAbilityToUpdateSwitch = {
["strength"] = function (nodeChar) AbilityScoreBonusesManager.updateStrengthRelatedBo nuses(nodeChar) end,
["dexterity"] = function (nodeChar) AbilityScoreBonusesManager.updateDexterityRelatedB onuses(nodeChar) end,
["constitution"] = function (nodeChar) AbilityScoreBonusesManager.updateConstitutionRelat edBonuses(nodeChar) end,
["intelligence"] = function (nodeChar) AbilityScoreBonusesManager.updateIntelligenceRelat edBonuses(nodeChar) end,
["wisdom"] = function (nodeChar) AbilityScoreBonusesManager.updateWisdomRelatedBonu ses(nodeChar) end,
["charisma"] = function (nodeChar) AbilityScoreBonusesManager.updateCharismaRelatedBo nuses(nodeChar) end
};

function updateAttributeBonuses(nodeScore)
local func = aAbilityToUpdateSwitch[nodeScore.getParent().getName()];

if func ~= nil then
func(nodeCharFromAbilityScore(nodeScore));
end
end


The problem is somehow likely related to whatever is happening under the hood when you preface a method in another script file with the "name" assigned to that "package" as it were.

So, my original attempt at putting the calls to the method didn't work (see first post)
Then I tried avoiding the explicit use of "AbilityScoreBonusesManager." in the function table. I figured, define a local var that references the function and put that var into the table instead (remember, total noob here so no clue if that was silly/crazy) then I got an error calling the method that I was calling up until now no problem. And the CALLER didn't change, yet the method it is calling became reported as being nil. So I looked and looked for typos, removed all code I could to keep it simple.

Eventually, I found out if I simply added this before the method I was calling I got the error:
local strBonusFunc = AbilityScoreBonusesManager.updateStrengthRelatedBo nuses;

I moved it up in the script to above a method I call before this point (with no issues) and that method all of us sudden is reported as being nil now.

Again, try this with one of your own package methods.



So, not calling this [SOLVED] but I DID figure out what I wanted, which is how to call a method (defined in another script file) in a table like a switch.
Added the above code block in case it helps anyone else.

Pardon my overly verbose names - it isn't just that I am verbose (I am...) it's my ADHD+bad memory - there is NO WAY I could have picked this project back up after a 10 month hiatus if not for my occasional inline comment and common self-commenting var/method names. Happens on projects that I spend 18 months coding where I do over 90% of the coding alone and yet 6+ months later I forgot much of what I wrote if, as too often happens, all the bloody bugs were worked out between QA and myself before release and I didn't have to keep touching it :(

Varsuuk
March 16th, 2019, 18:44
OOPS - I was typing my long message before these posts (I refreshed before starting - damn helpful race-conditions!) Will read now and see if you figured out what I was asking in last post!

Varsuuk
March 16th, 2019, 18:50
That makes sense!

But, as I typed that, I wondered about why it works when I define the method INSIDE the table ala "closure-esque" - but it still makes sense. Most likely (thinking indeterminate order of static definitions across compilation units in C) if it is "dereferenced" when first parsed? and it is nil still and never gets updated? But inside a table it is "created" after onInit and therefore at that time the reference is non nil.

All theories out my butt ;) BUT if my way (and yours) works it is good enough rationale for them and I will be satisfied enough not to rathole on it :)
I will try your extension though to try my alt way to fix on it too and verify works there as well.

Thanks - I LOVE how well folks help others here ;)

But now I am curious because what you said makes a lot of sense. Sure, the method is there and the package is there but maybe some of this is "evaluating

Varsuuk
March 16th, 2019, 19:13
Gamerhawaii,

I verified your fix - as expected, solves the issue.
I then tried doing something like what I outlined, moving the call to the outside method into an indirect call in the table. This did not work (well, it solved the error complaining that the package name was nil) it CALLS the method (seems fixed then right?) but even though console shows it is in that method that returns "Strength", the console reports an attempt to concat a nil value?

Anyhow - like said, the code now "works" that I listed above (I don't believe in leaving things not understood when I "solve" a problem because I might only have pushed it further down the line) but I will step away from it since now I can continue and perhaps thinking later on it and with your simple (well done - good idea) extension, I can isolate the issue better and find out the "why" and why it is still not good there.

What I changed in test.lua from your zip file:


--local aAbilityToUpdateSwitch = nil;

function onInit()
-- aAbilityToUpdateSwitch = {
-- ["strength"] = AbilityScoreBonusesManager.updateStrengthRelatedBo nuses,
-- ["dexterity"] = AbilityScoreBonusesManager.updateDexterityRelatedB onuses,
-- ["constitution"] = AbilityScoreBonusesManager.updateConstitutionRelat edBonuses,
-- ["intelligence"] = AbilityScoreBonusesManager.updateIntelligenceRelat edBonuses,
-- ["wisdom"] = AbilityScoreBonusesManager.updateWisdomRelatedBonu ses,
-- ["charisma"] = AbilityScoreBonusesManager.updateCharismaRelatedBo nuses
-- };

Debug.console("updateAttributeBonuses('strength') =", updateAttributeBonuses("strength"));
Debug.console("strength: " .. updateAttributeBonuses("strength"));
Debug.console("charisma: " .. updateAttributeBonuses("charisma"));
Debug.console("junk: " .. updateAttributeBonuses("junk"));
end


local aAbilityToUpdateSwitch = {
["strength"] = function () AbilityScoreBonusesManager.updateStrengthRelatedBo nuses() end,
["dexterity"] = function () AbilityScoreBonusesManager.updateDexterityRelatedB onuses() end,
["constitution"] = function () AbilityScoreBonusesManager.updateConstitutionRelat edBonuses() end,
["intelligence"] = function () AbilityScoreBonusesManager.updateIntelligenceRelat edBonuses() end,
["wisdom"] = function () AbilityScoreBonusesManager.updateWisdomRelatedBonu ses() end,
["charisma"] = function () AbilityScoreBonusesManager.updateCharismaRelatedBo nuses() end,
};

function updateAttributeBonuses(nodeScore)
-- the "local node func =" was a typo paste? "node" should not be there - didn't give error due to tuple feature ;)
local func = aAbilityToUpdateSwitch[nodeScore];

if func ~= nil then
return func();
else
return "no matches";
end
end

Varsuuk
March 16th, 2019, 19:22
LOL - I am so obsessive... decided to verify one last diff between ours and I added an argument which should not make a difference...

I added the following logging and method signature changes:


function updateStrengthRelatedBonuses(arg)
Debug.console("updateStrengthRelatedBonuses");
-- return "Strength";
Debug.console("So, here is the arg: ", arg);
return arg
end

-- left others as is, since it gets the error before they are called, didn't bother adding the args.



WHY does it not want to return your string? ;)
Your original works...


Received this in console:


Runtime Notice: Reloading ruleset
Runtime Notice: s'updateStrengthRelatedBonuses'
Runtime Notice: s'So, here is the arg: ' | s'hello world'
Runtime Notice: s'updateAttributeBonuses('strength') ='
Runtime Notice: s'updateStrengthRelatedBonuses'
Runtime Notice: s'So, here is the arg: ' | s'hello world'
Script Error: [string "test.lua"]:14: attempt to concatenate a nil value

gamerhawaii
March 16th, 2019, 19:33
if you are using my test module, you need to change this line:


return func();


to



return func(nodeScore);



otherwise you are not passing anything to the function and you will get a nil error

Varsuuk
March 16th, 2019, 20:01
Yup, I had already done that.
I'll just attach what I did - remember, the reason I changed it from your WORKING example was that I got it to work in the post before I saw yours by making the an embedded method to call the one I wanted. So tried to duplicate it in your extension as well.

See post #4 to see what I was trying to do here in my mod of yours to work like what I did.
The odd thing is even though console prints "So here is the arg: hello world" and then does a return on it - the concat still says nil.



Debug.console("So, here is the arg: ", arg);
return arg
end



Anyhow, attaching it

gamerhawaii
March 16th, 2019, 20:16
When defining your function, you did not return the value, just called the function.



["strength"] = function (arg) AbilityScoreBonusesManager.updateStrengthRelatedBo nuses(arg) end,


to this works:



["strength"] = function (arg) return AbilityScoreBonusesManager.updateStrengthRelatedBo nuses(arg) end,

Varsuuk
March 16th, 2019, 20:39
LOL, right - because I copied my real method which was performing UI updates :)


It's the stupid things I usually fall for.
Thanks much for seeing that.


So, now that we verified that it works by calling the method in the other script via the table but only if I do it indirectly - any thoughts as to why this and your onInit() work where if I had just done an unnamed method that calls the method I wanted it works. But if I change your Test3 to what I first tried:



--local aAbilityToUpdateSwitch = nil;
local aAbilityToUpdateSwitch = {
["strength"] = AbilityScoreBonusesManager.updateStrengthRelatedBo nuses,
["dexterity"] = AbilityScoreBonusesManager.updateDexterityRelatedB onuses,
["constitution"] = AbilityScoreBonusesManager.updateConstitutionRelat edBonuses,
["intelligence"] = AbilityScoreBonusesManager.updateIntelligenceRelat edBonuses,
["wisdom"] = AbilityScoreBonusesManager.updateWisdomRelatedBonu ses,
["charisma"] = AbilityScoreBonusesManager.updateCharismaRelatedBo nuses,
};


it is back to:


Runtime Notice: Reloading ruleset
Ruleset Warning: Font (windowtitle): Windows replaced specified font face (Alegreya Sans SC Bold) with (Alegreya Sans SC)
Script Error: [string "test.lua"]:3: attempt to index global 'AbilityScoreBonusesManager' (a nil value)

gamerhawaii
March 16th, 2019, 20:51
This may be right or wrong, but this would be my understanding.

When you are trying to use AbilityScoreBonusesManager directly, it is being called before everything is initialized so it does not exist yet. So the system cannot get the pointer to the functions, so you get nil. If you wait until "onInit()", now AbilityScoreBonusesManager exists and the system can see its functions.

When you wrap it in a function, you are defining the function to use right then so that function exists and can be assigned. The body of the function (which contains the call to AbilityScoreBonusesManager) is not evaluated until when called at run-time. By then, AbilityScoreBonusesManager exists.

gamerhawaii
March 16th, 2019, 21:25
Also, just to throw ideas out there. Since your functions are all named based on the name of the ability, you could also get rid of the table and use this instead (which finds the correct function based on its name.) Not saying you should do it this way, just providing another alternate. :)



function aAbilityToUpdateSwitch2(arg)
return AbilityScoreBonusesManager["update" .. arg:gsub("^%l", string.upper) .. "RelatedBonuses"];
end


then your call is like this instead:




local func = aAbilityToUpdateSwitch2(nodeScore);



or even just in-line it like:


local func = AbilityScoreBonusesManager["update" .. nodeScore:gsub("^%l", string.upper) .. "RelatedBonuses"];

Varsuuk
March 16th, 2019, 21:31
That is a succinct explanation and it makes sense to me.

I wondered as I said in prior response to your first onInit() solution if the second worked because only the "topmost" level is being evaluated immediately and as you said the method is created locally and is therefore visible at that time.
That the method being called is not yet defined is fine, it is not attempted until the method is called and by then it is defined and known.

Thanks a lot for your patience and efforts in helping me understand all of this.


The first part as I said before made good sense immediately. It may or may not be defined by the time this script is loaded. Putting it on onInit()of what I felt was the answer (the se

Varsuuk
March 24th, 2019, 03:29
Hey, again as before thanks ;) I decided to try your regex/sub idea as I may want to use it because while I like the explicitness of the table method (I am a big one, for example, in code for passing args versus defining "default args" other than when adapting code) I do not like that it creates a method in the table which calls a method. So lookup to call a method which calls a method is one too many calls for indulging in my "purity" ;P

I received an error however pointing to "gsub" being nil.
I fixed it as indicated, but posting in CASE I misused the original example or my version was naive (as I have little lua practice still and other languages creep in to mess me up at times.)



function updateAbilityBonuses(nodeScore)
local func = aAbilityToUpdateSwitch[nodeScore.getParent().getName()];

Debug.console(nodeScore);
abilName = string.gsub(nodeScore.getParent().getName(), "^%l", string.upper);
Debug.console("update" .. abilName .. "RelatedBonuses");
-- This one gave the error: Debug.console("update" .. nodeScore:gsub("^%l", string.upper) .. "RelatedBonuses");
...


Now, mind, you - I just never THOUGHT of "pasting together" the table name so that alone was a great service to me - to bump me out of my old C++ brain (and to lesser extent, Java now that I am forced to be full-time Java since August - one day I'll finish reading a good book on it lol... no need to worry that all FIX/OUCH Treasury/Bond traffic for NASDAQ goes through code I wrote on my own via googling about Java ;) )

So, rather than waste processing with gsub, etc - I can simply rename the method to be lowercase ;P it only slightly bothers me not to be camelCased. Probably I will change those methods to space_seperated_words to make my twitching stop. :)

But yeah, thanks - I will now remember and use the fact that I can basically create function names because they are merely syntactic sugar over the whole table["key"] thing anyway :)




Also, just to throw ideas out there. Since your functions are all named based on the name of the ability, you could also get rid of the table and use this instead (which finds the correct function based on its name.) Not saying you should do it this way, just providing another alternate. :)



function aAbilityToUpdateSwitch2(arg)
return AbilityScoreBonusesManager["update" .. arg:gsub("^%l", string.upper) .. "RelatedBonuses"];
end


then your call is like this instead:




local func = aAbilityToUpdateSwitch2(nodeScore);



or even just in-line it like:


local func = AbilityScoreBonusesManager["update" .. nodeScore:gsub("^%l", string.upper) .. "RelatedBonuses"];

Varsuuk
March 24th, 2019, 03:31
OK - I compromised since _-separated stuck out too much:
---> update_strengthRelatedBonuses

Now, thanks to you, a single line method:


function updateAbilityBonuses(nodeScore)
AbilityScoreBonusesManager["update_" .. nodeScore.getParent().getName() .. "RelatedBonuses"](nodeCharFromAbilityScore(nodeScore));
end