Jump to content

[Solved] how to save a function in a variable?


Recommended Posts

Hi :)

I have a component with the variable "self.initfn". In this I save a function.

The OnSave part contains this:
data.initfn = self.initfn
And the OnLoad part of the function does contain
self.initfn = data and data.initfn or nil

All other values like numbers and strings are saved and loaded properly. But the function gets nil when loading.
Does anyone know if I need to do something special, to save and load a function?!

Edited by Serpens
Link to comment
Share on other sites

@Serpens Functions can't be serialized because all that's there is a memory address pointing to the function itself.

If you want to save a specific function from a set of functions that you know of, then you can save a function index instead.

The index mapping would essentially be a number/string variable that would identify the function it's supposed to save, and then the onload would remap the index to the proper function- this is a huge savings in saved data.

 

If you do not know the function because it's very dynamic/other mods are supposed to touch it, then check out string.dump functionality that outputs a very unpretty string that can be reloaded in via loadstring.

I'm unaware as to if the saver portion of Klei's code will like what's spewed in the string.dump as it contains raw binary data, but something you can check out.

Link to comment
Share on other sites

33 minutes ago, CarlZalph said:

@Serpens Functions can't be serialized because all that's there is a memory address pointing to the function itself.

If you want to save a specific function from a set of functions that you know of, then you can save a function index instead.

The index mapping would essentially be a number/string variable that would identify the function it's supposed to save, and then the onload would remap the index to the proper function- this is a huge savings in saved data.

 

If you do not know the function because it's very dynamic/other mods are supposed to touch it, then check out string.dump functionality that outputs a very unpretty string that can be reloaded in via loadstring.

I'm unaware as to if the saver portion of Klei's code will like what's spewed in the string.dump as it contains raw binary data, but something you can check out.

oh, that sounds very complicated...

So I will write more how my code looks, maybe you can tell me the specific solution for it:
A mod puts in modmain into GLOBAL.TUNING.QUESTMOD a quest, which looks like this:
{questname="myquest", difficulty=1,...,initfn=MyQuestInit}
While MyQuestInit is a local function inside this mod. This is the "Questmod". (if it helps, I could leave out the "local" of the function or put it directly into a component. At the moment I still can change everything in the code)

Now the API mod is putting these collected quests in modmain into his "questgiver" component, which is assigned to pigking.
One quest is active at once and all those values of the active quest like name, difficulty and the initfn are put into the self. variables of the questgiver component. When a quest is started, the APImod is calling the initfn, if it exists.

The goal is of course that the Questmod can use the API while still being able to make a complete customizable quest, by using his own init, check and solve functions.

 

So to sum it up:
The original function is stored in the Questmod, while the component which should save it is in the APImod

I guess this makes things more complicated? Or do you have a better idea, how quest and api mod can interact with each other?

Here some code how it looks like what I described above.
Questmod modmain:

local function InitEmoteQuest(giver)
    -- do some stuff
    print("EmoteQuest initialized")
end

table.insert(GLOBAL.TUNING.QUESTSMOD.QUESTS, {questname="Emote",questdiff=1,questnumber=1,initfn=InitEmoteQuest,talking={near={},far={},solved={},wantskip={},skipped={}}})

API mod modmain:

AddPrefabPostInit("pigking",function(inst)
    if not GLOBAL.TheNet:GetIsServer() then 
        return
    end    
    if inst.components.questgiver==nil then
        inst:AddComponent("questgiver")
        inst:AddTag("questgiver")
    end
    if GLOBAL.TUNING.QUESTSMOD and type(GLOBAL.TUNING.QUESTSMOD.QUESTS)=="table" then
        for k,quest in pairs(GLOBAL.TUNING.QUESTSMOD.QUESTS) do
            table.insert(inst.components.questgiver.questlist,quest)
        end
    end
    inst:DoTaskInTime(3,function(inst) inst.components.questgiver:StartNextQuest() end)
end)

Some code from Questgiver component:
 

function QuestGiver:InitializeQuest(quest)
    self.questname = quest.questname
    self.questdiff = quest.questdiff
    self.questnumber = quest.questnumber
    self.initfn = quest.initfn
    self.talking = quest.talking
    if type(self.initfn)=="function" then
        self.initfn(self.inst) -- the questgiver
    end
end

function QuestGiver:StartNextQuest()
    local questlist = self.questlist
	if self.questname==nil and next(questlist) then -- if there is no current quest and there is another quest in list
        local quest = questfunctions.MyPickSome(1, questlist)[1] -- pick and remove one quest
        self:InitializeQuest(quest)
    end
end

 

Edited by Serpens
Link to comment
Share on other sites

2 minutes ago, Serpens said:

Now the API mod is putting these collected quests in modmain into his "questgiver" component, which is assigned to pigking.
One quest is active at once and all those values of the active quest like name, difficulty and the initfn are put into the self. variables of the questgiver component. When a quest is started, the APImod is calling the initfn, if it exists.
 

Yeah you'll have to use the string.dump function in this case since you don't know what the functions are going to be from your mod.

 

Personally I'd enforce the requirement to have the initfn loaded on a per mod basis so when your API mod is checking quests from OnLoad it'd just not use/delete the data if the initfn is left blank.

Since people can subscribe/unsubscribe from mods at any time.

Link to comment
Share on other sites

14 minutes ago, CarlZalph said:

Personally I'd enforce the requirement to have the initfn loaded on a per mod basis so when your API mod is checking quests from OnLoad it'd just not use/delete the data if the initfn is left blank.

Thank you. Can you elaborate a bit more on this? I know the meaning of every word, but I did not understand what you wanted to tell me (my english is not that good).
So you say, that instead of trying to call self.initfn in the component of the API mod, the Questmod should call the initfn himself. But how to do that? After one quest is finisehd, the next quest is chosen and needs again initialization. Do I need an event or such thing for this, so the questmod can trigger the ini function?
Maybe a small example would help.

edit:
is it the above what you ment?
Would it work with events? So instead of calling self.initfn, the questmod would push a custom event, which the Questmod is listening for and then the questmod calls his initfunction.
If you think that will work, do you know how to create custom events? Is it enough to only PushEvent("myevent",...) ? Or do I need more?

Edited by Serpens
Link to comment
Share on other sites

Alternative Solution:
It seems you can also save functions in GLOBAL variables, so no need to save them in a component.
(at least if this is done in modmain, so on every game start repeated)

Edited by Serpens
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...